#!/usr/bin/python
import os
import subprocess
import sys, getopt
import time
import re
import datetime
import json
import copy
import hashlib
import shutil
import random
from pprint import pprint
from subprocess import *
from distutils.spawn import find_executable


VERSION = 3.2

REGEXP_IMPORT = re.compile('/// *<reference path="(.*)/([^/]+)/[^"]+" */>')
#REGEXP_IMPORT_D_TS = re.compile('/// *<reference path=".*/([^/]+)/[^.]+\.class\.d\.ts" */>')
REGEXP_IMPORT_D_TS = re.compile('/// *<reference path="([^"]+)" */>')
REGEXP_IMPORT_D_TS_IMPORT = re.compile('/// *<reference path="(.*/[^/]+/[^.]+\.class\.d\.ts)" */>')
REGEXP_IMPORT_CUSTOM = re.compile('/// *< *([a-z]+)="([^"]+)" *([^/]+)? */>')
REGEXP_CUSTOM = re.compile('/// *< *([a-z]+) */>')

ESVersion = 3
USE_SOUND = True
USE_NOTIFICATION = True
TYPESCRIPT_PATH = None
ONCE = False

class Console:
    def __init__(self):
        self.__colors = {'RED':'\033[91m', 'BLUE':'\033[94m', 'GREEN':'\033[92m', 'ORANGE':'\033[93m', 'NORMAL':'\033[0m'}

    def getColor(self, name):
        return self.__colors[name]

    def red(self, value):
        value = self.__treat(value)
        self.__print(self.__colors['RED'] + str(value) + self.__colors['NORMAL'])

    def green(self, value):
        value = self.__treat(value)
        self.__print(self.__colors['GREEN'] + str(value) + self.__colors['NORMAL'])

    def blue(self, value):
        value = self.__treat(value)
        self.__print(self.__colors['BLUE'] + str(value) + self.__colors['NORMAL'])

    def orange(self, value):
        value = self.__treat(value)
        self.__print(self.__colors['ORANGE'] + str(value) + self.__colors['NORMAL'])

    def normal(self, value):
        value = self.__treat(value)
        self.__print(self.__colors['NORMAL'] + str(value))


    def error(self, value):
        value = self.__treat(value)
        self.red("[ERROR]:"+str(value))

    def info(self, value):
        value = self.__treat(value)
        self.blue("[INFO]:"+str(value))

    def out(self, value):
            value = self.__treat(value)
            now = datetime.datetime.now()
            self.normal("[OUT "+now.strftime("%d-%m-%Y %H:%M:%S")+"]:"+str(value))
    def __treat(self, value):
        if((value is None)):
            value = "None"
        if(not value):
            value = "empty"
        try:
            value = str(value)
        except:
            print(value)
            value = ""
        return value

    def __print(self, value):
        print(value)

class Voice:
    def __init__(self):
        self.count = 0
        self.good = ["thats better", "yep", "ok", "good", "very good", "excellent", "great", "you are on FIRE", "Hoorray!!"];
        #self.score = [-10, 0, 10, 20, 30, 40, 50, 60, 70];
        self.neutral = 2

    def getBadSound(self):
        if(self.count > 0):
            self.count = 0
        self.count -= 1
        return self.__getSound()

    def getGoodSound(self):
        self.count += 1
        return self.__getSound()

    def __getSound(self):
        r = self.getRandom()
        l = len(self.good)
        for i in range(l):
            if((i-self.neutral+1)*10>r):
                #print(str(self.score[i])+":"+str(((i-self.neutral+1)*10)))
                return self.good[i]
        self.count = 0
        return "imbattable"

    def getRandom(self):
        left = self.count
        right = self.count*10
        if(left>right):
            left, right = right, left
        return random.randint(left, right)

voice = Voice()


