import pl_build.core as pl
import pathlib

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):
        p = pathlib.PureWindowsPath(file)
        self.buffer += ' ' * self.indent + '@if exist "' + file +  '"'
        self.buffer += ' del "' + str(p) + '"\n'

def generate_build(name, user_options = None):

    platform = "Windows"
    compiler = "msvc"

    data = pl.get_script_data()

    # retrieve settings supported by this platform/compiler & add
    # default extensions if the user did not
    platform_settings = []
    for settings in data.current_settings:
        if settings.platform_name == platform and settings.name == compiler:
            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)


    helper = plWin32Helper()

    hot_reload = False

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

    helper.add_comment("Project: " + data.project_name)
    helper.add_comment("Auto Generated by:")
    helper.add_comment('"pl_build.py" version: ' + data.version)
    helper.add_spacing()
    helper.add_comment("Project: " + data.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
    helper.add_comment("modify PATH to find vcvarsall.bat")
    helper.add_line('@if exist "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Auxiliary/Build" @set PATH=C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Auxiliary\\Build;%PATH%')
    helper.add_line('@if exist "C:/Program Files/Microsoft Visual Studio/2019/Community/VC/Auxiliary/Build" @set PATH=C:\\Program Files\\Microsoft Visual Studio\\2019\\Community\\VC\\Auxiliary\\Build;%PATH%')
    helper.add_line('@if exist "C:/Program Files/Microsoft Visual Studio/2022/Professional/VC/Auxiliary/Build" @set PATH=C:\\Program Files\\Microsoft Visual Studio\\2022\\Professional\\VC\\Auxiliary\\Build;%PATH%')
    helper.add_line('@if exist "C:/Program Files/Microsoft Visual Studio/2019/Professional/VC/Auxiliary/Build" @set PATH=C:\\Program Files\\Microsoft Visual Studio\\2019\\Professional\\VC\\Auxiliary\\Build;%PATH%')
    helper.add_line('@if exist "C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Auxiliary/Build" @set PATH=C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Auxiliary\\Build;%PATH%')
    helper.add_line('@if exist "C:/Program Files/Microsoft Visual Studio/2019/Enterprise/VC/Auxiliary/Build" @set PATH=C:\\Program Files\\Microsoft Visual Studio\\2019\\Enterprise\\VC\\Auxiliary\\Build;%PATH%')
    helper.add_line('@if exist "C:/Program Files (x86)/Microsoft Visual Studio/2022/Community/VC/Auxiliary/Build" @set PATH=C:\\Program Files (x86)\\Microsoft Visual Studio\\2022\\Community\\VC\\Auxiliary\\Build;%PATH%')
    helper.add_line('@if exist "C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Auxiliary/Build" @set PATH=C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Auxiliary\\Build;%PATH%')
    helper.add_line('@if exist "C:/Program Files (x86)/Microsoft Visual Studio/2022/Professional/VC/Auxiliary/Build" @set PATH=C:\\Program Files (x86)\\Microsoft Visual Studio\\2022\\Professional\\VC\\Auxiliary\\Build;%PATH%')
    helper.add_line('@if exist "C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/VC/Auxiliary/Build" @set PATH=C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Professional\\VC\\Auxiliary\\Build;%PATH%')
    helper.add_line('@if exist "C:/Program Files (x86)/Microsoft Visual Studio/2022/Enterprise/VC/Auxiliary/Build" @set PATH=C:\\Program Files (x86)\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Auxiliary\\Build;%PATH%')
    helper.add_line('@if exist "C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/VC/Auxiliary/Build" @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=[1m[92mSuccessful.[0m')
    helper.add_spacing()

    # set default config
    if data.registered_configurations:
        helper.add_comment("default configuration")
        helper.add_line("@set PL_CONFIG=" + data.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 data.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()



    for register_config in data.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 data.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 " + data.reload_target_name + " exe is running")
            helper.add_line("@echo off")
            helper.add_line('2>nul (>>"' + data.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("[1m[97m[41m--------[42m HOT RELOADING [41m--------[0m")
            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:
                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 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()

            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("[1m[93mStep: " + settings.target_name +"[0m")
                helper.print_line("[1m[93m~~~~~~~~~~~~~~~~~~~~~~[0m")
                helper.print_line("[1m[36mCompiling...[0m")

                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 [1m[91mCompilation Failed with error code[0m: %PL_BUILD_STATUS%\n")
                helper.add_raw("    @set PL_RESULT=[1m[91mFailed.[0m\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 [1m[36mLinking...[0m\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 [1m[93mStep: ' + settings.target_name +'[0m\n')
                helper.add_raw('@echo [1m[93m~~~~~~~~~~~~~~~~~~~~~~[0m\n')
                helper.add_raw('@echo [1m[36mCompiling and Linking...[0m\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 [1m[91mCompilation Failed with error code[0m: %PL_BUILD_STATUS%\n")
                helper.add_raw("    @set PL_RESULT=[1m[91mFailed.[0m\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('[1m[93mStep: ' + settings.target_name +'[0m')
                helper.print_line('[1m[93m~~~~~~~~~~~~~~~~~~~~~~[0m')
                helper.print_line('[1m[36mCompiling and Linking...[0m')


                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 [1m[91mCompilation Failed with error code[0m: %PL_BUILD_STATUS%\n")
                helper.add_raw("    @set PL_RESULT=[1m[91mFailed.[0m\n")
                helper.add_raw("    goto " + 'Cleanup' + register_config)
                helper.add_raw("\n)\n")

            helper.add_spacing()

            helper.add_comment("print results")
            helper.print_line("[36mResult: [0m %PL_RESULT%")
            helper.add_raw("@echo [36m~~~~~~~~~~~~~~~~~~~~~~[0m\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('[1m[36mCleaning...[0m')

        helper.add_spacing()
        helper.add_comment("delete obj files(s)")
        for dir in output_dirs:
            p = pathlib.PureWindowsPath(dir + '/*.obj')
            helper.add_line('@del "' + str(p) + '"  > 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(name)
