#!/usr/bin/env python3

# Copyright (c) 2020, Ericson Joseph
# 
# All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
# 
#     * Redistributions of source code must retain the above copyright notice,
#       this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright notice,
#       this list of conditions and the following disclaimer in the documentation
#       and/or other materials provided with the distribution.
#     * Neither the name of pyMakeTool nor the names of its contributors
#       may be used to endorse or promote products derived from this software
#       without specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


from pathlib import Path
import sys
import os
import re
import copy
import argparse
from pymakelib import preconts as K
from pymakelib import prelib as plib
from pymakelib import moduleignore
from pymakelib import eclipse_files
from pymakelib import make_files
from pymakelib import addon
from pymakelib import Logger
from pymakelib import project

log = Logger.getLogger()

parser = argparse.ArgumentParser()
parser.add_argument('goal', type=str, help='Makefile command goal', default=None, nargs='?')
parser.add_argument('--init', type=str, help='initialize project', const=os.path.basename(os.getcwd()), dest='project_name', nargs='?')
parser.add_argument('-v', '--version', action='version', version='%(prog)s 2.0.0')
args = parser.parse_args()

goal = args.goal

if args.project_name:
    fproject = open('.project', 'w')
    print('Init {0} project'.format(args.project_name))
    fproject.write(eclipse_files.FILE_PROJECT.format(args.project_name))
    fproject.close()
    try:
        os.mkdir(K.PYMAKEPROJ)
    except Exception as e:
        log.exception(e)

    try:
        os.mkdir(K.ECLIPSE_SETTING)
    except Exception as e:
        log.exception(e)

    fileset = [
        [K.PYMAKEPROJ + '/.cproject_template',eclipse_files.FILE_CPROJECT_TEMP],
        [K.PYMAKEPROJ + '/.language.settings_template', eclipse_files.FILE_LANGUAJE_SETTING_XML],
        ['Makefile', make_files.FILE_MAKEFILE],
        ['makefile.mk', make_files.FILE_MAKEFILE_MK],
        ['Makefile.py', make_files.FILE_MAKEFILE_PY]
    ]

    for f in fileset:
        try:
            fileout = open(f[0], 'x')
            fileout.write(f[1])
            fileout.close()
        except Exception as e:
            log.exception(e)

    sys.exit()


USE_EXCLUDE_FOLDERS = True

# ------------------------------------------------------
# ------------------------------------------------------
# ------------------------------------------------------

if not os.path.exists(K.PYMAKEPROJ):
    print('Not a pymaketool project')
    sys.exit()

if not goal:
    print('pymaketool: error: Add a goal (target)')
    sys.exit()

modules = []
modulesPaths = []
ignoreModuleList = []

# Add project path to sys.path for users scripts
sys.path.append(str(os.getcwd()))

projSettings, compilerOpts, compilerSettings = plib.read_Makefilepy()

globalSettings = {
    'PROJECT_SETTINGS': projSettings,
    'COMPILER_OTPS':    compilerOpts,
    'COMPILER_SETTINGS': compilerSettings
}

project.setSettings(globalSettings)

ignoreModuleList = moduleignore.readIgnoreFile()

#Execute GenHeaders
headersPaths = list(Path('./').rglob('*[.|_]h.py'))
for gheader in headersPaths:
    fileout = re.sub('[.|_]h.py', '.h', str(gheader))
    print(f"Generator: {gheader} > {fileout}")
    plib.reafGenHeader(gheader)


modulesPaths = list(Path('./').rglob('*[.|_]mk.py'))
# Load modules
for filename in modulesPaths:
    mod = plib.readModule(filename, copy.deepcopy(compilerOpts), goal)
    modules.extend(mod)

for ex in ignoreModuleList:
    for i, o in enumerate(modules):
        if o.filename == ex:
            del modules[i]
            break

# Write CSRC
srcsfile = open('srcs.mk', 'w')

includes = []

def getLineSeparator(key: str, num: int):
    header = ''
    for _ in range(num):
        header += key
    return header

