#!/usr/bin/python

import sys, os, shutil, glob, fileinput
import string, random
import subprocess
import ConfigParser
import stat
import re
import fnmatch

THIRDPARTY_DIR_NAME   = "third-party"
TESTS_DIR_NAME        = "tests"
COMMON_DIR_NAME       = "common"
CONFIG_DIR_NAME       = "config"
TEMPLATES_DIR_NAME    = "templates"
PROJECT_MAKEFILE_NAME = "Makefile"
PROJECT_CMAKEFILE_NAME = "CMakeLists.txt"
ADD_PREFIX_PARAM_NAME = "--add-project-prefix"
PROJECT_CONFIG_FILE_NAME = "project.cfg"
REMOVE_PREFIX_PARAM_NAME = "--remove-prefix"

def makeDir(path):
    if not os.path.isdir(path):
        os.mkdir(path)

def copyDir(source_item, destination_item):
    if os.path.isdir(source_item):
        makeDir(destination_item)
        sub_items = glob.glob(source_item + '/*')
        for sub_item in sub_items:
            copyDir(sub_item, destination_item + '/' + sub_item.split('/')[-1])
    else:
        shutil.copy(source_item, destination_item)
	os.chmod(destination_item, stat.S_IREAD|stat.S_IWRITE)

def replaceTextInFile(filePath, textToSearch, textToReplace):
    tempFile = open( fileToSearch, 'r+' )

    for line in fileinput.input( fileToSearch ):
        if textToSearch in line :
            print('Match Found')
        else:
            print('Match Not Found!!')
        tempFile.write( line.replace( textToSearch, textToReplace ) )
    tempFile.close()

def findAllFiles(directory, pattern, includeRootDir = True):
    matches = []
    for root, dirnames, filenames in os.walk(directory):
        for filename in fnmatch.filter(filenames, pattern):
            if includeRootDir:
                matches.append(os.path.relpath(os.path.join(root, filename), getProjectDirectory()))
            else:
                matches.append(os.path.relpath(os.path.join(root, filename), directory))

    return matches

def findAllCSources(directory, forNdk = False):
    return findAllFiles(directory, '*.c', not forNdk)

def findAllCppSources(directory, forNdk = False):
    return findAllFiles(directory, '*.cpp', not forNdk)

def findAllIncDirs(directory, forNdk = False):
    incDirs = []
    hdrC = findAllFiles(directory, '*.h', not forNdk)
    hdrCpp = findAllFiles(directory, '*.hpp', not forNdk)

    for hdr in hdrC:
        if not os.path.dirname(hdr) in incDirs:
            incDirs.append(os.path.dirname(hdr))

    return incDirs;

def setProjectParam(paramName, paramValue):
    cfgFile = os.path.join(os.path.join(getProjectDirectory(), CONFIG_DIR_NAME), PROJECT_CONFIG_FILE_NAME)
    config = ConfigParser.ConfigParser()

    if os.path.exists(cfgFile):
        config.read(cfgFile)

    if not config.has_section('project'):
        config.add_section('project')

    config.set('project', paramName, paramValue)

    fp = open(cfgFile, 'w')
    config.write(fp)
    fp.close()

def getProjectParam(paramName):
    cfgFile = os.path.join(os.path.join(getProjectDirectory(), CONFIG_DIR_NAME), PROJECT_CONFIG_FILE_NAME)
    config = ConfigParser.ConfigParser()
    config.read(cfgFile)

    return config.get('project', paramName)

def setProjectName(projectName):
    setProjectParam('name', projectName)

def setProjectPrefix(projectPrefix):
    setProjectParam('prefix', projectPrefix)

def getProjectName():
    return getProjectParam('name')

def getProjectPrefix():
    return getProjectParam('prefix')

def isModulePresent(moduleName):
    modulePath = os.path.join(getProjectDirectory(), moduleName)
    if os.path.exists(modulePath):
        return True
    return False

def getModuleMakefileName(moduleName):
    return moduleName + ".mk"

def addModuleToProject(moduleName):
    projectMakefile = os.path.join(getProjectDirectory(), PROJECT_MAKEFILE_NAME)

    toWrite = ""

    if os.path.exists(projectMakefile):
        makefile = open(projectMakefile, "r")
        toWrite = makefile.read()
        makefile.close()

    # Check if module is already present
    if len(re.findall(re.compile(r"include\s*" + getModuleMakefileName(moduleName)), toWrite)) > 0:
        return;

    toWrite += "include " + getModuleMakefileName(moduleName) + "\n"
    makefile = open(projectMakefile, "w")
    makefile.write(toWrite)
    makefile.close()

