import pl_build as pl
from pl_build import _context, __version__

class plWin32Helper:

    def __init__(self):
        self.buffer = '\n'
        self.indent = 0

    def set_indent(self, indent):
        self.indent = indent

    def write_file(self, file_path):
        with open(file_path, "w") as file:
            file.write(self.buffer)

    def add_line(self, line):
        self.buffer += ' ' * self.indent + line + '\n'

    def add_raw(self, text):
        self.buffer += text

    def add_label(self, label):
        self.buffer += ':' + label + '\n'
        
    def add_spacing(self, count = 1):
        self.buffer += '\n' * count

    def add_title(self, title):
        line_length = 80
        padding = (line_length  - 2 - len(title)) / 2
        self.buffer += ":: " + "#" * line_length + "\n"
        self.buffer += ":: #" + " " * int(padding) + title + " " * int(padding + 0.5) + "#" + "\n"
        self.buffer += (":: " + "#" * line_length) + "\n"

    def add_sub_title(self, title):
        line_length = 80
        padding = (line_length  - 2 - len(title)) / 2
        self.buffer += "::" + "~" * int(padding) + " " + title + " " + "~" * int(padding + 0.5) + "\n"

    def add_comment(self, comment):
        self.buffer += ' ' * self.indent + ':: ' + comment + '\n'
        
    def print_line(self, text):
        self.buffer += ' ' * self.indent + '@echo ' + text + '\n'

    def print_space(self):
        self.buffer += '@echo.\n'
        
    def create_directory(self, directory):
        self.buffer += ' ' * self.indent + '@if not exist "' + directory + '" @mkdir "' + directory + '"\n\n'

    def delete_file(self, file):
        self.buffer += ' ' * self.indent + '@if exist "' + file +  '"'
        self.buffer += ' del "' + file + '"\n'

