#!/usr/bin/env python3.5

from sys import exit, argv
from json import load
from subprocess import run
from hashlib import sha1
from os import path, getcwd, makedirs
from copy import deepcopy

class Options:

    def __init__(self, argv):
        self.argv = argv
        self.root_dir = argv[0]
        for i in range(2): # On the level of 2 from the root.
            self.root_dir = path.dirname(self.root_dir)
        self.cache_dir = path.join(self.root_dir, '.make')
        if not path.isdir(self.cache_dir):
            makedirs(self.cache_dir)
        self.current_dir = getcwd()

    def get_root_dir(self):
        return self.root_dir

    def get_cache_dir(self):
        return self.cache_dir

    def get_current_dir(self):
        return self.current_dir

    def verbose(self):
        if '-v' in argv:
            return True
        return False

class BuildUnit:

    def __init__(self, build_filename, options):
        self.options = options
        self.__parse_buildfile(build_filename)
        
    def __parse_buildfile(self, build_filename):
        with open(build_filename) as f:
            self.buildfile = load(f)

    def __get_cached_filename(self, filename, use_content=True):
        cache_dir = self.options.get_cache_dir()
        current_dir = self.options.get_current_dir()
        file_sha1 = filename
        filename = path.join(current_dir, filename)
        filename_sha1 = sha1(filename.encode('utf-8')).hexdigest()
        if use_content:
            with open(filename, 'rb') as f:
                file_sha1 = sha1(f.read()).hexdigest()
        return path.join(cache_dir, '{0}-{1}'.format(filename_sha1, file_sha1))
 
    def __compile(self, filename):
        cached_filename = self.__get_cached_filename(filename)
        root_dir = self.options.get_root_dir()
        filename = path.join(self.options.get_current_dir(), filename)
        if not path.isfile(cached_filename):
            print('[CC] {0}'.format(filename))
            command = 'g++ -I {0} -c {1} -o {2}'.format(root_dir, filename, cached_filename)
            if self.options.verbose():
                print(command)
            run(command, shell=True)
        return cached_filename

    def __link(self, objects):
        current_dir = self.options.get_current_dir()

        buildfile_type = self.buildfile['type']
        if buildfile_type  == 'library':
            executable = 'ld -r'
            name = self.__get_cached_filename(self.buildfile['name'], False)
        elif buildfile_type  == 'programm':
            executable = 'g++'
            name = path.join(current_dir, self.buildfile['name'])
        else:
            raise Exception('Wrong Buildfile type : {0}'.format(buildfile_type))

        print('[LD] {0}'.format(name))

        
        command = '{0} {1} -o {2}'.format(executable, ' '.join(objects), name)
        if self.options.verbose():
            print(command)
        run(command, shell=True)
        return name

    def __build_dep(self, dep):
        root_dir = self.options.get_root_dir()
        dep_dir = path.join(root_dir, dep)
        build_filename = path.join(dep_dir, 'Buildfile')
        dep_options = deepcopy(self.options)
        dep_options.current_dir = dep_dir
        dep_unit = BuildUnit(build_filename, dep_options)
        return dep_unit.build()

    def build(self):
        objects = []
        for filename in self.buildfile['files']:
            objects.append(self.__compile(filename))
        if 'deps' in self.buildfile:
            for dep in self.buildfile['deps']:
                objects.append(self.__build_dep(dep))
        return self.__link(objects)

def main(argv):
    options = Options(argv)
    unit = BuildUnit('Buildfile', options)
    unit.build()

if __name__ == "__main__":
    exit(main(argv))