def createCmakeFile():
    toWrite = "cmake_minimum_required(VERSION 2.8)\n"
    toWrite += "\n"
    toWrite += "add_subdirectory(multibuild)\n"

    makefile = open(PROJECT_CMAKEFILE_NAME, "w")
    makefile.write(toWrite)
    makefile.close()

def addModuleToCmake(moduleName):
    projectMakefile = os.path.join(getProjectDirectory(), PROJECT_CMAKEFILE_NAME)

    toWrite = ""

    if os.path.exists(projectMakefile):
        makefile = open(projectMakefile, "r")
        toWrite = makefile.read()
        makefile.close()

    # Check if module is already present
    if len(re.findall(re.compile(r"add_subdirectory(\s*" + moduleName + ")" ), toWrite)) > 0:
        return;

    toWrite += "add_subdirectory(" + moduleName + ")\n"
    makefile = open(projectMakefile, "w")
    makefile.write(toWrite)
    makefile.close()

def getTemplatesDirectory():
    tzxPath = os.path.dirname(os.path.abspath(__file__))
    templatesPath = os.path.join(tzxPath, TEMPLATES_DIR_NAME)
    return templatesPath

def getProjectDirectory():
    tzxPath = os.path.dirname(os.path.abspath(__file__))
    platformPath = os.path.dirname(tzxPath)
    projectPath = os.path.dirname(platformPath)
    return projectPath

def copyFileWithReplacements(srcFilePath, dstFilePath, replacements):
    srcFile = open(srcFilePath, "r")
    data = srcFile.read()
    srcFile.close()

    for rep in replacements:
        data = re.sub(rep, replacements[rep], data)

    dstFile = open(dstFilePath, "w")
    dstFile.write(data)
    dstFile.close()

def copyTemplate(dstModuleName, templatePath, templateSources = [], templateMakefiles = [], forNdk = False):
    moduleDir = os.path.join(getProjectDirectory(), dstModuleName)
    androidIncDirs = []

    for srcfile in templateSources:
        shutil.copy(os.path.join(templatePath, srcfile), os.path.join(moduleDir, getProjectPrefix() + srcfile))

    srcC = findAllCSources(moduleDir, forNdk)
    srcCpp = findAllCppSources(moduleDir, forNdk)
    incDirs = findAllIncDirs(moduleDir, forNdk)

    reps = {"%APP_NAME%"    : dstModuleName,
            "%SRC_C%"       : (' '.join(srcC)).lstrip().rstrip(),
            "%SRC_CPP%"     : (' '.join(srcCpp)).lstrip().rstrip(),
            "%INCLUDE_DIRS%": (' '.join(incDirs)).lstrip().rstrip()}

    if forNdk:
        for inc in incDirs:
            androidIncDirs.append(os.path.join("$(LOCAL_PATH)", inc))
        reps["%INCLUDE_DIRS%"] = (' '.join(androidIncDirs)).lstrip().rstrip()

    for mkfile in templateMakefiles:
        if not forNdk:
            copyFileWithReplacements(os.path.join(templatePath, mkfile), os.path.join(getProjectDirectory(), getModuleMakefileName(dstModuleName)), reps)
        else:
            copyFileWithReplacements(os.path.join(templatePath, mkfile), os.path.join(moduleDir, mkfile), reps)

def copyNWdTizenTemplate(dstModuleName, templatePath):
    # Create madule dir for tizen files
    dstModuleDir = os.path.join(getProjectDirectory(), dstModuleName)
    # Setup module replace patterns
    reps = {"%APP_NAME%"    : dstModuleName}
    # Create rpm direcotry
    dstPackagingDir = os.path.join(getProjectDirectory(), "packaging")
    makeDir(dstPackagingDir)    
    # Copy gbs.conf file
    copyFileWithReplacements(os.path.join(templatePath, "gbs.conf"), os.path.join(getProjectDirectory(), "packaging", "gbs.conf"), reps)
    # Copy spec file
    specFile = os.path.join(templatePath, "packaging", "spec")
    copyFileWithReplacements(specFile, os.path.join(getProjectDirectory(), "packaging", dstModuleName + ".spec"), reps)