class MegaWatcher:
    def __init__(self, folders):
        self.__folders = folders
        self.__fileWatchers = {} 
        self.__compilations = 0
        self.__errors = 0

        start_time = time.time()
        #LOG.green(folders)
        
        for folder in self.__folders:
            name =  folder.split("/")[-1]
            self.__fileWatchers[name] = TSFilesWatcher(folder, self, name)

        try:
            file_config=open('.cache_metacompile.json','r')

            data = json.load(file_config)
            file_config.close()
            for key in data.keys():
                parentName = key.split("/")[0]
                module = key.split("/")[1]
                if(parentName in self.__fileWatchers):
                    if(self.__fileWatchers[parentName].hasModule(module)):
                        self.__fileWatchers[parentName].getModule(module).setDependencyMD5(data[key]["dependencies"])
                        self.__fileWatchers[parentName].getModule(module).setError(data[key]["errors"])
                        self.__fileWatchers[parentName].getModule(module).setLastDate(data[key]["last_date"])
                        #force recompilation of errors
                        if(data[key]["errors"]):
                            self.__fileWatchers[parentName].getModule(module).setLastCompilationDate(0)
                        else:
                            self.__fileWatchers[parentName].getModule(module).setLastCompilationDate(data[key]["last_date_compilation"])
                        self.__fileWatchers[parentName].getModule(module).init()
        except Exception as error:
            LOG.orange("No previous compilation found")
            print(error)
            pass
        

        keys = self.__fileWatchers.keys()
        #for name in keys:
            #LOG.red("compiling module "+name)
            #self.__fileWatchers[name].compileAll()


        for name in keys:
            #LOG.red("dependencies module "+name)
            self.__fileWatchers[name].checkDependenciesAll()


        self.compileAll()
        LOG.green("MetaTypescript Compiler v"+str(VERSION)+" is ready in "+str(round(time.time()-start_time,2))+"s")
        self.watch()

    def hasModule(self, moduleName, name = None):
        if(name is None):
            name = moduleName.split("/")[0]
            moduleName = moduleName.split("/")[1]

        if(name in self.__fileWatchers.keys()):
            return self.__fileWatchers[name].hasModule(moduleName)
        else:
            return False

    def getModule(self, moduleName, name = None):
        if(name is None):
            name = moduleName.split("/")[0]
            moduleName = moduleName.split("/")[1]

        if(name in self.__fileWatchers.keys()):
            return self.__fileWatchers[name].getModule(moduleName)
        else:
            return None

    def getModuleList(self):
        module_list = []
        keys = self.__fileWatchers.keys()
        for name in keys:
            module_list.append(name)
        return module_list


    def watch(self):
        try:
            i = 0
            keys = self.__fileWatchers.keys()
            while(True):
                i = i + 1
                self.compileAll()
                
                time.sleep(2)
                if(i%5 == 0):
                    for name in keys:
                        self.__fileWatchers[name].seekFiles()
        except KeyboardInterrupt:
            print("End of program")
    def compileAll(self):
        start_time = time.time()
        dep = {}
        done = []
        toCompile = []
        for name in self.__fileWatchers.keys():
            toCompile+=self.__fileWatchers[name].getFiles()
        for tsfile in toCompile:
            dep[tsfile.getLongModuleName()] = tsfile.getDependencies()
            tsfile.checkLastDateChanged()
        metaHasCompiled = False
        hasCompiled = True

        self.__errors = 0
        errors = 0
        compilations = 0

        error_line = "error"
        module_list = []
        module_list_error = []
        list_error = []
        times = []

        while(hasCompiled):
            hasCompiled = False
            isFailed = False
            for tsfile in toCompile:
                compile = False


                if(not tsfile.isUpToDate()):
                    compile = True
                    module_list.append(tsfile.getLongModuleName())
                    LOG.orange(tsfile.getLongModuleName()+" has changed...")
                else:
                    # LOG.info(file.getModule()+":"+str(file.getLastDate()))
                    compile = False
                    for depend in dep[tsfile.getLongModuleName()]:
                        md5 = tsfile.getDependencyMD5(depend)
                        if(md5!=self.getModule(depend).getMD5() and not self.getModule(depend).isFailed()):
                            compile = True
                            LOG.orange(tsfile.getLongModuleName()+" depends of "+depend+" and has to be recompiled")
                            #LOG.red(md5)
                            #LOG.green(self.getModule(depend).getMD5())
                            break
                if(compile):
                    #LOG.out("Compile "+file.getLongModuleName())
                    success, t = tsfile.compile()
                    times.append(t)
                    if(not success):
                        module_list_error.append(tsfile.getLongModuleName())
                        list_error.append(tsfile.getLastError())
                        LOG.error("Error during compilation of "+tsfile.getLongModuleName())
                        self.__errors = self.__errors + 1
                        errors = errors + 1


                    self.__compilations = self.__compilations + 1
                    compilations = compilations + 1
                    hasCompiled = True
                    metaHasCompiled = True

                    if(success):
                        for module in toCompile:
                            if(module.isFailed()):
                                module.resetFailed(tsfile.getLongModuleName())


        isFailed = False
        for tsfile in toCompile:
            if(tsfile.isFailed()):
                isFailed = True
                break

        if(metaHasCompiled):
            #LOG.green("Step 1 in "+str(round(time.time()-start_time,2))+"s")
                    
            save = {}
            for tsfile in toCompile:
                save[tsfile.getLongModuleName()] = {"dependencies":tsfile.getDependencyMD5(), "errors":tsfile.isFailed(),"last_date":tsfile.getLastDate(),"last_date_compilation":tsfile.getLastCompilationDate()}
            save = json.dumps(save)
            f = open(".cache_metacompile.json","w")
            f.write(save)
            f.close()


            if("compile_modules" in data and data["compile_modules"] == True):
                ##Javascript concatenation for each module

                dep_order = self.getDependenciesInOrder(dep, dep.keys())
                files = {}
                names = {}
                maps = {}
                for name in dep_order:
                    tsfile = self.getModule(name)
                    if(tsfile.getRoot() not in files):
                        files[tsfile.getRoot()] = []
                        maps[tsfile.getRoot()] = []
                        names[tsfile.getRoot()] = [""]  
                    names[tsfile.getRoot()] += [tsfile.getLongModuleName()]
                    files[tsfile.getRoot()] += tsfile.getJSContent(True)
                    maps[tsfile.getRoot()].append(tsfile.getMapFile())
                for name in files:
                    f = open(name+"/index.js",'w')
                    f.write("".join(files[name]))
                    f.write("\n///".join(names[name]))
                    f.write("\n//# sourceMappingURL=index.js.map")
                    f.close()
                    args = ["node",".pythoncompile/node_modules/source-map-concat/bin/source-map-concat.js","--source-file", name+"/index.js", "--out-file",name+"/index.js.map"] + maps[name]
                    #print(" ".join(args))
                    call(args)
                    
                #LOG.green("Step 2 in "+str(round(time.time()-start_time,2))+"s")
            if("out" in data):
                source = False
                if("concat_sourcemaps" in data and data["concat_sourcemaps"] == True):
                    source = True
                for out in data["out"]:
                    module = self.getModule(out)
                    maps = []
                    if(module is not None):
                        dependencies = self.getDependenciesInOrder(dep, module.getAllDependencies()) + [module.getLongModuleName()]
                        writer = open(data["out"][out],'w')
                        for name in dependencies:
                            tsfile = self.getModule(name)
                            maps.append(tsfile.getMapFile())
                            try:
                                writer.write(""+("".join(tsfile.getJSContent(True))))