import os
import fnmatch
import glob
import types
import time
from os import path
from scripts.run import run

def get_sources_path(directory, pattern):
    """obtain path of all source files for a matching pattern"""
    matches = []
    for root, dirnames, filenames in os.walk(directory):
        for filename in filenames:
            full_path = os.path.join(root, filename)
            if fnmatch.filter([full_path], pattern):
                matches.append(os.path.join(root, filename))
    return matches
    
class Builder(object):
    """helper to generate tasks to compile vala code"""

    def __init__(self,
                 source_directory,
                 valac_command,
                 cc_command,
                 linker_command,
                 target_binary,
                 link = None,
                 dependencies = None):
        self.source_directory = source_directory
        self.valac_command = valac_command
        self.cc_command = cc_command
        self.linker_command = linker_command
        self.target_binary = target_binary
        self.link = link
        self.dependencies = dependencies

    def build(self):
        source_directory = self.source_directory
        valac_command = self.valac_command
        cc_command = self.cc_command
        linker_command = self.linker_command
        target_binary = self.target_binary
        
        if not self.dependencies == None:
            bindep = [path.join('build', 'bin', f) for f in self.dependencies]
        else:
            bindep = []
            
        copied_csource_paths = get_sources_path(source_directory, '*.c')
        copied_cheader_paths = get_sources_path(source_directory, '*.h')
        vala_source_paths = get_sources_path(source_directory, '*.vala')
        build_directory = path.join('build', source_directory)

        csource_files = [path.basename(f) for f in copied_csource_paths]
        generated_csource_paths = []
        for vala_path in vala_source_paths:
            vala_file = path.basename(vala_path)
            cfile = vala_file.replace('.vala', '.c')
            cpath = path.join(build_directory, cfile)
            generated_csource_paths.append(cpath)
            csource_files.append(cfile)
       
        build_file = path.join(build_directory, 'placeholder')
        yield {
            'basename': 'mkdir ' + build_directory,
            'actions': ['mkdir -p ' + path.join('build', 'bin'),
                        'mkdir -p ' + build_directory, 
                        '[ -e "' + build_file + '" ] || touch "' + build_file + '"'],
            'targets': [build_file],
        }
        
        copied_csources = []
        copied_cheader = [] 
        for csource in copied_cheader_paths + copied_csource_paths:
            dest = path.join(build_directory, path.basename(csource))
            
            if not dest[-2:] == ".h":
                copied_csources.append(dest)
            else:
                copied_cheader.append(dest)
                
            yield {
                'basename': 'copy ' + csource,
                'file_dep': [build_file] + [csource],
                'actions': ['cp ' + csource + ' ' + build_directory],
                'targets': [dest]
            }
        
        yield {
            'basename': 'valac ' + source_directory,
            'file_dep': [build_file] + vala_source_paths + bindep,
            'actions': [valac_command],
            'targets': generated_csource_paths
        }
               
        csource_paths = generated_csource_paths + copied_csources
        object_files = []
        
        for csource in csource_paths:
            object_file = path.basename (csource.replace('.c', '.o'))
            object_files.append(object_file);
             
            command = cc_command.replace('C_SOURCE', csource)
            object_path = path.join(build_directory, object_file)
            command = command.replace('OBJECT_FILE', object_path)
            yield {
                'basename': 'compile ' + csource,
                'file_dep': [build_file, csource] + bindep + copied_cheader,
                'actions': [command],
                'targets': [path.join(build_directory, object_file)],
            }
            
        object_paths = [path.join(build_directory, f) for f in object_files] 
        yield {
            'basename': source_directory,
            'file_dep': object_paths + [build_file] + bindep,
            'actions': [linker_command],
            'targets': [path.join('build', 'bin', target_binary)]
        }

        if not self.link == None:
            createlink = 'cd build/bin/ && ln -s -f ' + target_binary + ' ' + self.link
            yield {
                'basename': 'Create link ' + target_binary + ' ' + self.link,
                'file_dep': [path.join('build', 'bin', target_binary)],
                'actions': [createlink],
                'targets': [path.join('build', 'bin', self.link)]
            }

def is_up_to_date(task):
    for target in task['targets']:
        if not path.isfile(target):
            return False

    if not 'file_dep' in task.keys():
        return False

    for dep in task['file_dep']:
        if not path.isfile(dep):
            targets = " ".join(str(x) for x in task['targets'])
            print('Build failed because dependency is not created yet: ' + dep)
            print()
            print('Targetes depending on ' + dep + ":\n" + targets)
            exit(1)

    target_times = []
    for target in task['targets']:
        target_times.append(path.getmtime(target))
    target_times.sort()

    dependency_times = []
    for dependency in task['file_dep']:
        if not path.basename(dependency) == 'placeholder':
            dependency_times.append(path.getmtime(dependency))	
    dependency_times.sort()

    if len(dependency_times) == 0 or len(target_times) == 0:
        return False

    return dependency_times[-1] <= target_times[0]


def get_name(task):
    try:
	    return task['name']
    except KeyError:
        return task['basename']

def execute_task(task):
    if is_up_to_date(task):
        print(get_name(task) + ' - up to date.')
    else:
        for action in task['actions']:
            print(action)
            run(action)

def process_tasks(generator):
	for task in generator:
		if isinstance(task, types.GeneratorType):
			process_tasks(task)
		else:
			execute_task(task)