def initConfigFromTemplate():
    addDirToProject(CONFIG_DIR_NAME)

    templateDir = os.path.join(getTemplatesDirectory(), CONFIG_DIR_NAME)
    moduleDir = os.path.join(getProjectDirectory(), CONFIG_DIR_NAME)

    templateMakefiles = ["config.mk", "targets.mk", "paths.mk"]

    for mkfile in templateMakefiles:
        copyFileWithReplacements(os.path.join(templateDir, mkfile), os.path.join(moduleDir, mkfile), {})

def copyAndroidTemplate(dstModuleName):
    templatePath = os.path.join(getTemplatesDirectory(), "android-java")
    copyTemplate(dstModuleName, templatePath, [], ["java.mk"])
    moduleDir = os.path.join(getProjectDirectory(), dstModuleName)
    shutil.copy(os.path.join(templatePath, "build.xml"), moduleDir)

def copySWdBinTemplate(dstModuleName):
    templatePath = os.path.join(getTemplatesDirectory(), "swd-bin")
    copyTemplate(dstModuleName, templatePath, ["gp-handler.c"], ["trustlet.mk"])

def copySWdLibTemplate(dstModuleName):
    templatePath = os.path.join(getTemplatesDirectory(), "swd-lib")
    copyTemplate(dstModuleName, templatePath, [], ["library.mk"])

def copyNWdBinTemplate(dstModuleName):
    templatePath = os.path.join(getTemplatesDirectory(), "nwd-bin")
    copyTemplate(dstModuleName, templatePath, ["gp-main.c"], ["client.mk"])
    # Copy Android related template files
    copyTemplate(dstModuleName, templatePath, [], ["Android.mk", "Application.mk"], True)
    # Copy Tizen related template files
    copyTemplate(dstModuleName, templatePath, [], ["CMakeLists.txt"], True)
    # Copy Tizen RPM (GBS) related template files
    templatePath = os.path.join(getTemplatesDirectory(), "tizen-ndk")
    copyNWdTizenTemplate(dstModuleName, templatePath)
    # Add nwd module 
    addModuleToCmake(dstModuleName)
    
def copyNWdLibTemplate(dstModuleName):
    templatePath = os.path.join(getTemplatesDirectory(), "nwd-lib")
    copyTemplate(dstModuleName, templatePath, ["main.c"], ["library.mk"])
    # Copy Android related template files
    copyTemplate(dstModuleName, templatePath, [], ["Android.mk", "Application.mk"], True)
    # Copy Tizen related template files
    copyTemplate(dstModuleName, templatePath, [], ["CMakeLists.txt"], True)
    # Copy Tizen RPM (GBS) related template files
    templatePath = os.path.join(getTemplatesDirectory(), "tizen-ndk")
    copyNWdTizenTemplate(dstModuleName, templatePath)
    # Add nwd module 
    addModuleToCmake(dstModuleName)

def copyTestsTemplate():
    templatePath = os.path.join(getTemplatesDirectory(), "tests")
    moduleDir = os.path.join(getProjectDirectory(), "tests")

    copyTemplate("tests", templatePath, [], ["tests.mk"])
    shutil.copytree(os.path.join(templatePath, "func-tests"), os.path.join(moduleDir, "func-tests"))
    shutil.copytree(os.path.join(templatePath, "unit-tests"), os.path.join(moduleDir, "unit-tests"))

def copyCmakeTemplate():
    createCmakeFile()
    
    templatePath = os.path.join(getTemplatesDirectory(), "cmake")
    projectPath = os.path.join(getProjectDirectory(), "cmake")
    copyDir(templatePath, projectPath)

def addProjectPrefixToFiles(directory, pattern = "*", oldPrefix = None):
    for root, dirnames, filenames in os.walk(directory):
        for filename in fnmatch.filter(filenames, pattern):
            newName = os.path.basename(filename)

            if oldPrefix != None and newName.startswith(oldPrefix):
                newName = newName[len(oldPrefix):]

            newName = getProjectPrefix() + newName

            os.rename(os.path.join(root, filename), os.path.join(root, newName))

def getNecessaryOptionsNumber(cmdParams):
    for i in range(0, len(cmdParams)):
        if cmdParams[i].startswith("--"):
            return i
    return len(cmdParams)