log.debug('**load modules**')
for mod in modules:
    if mod.isEmpty():
        log.debug(f"module \'{mod.filename}\' is empty, module skiped*")
        continue

    print('Module: {}'.format(mod.filename))
    log.info('read module \'{}\''.format(mod.filename))
    srcsfile.write("{}\n".format(getLineSeparator('#', 52)))
    srcsfile.write("#{0:^50}#\n".format(str(mod.filename)))
    srcsfile.write("{}\n".format(getLineSeparator('#', 52)))

    if mod.staticLib:
        log.debug(f"module \'{mod.filename}\' static library name {mod.staticLib.name}")
        log.debug(f"module \'{mod.filename}\' static output directory {mod.staticLib.outputDir}")
        mkkey = mod.staticLib.name.upper()
        srcsfile.write('{}_NAME = {}\n'.format(mkkey, mod.staticLib.name))
        srcsfile.write('{}_OUTPUT = {}\n'.format(mkkey, str(mod.staticLib.outputDir)))
        library = mod.staticLib.outputDir / Path('lib'+mod.staticLib.name+'.a')
        srcsfile.write('{}_AR = {}\n'.format(mkkey, str(library)))
        srcsfile.write('\n')

    prefixSrcs = ""
    if mod.staticLib:
        prefixSrcs = mod.staticLib.name.upper() + "_"
        
    for src in mod.srcs:
        if str(src).endswith('.c'):
            srcsfile.write("{}CSRC += {}\n".format(prefixSrcs, src))
        elif str(src).endswith('.cpp'):
            srcsfile.write("{}CXXSRC += {}\n".format(prefixSrcs, src))
        elif str(src).endswith('.s'):
            srcsfile.write("{}ASSRC += {}\n".format(prefixSrcs, src))

    srcsfile.write('\n')

    if not mod.staticLib:
        for d in mod.getDirs():
            srcsfile.write("SRC_DIRS += {}\n".format(str(d)))

    srcsfile.write('\n')

    for inc in mod.incs:
        if inc:
            srcsfile.write("INCS += -I{}\n".format(inc))
            includes.append(inc)

    srcsfile.write('\n')

    if mod.flags:
        if mod.staticLib:
            for src in mod.srcs:
                objs = str(src).replace('.c', '.o').replace('.s', '.o')
                mkkey = mod.staticLib.name.upper()
                outputObj = '$({}_OUTPUT)/'.format(mkkey) + str(objs)
                srcsfile.write("{} : CFLAGS = {}\n".format(
                    outputObj, plib.compilerOptsByModuleToLine(mod.flags)))
        else:
            for src in mod.srcs:
                objs = str(src).replace('.cpp', '.o')
                objs = objs.replace('.c', '.o')
                objs = objs.replace('.s', '.o')
                srcsfile.write("{} : CFLAGS = {}\n".format(
                    projSettings['FOLDER_OUT'] / str(objs), plib.compilerOptsByModuleToLine(mod.flags)))

    srcsfile.write('\n')
 
    if mod.staticLib:
        mkkey = mod.staticLib.name.upper()
        srcsfile.write('{0}_OBJECTS = $({0}_CSRC:%.c=$({0}_OUTPUT)/%.o) $({0}_CSRC:%.s=$({0}_OUTPUT)/%.o)\n'.format(mkkey))
        srcsfile.write('\n')
        srcsfile.write('$({0}_OUTPUT)/%.o: %.c\n\t$(call logger-compile-lib,"CC",\"{1}\",$<)\n\t@mkdir -p $(dir $@)\n\t$(CC) $(CFLAGS) $(INCS) -o $@ -c $<\n'.format(mkkey, library))
        srcsfile.write('\n')
        srcsfile.write('$({0}_AR): $({0}_OBJECTS)\n\t$(call logger-compile,"AR",$@)\n\t$(AR) -rc $@ $(filter %.o,$({0}_OBJECTS))'.format(mkkey))
        srcsfile.write('\n\n')
        srcsfile.write('SLIBS_NAMES += {}\n'.format(mod.staticLib.name))
        srcsfile.write('SLIBS_OBJECTS += {}\n'.format(library))
        if mod.staticLib.rebuild:
            srcsfile.write('\n')
            for src in mod.srcs:
                obj = str(src).replace('.c', '.o').replace('.s', '.o')
                mkkey = mod.staticLib.name.upper()
                outputObj = '$({}_OUTPUT)/'.format(mkkey) + str(obj)
                srcsfile.write("{} : .FORCE\n".format(outputObj))
            srcsfile.write('\n\n')

    srcsfile.write('\n')

srcsfile.close()

strIncs = []
aux = []

if compilerSettings['INCLUDES']:
    aux += compilerSettings['INCLUDES']
if includes:
    aux += includes

strIncs = [str(i) for i in aux]

listToExclude = []

if (USE_EXCLUDE_FOLDERS):
    
    allIncFoldes = []
    includes = [str(i) for i in includes]

    for filename in Path('.').rglob('*.h'):
        allIncFoldes.append(str(filename.parent))

    allIncFoldes = list(dict.fromkeys(allIncFoldes))
    allIncFoldes.sort()

    filtedist = []
    parent = ""
    for p in allIncFoldes:
        if parent == "":
            parent = p
            filtedist.append(p)
        elif(p.startswith(parent+"/")):
            None
        else:
            parent = p
            filtedist.append(p)

    allIncFoldes = filtedist

    p = re.compile('^(I|i)nc(lude)*$')

    for allinc in allIncFoldes:
        aux = allinc + '/'
        if not any(aux.startswith(a + "/") for a in includes):
            if not str(allinc).startswith('Test/ceedling') and not allinc == '.':
                auxpath = Path(allinc)
                if p.match(auxpath.name):
                    listToExclude.append(str(auxpath.parent))
                else:
                    listToExclude.append(allinc)
            
    listToExclude.append('Test/ceedling')
    

cproject_setting = {
    'C_SETTINGS': projSettings,
    'C_TARGETS': compilerOpts['TARGETS'],
    'C_INCLUDES': strIncs,
    'C_SYMBOLS': compilerOpts['MACROS'],
    'C_EXCLUDE': listToExclude
}

try:
    # Execute init function of plugins
    for initFunc in addon.initAddonFuncs:
            initFunc(cproject_setting, compilerSettings)

    # Execute class of plugins
    for obj in addon.initAddonClass:
        p = obj(cproject_setting, compilerSettings)
        p.init()
except Exception as ex:
    log.exception(ex)
    exit(-1)