def generate_build(name, platform, compiler, user_options):

    helper = plWin32Helper()

    hot_reload = False

    ###############################################################################
    #                                 Intro                                       #
    ###############################################################################

    helper.add_comment("Project: " + _context.project_name)
    helper.add_comment("Auto Generated by:")
    helper.add_comment('"pl_build.py" version: ' + __version__)
    helper.add_spacing()
    helper.add_comment("Project: " + _context.project_name)
    helper.add_spacing()

    helper.add_title("Development Setup")
    helper.add_spacing()

    helper.add_comment('keep environment variables modifications local')
    helper.add_line("@setlocal")
    helper.add_spacing()

    helper.add_comment("make script directory CWD")
    helper.add_line('@pushd %~dp0')
    helper.add_line('@set dir=%~dp0')
    helper.add_spacing()

    # try to setup dev environment
    if isinstance(user_options, dict):
        if "dev env setup" in user_options:
            if user_options["dev env setup"] == True:
                helper.add_comment("modify PATH to find vcvarsall.bat")
                helper.add_line('@set PATH=C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Auxiliary\\Build;%PATH%')
                helper.add_line('@set PATH=C:\\Program Files\\Microsoft Visual Studio\\2019\\Community\\VC\\Auxiliary\\Build;%PATH%')
                helper.add_line('@set PATH=C:\\Program Files (x86)\\Microsoft Visual Studio\\2022\\Community\\VC\\Auxiliary\\Build;%PATH%')
                helper.add_line('@set PATH=C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Auxiliary\\Build;%PATH%')
                helper.add_line('@set PATH=C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Enterprise/VC\\Auxiliary\\Build;%PATH%')
                helper.add_spacing()

                helper.add_comment("setup environment for MSVC dev tools")
                helper.add_line('@call vcvarsall.bat amd64 > nul')
                helper.add_spacing()

    helper.add_comment("default compilation result")
    helper.add_line('@set PL_RESULT=Successful.')
    helper.add_spacing()

    # set default config
    if _context.registered_configurations:
        helper.add_comment("default configuration")
        helper.add_line("@set PL_CONFIG=" + _context.registered_configurations[0])
        helper.add_spacing()
    
    # check command line args for config
    helper.add_comment("check command line args for configuration")
    helper.add_label("CheckConfiguration")
    helper.add_line('@if "%~1"=="-c" (@set PL_CONFIG=%2) & @shift & @shift & @goto CheckConfiguration')
    for register_config in _context.registered_configurations:
        helper.add_line('@if "%PL_CONFIG%" equ "' + register_config +  '" ( goto ' + register_config + ' )')
    helper.add_spacing()

    # if _context.pre_build_step is not None:
    #     helper.add_line(_context.pre_build_step)
    #     helper.add_spacing()

    # filter win32 only settings & apply defaults
    platform_settings = []
    for settings in _context.current_settings:
        if settings.platform_name == platform:
            if settings.output_binary_extension is None:
                if settings.target_type == pl.TargetType.EXECUTABLE:
                    settings.output_binary_extension = ".exe"
                elif settings.target_type == pl.TargetType.DYNAMIC_LIBRARY:
                    settings.output_binary_extension = ".dll"
                elif settings.target_type == pl.TargetType.STATIC_LIBRARY:
                    settings.output_binary_extension = ".lib"
            platform_settings.append(settings)

    for register_config in _context.registered_configurations:

        # filter this config only settings
        config_only_settings = []
        for settings in platform_settings:
            if settings.config_name == register_config:
                config_only_settings.append(settings)

        if len(config_only_settings) == 0:
            continue

        # find hot reload target
        if _context.reload_target_name is not None:
            hot_reload = True

        helper.add_title("configuration | " + register_config)
        helper.add_spacing()
        helper.add_label(register_config)
        helper.add_spacing()

        output_dirs = set()
        for settings in config_only_settings:
            output_dirs.add(settings.output_directory)

        # create output directories
        helper.add_comment("create output directories")
        for dir in output_dirs:
            helper.create_directory(dir)

        lock_files = set()
        for settings in config_only_settings:
            lock_files.add(settings.lock_file)
        helper.add_comment("create lock file(s)")
        for lock_file in lock_files:
            helper.add_line('@echo LOCKING > "' + settings.output_directory + '/' + lock_file + '"')

        helper.add_spacing()
        
        if hot_reload:

            helper.add_comment("check if this is a hot reload")
            helper.add_line("@set PL_HOT_RELOAD_STATUS=0")
            
            helper.add_spacing()
            helper.add_comment("hack to see if " + _context.reload_target_name + " exe is running")
            helper.add_line("@echo off")
            helper.add_line('2>nul (>>"' + _context.reload_target_name + '.exe" echo off) && (@set PL_HOT_RELOAD_STATUS=0) || (@set PL_HOT_RELOAD_STATUS=1)')
            
            helper.add_spacing()
            helper.add_comment("let user know if hot reloading")
            helper.add_line("@if %PL_HOT_RELOAD_STATUS% equ 1 (")
            helper.set_indent(4)
            helper.print_line("-------- HOT RELOADING --------")
            helper.set_indent(0)
            helper.add_line(")")
            helper.set_indent(0)
            helper.add_spacing()
            helper.add_comment("cleanup binaries if not hot reloading")
            helper.add_line("@if %PL_HOT_RELOAD_STATUS% equ 0 (\n")
            helper.set_indent(4)
        
        # delete old binaries & files
        for settings in config_only_settings:
            if settings.source_files:
                if settings.name == compiler:
                    helper.delete_file(settings.output_directory + '/' + settings.output_binary + settings.output_binary_extension)
                    if settings.target_type == pl.TargetType.DYNAMIC_LIBRARY:
                        helper.delete_file(settings.output_directory + '/' + settings.output_binary + '_*' + settings.output_binary_extension)
                        helper.delete_file(settings.output_directory + '/' + settings.output_binary + '_*.pdb')
                    elif settings.target_type == pl.TargetType.EXECUTABLE:
                        helper.delete_file(settings.output_directory + '/' + settings.output_binary + '_*.pdb')
        
        helper.set_indent(0)
        if hot_reload:
            helper.add_spacing()
            helper.add_line(")")
        helper.add_spacing()

        # other targets
        for settings in config_only_settings:
            helper.add_sub_title(settings.target_name + " | " + register_config)
            helper.add_spacing()

            if settings.name == compiler:

                if not settings.reloadable and hot_reload:
                    helper.add_comment("skip during hot reload")
                    helper.add_line('@if %PL_HOT_RELOAD_STATUS% equ 1 goto ' + "Exit_" + settings.target_name)
                    helper.add_spacing()

                if settings.pre_build_step is not None:
                    helper.add_line(settings.pre_build_step)
                    helper.add_spacing()
                
                if settings.definitions:
                    helper.add_raw('@set PL_DEFINES=')
                    for define in settings.definitions:
                        helper.add_raw("-D" + define + " ")
                    helper.add_spacing()

                if settings.include_directories:
                    helper.add_raw('@set PL_INCLUDE_DIRECTORIES=')
                    for include in settings.include_directories:
                        helper.add_raw('-I"' + include + '" ')
                    helper.add_spacing()

                if settings.link_directories:
                    helper.add_raw('@set PL_LINK_DIRECTORIES=')
                    for link in settings.link_directories:
                        helper.add_raw('-LIBPATH:"' + link + '" ')
                    helper.add_spacing()

                if settings.compiler_flags:
                    helper.add_raw('@set PL_COMPILER_FLAGS=')
                    for flag in settings.compiler_flags:
                        helper.add_raw(flag + " ")
                    helper.add_spacing()

                settings.linker_flags.extend(["-incremental:no"])
                if settings.target_type == pl.TargetType.DYNAMIC_LIBRARY:
                    settings.linker_flags.extend(["-noimplib", "-noexp"])

                if settings.linker_flags:
                    helper.add_raw('@set PL_LINKER_FLAGS=')
                    for flag in settings.linker_flags:
                        helper.add_raw(flag + " ")
                    helper.add_spacing()

                if settings.static_link_libraries:
                    helper.add_raw('@set PL_STATIC_LINK_LIBRARIES=')
                    for link in settings.static_link_libraries:
                        helper.add_raw(link + ".lib ")
                    helper.add_spacing()

                if settings.dynamic_link_libraries:
                    helper.add_raw('@set PL_DYNAMIC_LINK_LIBRARIES=')
                    for link in settings.dynamic_link_libraries:
                        helper.add_raw(link + ".lib ")
                    helper.add_spacing()

                if settings.target_type == pl.TargetType.STATIC_LIBRARY:

                    helper.add_spacing()
                    helper.add_comment("run compiler only")
                    helper.print_space()
                    helper.print_line("Step: " + settings.target_name +"")
                    helper.print_line("~~~~~~~~~~~~~~~~~~~~~~")
                    helper.print_line("Compiling...")

                    helper.add_spacing()
                    helper.add_comment('each file must be compiled separately')
                    for source in settings.source_files:
                        sub_buffer = ""
                        if settings.include_directories:
                            sub_buffer += " %PL_INCLUDE_DIRECTORIES%"
                        if settings.definitions:
                            sub_buffer += " %PL_DEFINES%"
                        if settings.compiler_flags:
                            sub_buffer += " %PL_COMPILER_FLAGS%"
                        helper.add_line('cl -c' + sub_buffer + " " + source + ' -Fo"' + settings.output_directory + '/"')
                        helper.add_spacing()

                    helper.add_spacing()
                    helper.add_comment("check build status")
                    helper.add_line("@set PL_BUILD_STATUS=%ERRORLEVEL%")

                    helper.add_spacing()
                    helper.add_comment("if failed, skip linking")
                    helper.add_raw("@if %PL_BUILD_STATUS% NEQ 0 (\n")
                    helper.add_raw("    @echo Compilation Failed with error code: %PL_BUILD_STATUS%\n")
                    helper.add_raw("    @set PL_RESULT=Failed.\n")
                    helper.add_raw("    goto " + 'Cleanup' + register_config)
                    helper.add_raw("\n)\n")

                    helper.add_spacing()
                    helper.add_comment('link object files into a shared lib')
                    helper.add_raw("@echo Linking...\n")
                    helper.add_raw('lib -nologo -OUT:"' + settings.output_directory + '/' + settings.output_binary + settings.output_binary_extension + '" "' + settings.output_directory + '/*.obj"\n')

                elif settings.target_type == pl.TargetType.DYNAMIC_LIBRARY:

                    helper.add_raw('@set PL_SOURCES=')
                    for source in settings.source_files:
                        helper.add_raw('"' + source + '" ')
                    helper.add_spacing()

                    sub_buffer0 = ""
                    sub_buffer1 = ""
                    sub_buffer2 = ""
                    if settings.include_directories:
                        sub_buffer0 += " %PL_INCLUDE_DIRECTORIES%"
                    if settings.definitions:
                        sub_buffer0 += " %PL_DEFINES%"
                    if settings.compiler_flags:
                        sub_buffer0 += " %PL_COMPILER_FLAGS%"
                    if settings.linker_flags:
                        sub_buffer1 = " %PL_LINKER_FLAGS%"
                    if settings.link_directories:
                        sub_buffer2 += " %PL_LINK_DIRECTORIES%"
                    if settings.static_link_libraries:
                        sub_buffer2 += " %PL_STATIC_LINK_LIBRARIES%"
                    if settings.dynamic_link_libraries:
                        sub_buffer2 += " %PL_DYNAMIC_LINK_LIBRARIES%"

                    helper.add_spacing()
                    helper.add_comment("run compiler (and linker)")
                    helper.print_space()
                    helper.add_raw('@echo Step: ' + settings.target_name +'\n')
                    helper.add_raw('@echo ~~~~~~~~~~~~~~~~~~~~~~\n')
                    helper.add_raw('@echo Compiling and Linking...\n')
                    helper.add_raw('cl' + sub_buffer0 + ' %PL_SOURCES% -Fe"' + settings.output_directory + '/' + settings.output_binary + settings.output_binary_extension + '" -Fo"' + settings.output_directory + '/" -LD -link' + sub_buffer1 + ' -PDB:"' + settings.output_directory + '/' + settings.output_binary + '_%random%.pdb"' + sub_buffer2 + "\n\n")

                    helper.add_comment("check build status")
                    helper.add_raw("@set PL_BUILD_STATUS=%ERRORLEVEL%\n")

                    helper.add_raw("\n:: failed\n")
                    helper.add_raw("@if %PL_BUILD_STATUS% NEQ 0 (\n")
                    helper.add_raw("    @echo Compilation Failed with error code: %PL_BUILD_STATUS%\n")
                    helper.add_raw("    @set PL_RESULT=Failed.\n")
                    helper.add_raw("    goto " + 'Cleanup' + register_config)
                    helper.add_raw("\n)\n")

                elif settings.target_type == pl.TargetType.EXECUTABLE:

                    helper.add_raw('@set PL_SOURCES=')
                    for source in settings.source_files:
                        helper.add_raw('"' + source + '" ')
                    helper.add_spacing(2)

                    sub_buffer0 = ""
                    sub_buffer1 = ""
                    sub_buffer2 = ""
                    if settings.include_directories:
                        sub_buffer0 += " %PL_INCLUDE_DIRECTORIES%"
                    if settings.definitions:
                        sub_buffer0 += " %PL_DEFINES%"
                    if settings.compiler_flags:
                        sub_buffer0 += " %PL_COMPILER_FLAGS%"
                    if settings.linker_flags:
                        sub_buffer1 = " %PL_LINKER_FLAGS%"
                    if settings.link_directories:
                        sub_buffer2 += " %PL_LINK_DIRECTORIES%"
                    if settings.static_link_libraries:
                        sub_buffer2 += " %PL_STATIC_LINK_LIBRARIES%"
                    if settings.dynamic_link_libraries:
                        sub_buffer2 += " %PL_DYNAMIC_LINK_LIBRARIES%"

                    helper.add_comment("run compiler (and linker)")
                    helper.print_space()
                    helper.print_line('Step: ' + settings.target_name +'')
                    helper.print_line('~~~~~~~~~~~~~~~~~~~~~~')
                    helper.print_line('Compiling and Linking...')


                    if hot_reload:
                        helper.add_raw('\n:: skip actual compilation if hot reloading\n')
                        helper.add_raw('@if %PL_HOT_RELOAD_STATUS% equ 1 ( goto ' + 'Cleanup' + settings.target_name + ' )\n')
                    
                    helper.add_raw('\n:: call compiler\n')
                    helper.add_raw('cl' + sub_buffer0 + ' %PL_SOURCES% -Fe"' + settings.output_directory + '/' + settings.output_binary + settings.output_binary_extension + '" -Fo"' + settings.output_directory + '/" -link' + sub_buffer1 + ' -PDB:"' + settings.output_directory + '/' + settings.output_binary + '_%random%.pdb"' + sub_buffer2 + "\n\n")

                    helper.add_comment("check build status")
                    helper.add_raw("@set PL_BUILD_STATUS=%ERRORLEVEL%\n")
                    helper.add_raw("\n:: failed\n")
                    helper.add_raw("@if %PL_BUILD_STATUS% NEQ 0 (\n")
                    helper.add_raw("    @echo Compilation Failed with error code: %PL_BUILD_STATUS%\n")
                    helper.add_raw("    @set PL_RESULT=Failed.\n")
                    helper.add_raw("    goto " + 'Cleanup' + register_config)
                    helper.add_raw("\n)\n")

                helper.add_spacing()

                helper.add_comment("print results")
                helper.print_line("Result:  %PL_RESULT%")
                helper.add_raw("@echo ~~~~~~~~~~~~~~~~~~~~~~\n")
                helper.add_spacing()

                if settings.post_build_step is not None:
                    helper.add_line(settings.post_build_step)
                    helper.add_spacing()

                if not settings.reloadable:
                    helper.add_label("Exit_" + settings.target_name)
                    helper.add_spacing()

        helper.add_line(':Cleanup' + register_config)
        helper.add_spacing()
        helper.print_line('Cleaning...')

        helper.add_spacing()
        helper.add_comment("delete obj files(s)")
        for dir in output_dirs:
            helper.add_line('@del "' + dir + '/*.obj"  > nul 2> nul')

        helper.add_spacing()
        helper.add_comment("delete lock file(s)")
        for lock_file in lock_files:
            helper.delete_file(settings.output_directory + '/' + lock_file)
    
    helper.add_spacing()
    helper.add_comment('~' * 40)
    helper.add_comment('end of ' + settings.config_name + ' configuration')
    helper.add_line("goto ExitLabel")
    helper.add_spacing()

    helper.add_label("ExitLabel")
    helper.add_spacing()
    # if _context.post_build_step is not None:
    #     helper.add_line(_context.post_build_step)
    #     helper.add_spacing()
    helper.add_comment("return CWD to previous CWD")
    helper.add_line('@popd')
    helper.write_file(_context.working_directory + "/" + name)