def createModuleCommon(cmdParams, importSources = False):
    necessaryParamsNum = getNecessaryOptionsNumber(cmdParams)
    if necessaryParamsNum < 2:
        return False

    moduleType = cmdParams[0]
    moduleName = cmdParams[1]
    moduleDir = os.path.join(getProjectDirectory(), moduleName)

    if isModulePresent(moduleName):
        print "Module " + moduleName + " is already present!"
        return False

    if not importSources: # Handle clean module
        if not os.path.exists(moduleDir):
            os.mkdir(moduleDir)
    else: # Handle importing module sources
        if necessaryParamsNum < 3:
            return False

        moduleSrcPath = cmdParams[2]

        if not os.path.exists(moduleSrcPath):
            moduleSrcPath = os.path.join(os.getcwd(), moduleSrcPath)

        if not os.path.exists(moduleSrcPath):
            print "Source directory not found!"
            return False

        shutil.copytree(moduleSrcPath, moduleDir)

    if ADD_PREFIX_PARAM_NAME in cmdParams:
        oldPrx = None
        if REMOVE_PREFIX_PARAM_NAME in cmdParams:
            if cmdParams.index(REMOVE_PREFIX_PARAM_NAME) < len (cmdParams):
                oldPrx = cmdParams[cmdParams.index(REMOVE_PREFIX_PARAM_NAME) + 1]

        addProjectPrefixToFiles(moduleDir, "*", oldPrx)

    if moduleType in templatesHandler:
        templatesHandler[moduleType](moduleName)
        addModuleToProject(moduleName)
    else:
        return False

    print "Module " + moduleName + " added!"
    return True

def importModule(cmdParams):
    if not createModuleCommon(cmdParams, True):
        handleError("import-module")


def initModule(cmdParams):
    if not createModuleCommon(cmdParams):
        handleError("init-module")

def isProjectInited():
    configDir = os.path.join(getProjectDirectory(), CONFIG_DIR_NAME);

    if os.path.exists(configDir):
        return True
    else:
        return False

def addDirToProject(dirName):
    targetDir = os.path.join(getProjectDirectory(), dirName);

    if not os.path.exists(targetDir):
        os.mkdir(targetDir)

def initProject(cmdParams):
    necessaryParamsNum = getNecessaryOptionsNumber(cmdParams)

    if isProjectInited():
        print "Project is already initialized!"
        exit()

    if necessaryParamsNum < 1:
        handleError("init-project")

    initConfigFromTemplate()

    projectName = cmdParams[0]
    if necessaryParamsNum > 1:
        projectPrefix = cmdParams[1]
    else:
        projectPrefix = projectName + "_"

    setProjectName(projectName)
    setProjectPrefix(projectPrefix)

    addDirToProject(TESTS_DIR_NAME)
    addDirToProject(THIRDPARTY_DIR_NAME)

    copyTestsTemplate()
    copyCmakeTemplate()

def updatePlatforms(cmdParams):
    print "Operation is not supported yet!"

templatesHandler = {"swd-bin" : copySWdBinTemplate,
                    "nwd-bin" : copyNWdBinTemplate,
                    "swd-lib" : copySWdLibTemplate,
                    "nwd-lib" : copyNWdLibTemplate,
                    "android-java" : copyAndroidTemplate}

operations = {"init-module"   : initModule,
              "init-project"  : initProject,
              "import-module" : importModule}

usage = {"multibuild"   : "multibuild.py init-project|init-module|import-module [param1, param2, ....]\n",
         "import-module": "multibuild.py import-module nwd-bin|swd-bin|nwd-lib|swd-lib module-name source-dir [--add-project-prefix [--remove-prefix file-prefix-to-remove]]\n",
         "init-module"  : "multibuild.py init-module nwd-bin|swd-bin|nwd-lib|swd-lib|android-java module-name\n",
         "init-project" : "multibuild.py init-project project-name project-prefix\n"}

def printUsage(cmd):
    print usage[cmd]

def handleError(cmd):
    print "Invalid usage!\nUsage:\n"
    printUsage(cmd)
    exit()

def main(args):
    if len(args) < 2:
        handleError("multibuild")

    operation = args[1]
    params = args[2:]

    if operation in operations:
        operations[operation](params);
    else:
        handleError("multibuild")

if __name__ == "__main__":
    main(sys.argv)
