import os
import rtconfig
import resource
import json
import fnmatch

# Check SIFLI SDK environment variable
SIFLI_SDK = os.getenv('SIFLI_SDK')
L_SIFLI_SDK = SIFLI_SDK.replace('/', '\\') if SIFLI_SDK else ''
if not SIFLI_SDK:
    print("Please run set_env.bat in root folder of SIFLI SDK to set environment.")
    exit(-1)

# Check SIFLI SOLUTION environment variable
SIFLI_SOLUTION = os.getenv('SIFLI_SOLUTION')
if not SIFLI_SOLUTION:
    print("Please run set_env.bat in root folder of SIFLI SOLUTION to set environment.")
    exit(-1)

from building import *

# Add command line option: --resource (compile resource files)
AddOption("", "--resource",
          action="store_true", dest="resource", default=False,
          help="Compile resource")

# Add command line option: --lang (specify language pack name)
AddOption('--lang',
          dest='lang',
          type='string',
          nargs=1,
          action='store',
          default='lang_pack',
          help='Set lang pack name')

# Create rtconfig.h if it does not exist
try:
    f = open('./rtconfig.h', 'r')
except:
    f = open('./rtconfig.h', 'w')
finally:
    f.close()

# Generate post-build batch file (postbuild.bat)
def gen_postbuilt_bat():
    with open(os.path.join('postbuild.bat'), "w+") as fpout:
        fpout.write('fromelf --bin %1 --output build\\bf0_ap.bin\n')
        fpout.write('fromelf --i32 %1 --output build\\bf0_ap.hex\n')
        fpout.write('fromelf --text -c %1 > build\\keil\\Obj\\bf0_ap.lst\n')
        fpout.write('copy %1 build\\ /y\n')
        fpout.write('copy build\\keil\\obj\\bf0_ap.lst build\\ /y\n')
        fpout.write('copy build\\keil\\list\\bf0_ap.map build\\ /y\n')
        fpout.write('del build\\bf0_ap.bin\\*.bin\n')
        fpout.write('copy  build\\bf0_ap.bin\\* build\\bf0_ap.bin\\*.bin /y\n')

gen_postbuilt_bat()

# Set default compilation options for SIFLI project
SifliEnv()

# Generate pre-build batch file (prebuild.bat)
def gen_prebuilt_bat():
    with open(os.path.join('prebuild.bat'), "w+") as fpout:
        board_str = ''
        if (GetDepend('BSP_USING_BOARD_EH_LB523XXX') or
            GetDepend('BSP_USING_BOARD_EM_LB525XXX') or
            GetDepend('BSP_USING_BOARD_EM_LB52D') or
            GetDepend('BSP_USING_BOARD_SF32LB52_ULP') or
            GetDepend('BSP_USING_BOARD_SF32LB52_NANO_52J')):
            board_str = 'lcpu_lb6500'
        elif (GetDepend('BSP_USING_BOARD_EH_SS6500XXX') or
              GetDepend('BSP_USING_BOARD_SF32_OED_EPD')):
            board_str = 'lcpu_lb6500'
        if board_str and L_SIFLI_SDK:
            fpout.write("mkdir lcpu_img\n")
            fpout.write("copy /Y " + L_SIFLI_SDK + "example\\rom_bin\\lcpu_general_ble_img\\" +
                        board_str + '.c lcpu_img\\lcpu_img.c')

gen_prebuilt_bat()
os.system("prebuild.bat")

# Generate linker script include file (link_include.h)
def gen_linker_script():
    if not os.path.exists('linker_script'):
        os.makedirs('linker_script')
    with open(os.path.join('linker_script/link_include.h'), "w+") as fpout:
        fpout.write('#include "../rtconfig.h"\n')
        if GetDepend('SOC_SF32LB58X'):
            fpout.write('#include "' + SIFLI_SDK + 'drivers/cmsis/sf32lb58x/mem_map.h"\n')
        elif GetDepend('SOC_SF32LB56X'):
            fpout.write('#include "' + SIFLI_SDK + 'drivers/cmsis/sf32lb56x/mem_map.h"\n')
        elif GetDepend('SOC_SF32LB52X'):
            fpout.write('#include "' + SIFLI_SDK + 'drivers/cmsis/sf32lb52x/mem_map.h"\n')
        else:
            fpout.write('#include "' + SIFLI_SDK + 'drivers/cmsis/sf32lb55x/mem_map.h"\n')
        if GetDepend('SOLUTION_RES_USING_NAND_C'):
            fpout.write('#undef HCPU_FLASH2_IMG_SIZE\n')
            fpout.write('#define HCPU_FLASH2_IMG_SIZE (128*1024*1024)\n')

gen_linker_script()

# ----------------------------------------------------------------------
#  Helper to safely extract source files from a SCons object
# ----------------------------------------------------------------------
def get_sources_from_obj(obj):
    """
    Extract source file paths from a SCons object (Node or Builder).
    Returns a list of source file paths (strings).
    """
    sources = []

    # Handle 'sources' attribute (most common)
    if hasattr(obj, 'sources'):
        src_attr = obj.sources
        if callable(src_attr):
            # It's a method, call it to get the actual sources
            try:
                src_attr = src_attr()
            except Exception as e:
                print("Warning: calling obj.sources() failed: {}".format(e))
                src_attr = None
        if src_attr:
            # Check if it's a list/tuple
            if isinstance(src_attr, (list, tuple)):
                sources.extend(src_attr)
            else:
                # Single node
                sources.append(src_attr)

    # Return the list of sources (may be empty)
    return sources

# ----------------------------------------------------------------------
#  compile_commands.json generation function (enhanced)
# ----------------------------------------------------------------------
def generate_compile_commands(env, source_list):
    """
    Generate compile_commands.json based on the build environment `env` and the list of source files `source_list`.
    This file is used by clangd for accurate code navigation.
    """
    compile_commands = []
    project_root = os.path.abspath('.')

    # 1. Extract necessary compilation variables from env (fallback to rtconfig if missing)
    cc = env.get('CC', rtconfig.CC)

    cflags = env.get('CFLAGS', rtconfig.CFLAGS)
    cxxflags = env.get('CXXFLAGS', getattr(rtconfig, 'CXXFLAGS', ''))

    # Include paths
    cpppath = env.get('CPPPATH', [])
    if not cpppath and hasattr(rtconfig, 'CPPPATH'):
        cpppath = rtconfig.CPPPATH
    include_flags = ' '.join(['-I' + os.path.abspath(p) for p in cpppath])

    # Macro definitions
    defines = env.get('DEFINES', [])
    if not defines and hasattr(rtconfig, 'DEFINES'):
        defines = rtconfig.DEFINES
    define_flags = ' '.join(['-D' + d for d in defines])

    # Build base command
    base_cmd = '{cc} {cflags} {cxxflags} {include_flags} {define_flags}'.format(
        cc=cc,
        cflags=cflags,
        cxxflags=cxxflags,
        include_flags=include_flags,
        define_flags=define_flags
    ).strip()

    # Fallback if base_cmd is empty
    if not base_cmd:
        print("Warning: base_cmd is empty. Using fallback minimal command.")
        base_cmd = cc + ' -c'

    # 2. Process each source file
    for src in source_list:
        src_path = os.path.abspath(str(src))
        obj_name = os.path.basename(src_path).replace('.c', '.o').replace('.cpp', '.o').replace('.cxx', '.o')
        output_dir = getattr(rtconfig, 'OUTPUT_DIR', 'build')
        obj_path = os.path.join(output_dir, obj_name)

        full_cmd = base_cmd + ' -c ' + src_path + ' -o ' + obj_path

        compile_commands.append({
            "directory": project_root,
            "command": full_cmd,
            "file": src_path
        })

    # 3. Write to file
    output_path = os.path.join(project_root, 'compile_commands.json')
    with open(output_path, 'w') as f:
        json.dump(compile_commands, f, indent=2)

    print("\n=== compile_commands.json generation completed ===")
    print("File path: {}".format(output_path))
    print("Number of entries: {}".format(len(compile_commands)))
    if len(compile_commands) == 0:
        print("Warning: No source files found. Please check the project structure or build configuration.")
    else:
        print("Success: compile_commands.json is ready for clangd.")
# ----------------------------------------------------------------------

# Determine build target based on command line options
if GetOption('resource'):
    resource.BuildStringPackage2('resources/lang', GetOption('lang'))
    print("Resource compilation completed. No compile_commands.json generated (no source files).")
else:
    OUTPUT_DIR = getattr(rtconfig, 'OUTPUT_DIR', 'build')
    TARGET = OUTPUT_DIR + rtconfig.TARGET_NAME + '.' + rtconfig.TARGET_EXT

    env = Environment(tools=['mingw'],
                      AS=rtconfig.AS, ASFLAGS=rtconfig.AFLAGS,
                      CC=rtconfig.CC, CFLAGS=rtconfig.CFLAGS,
                      CXX=rtconfig.CXX, CXXFLAGS=rtconfig.CXXFLAGS,
                      AR=rtconfig.AR, ARFLAGS='-rc',
                      LINK=rtconfig.LINK, LINKFLAGS=rtconfig.LFLAGS)
    env.PrependENVPath('PATH', rtconfig.EXEC_PATH)
    env.Append(INCPATH=[os.path.abspath('.')])

    if hasattr(rtconfig, 'CPPPATH'):
        env.Append(CPPPATH=rtconfig.CPPPATH)
    if hasattr(rtconfig, 'DEFINES'):
        env.Append(DEFINES=rtconfig.DEFINES)

    objs = PrepareBuilding(env)
    DoBuilding(TARGET, objs)

    # --- Collect source files from build objects using the safe helper ---
    source_files = []
    for obj in objs:
        source_files.extend(get_sources_from_obj(obj))

    # Remove duplicates
    source_files = list(set(source_files))

    print("\n=== Debug: Source files collected from build objects ===")
    print("Number of source files: {}".format(len(source_files)))
    if source_files:
        print("First few source files: {}".format(source_files[:5]))
    else:
        print("Warning: No source files found from build objects. Performing fallback directory scan...")
        # Fallback: recursively scan for source files
        for root, dirs, files in os.walk('.'):
            # Skip build/output directories
            if any(skip in root for skip in ['build', 'output', '.git', '__pycache__']):
                continue
            for file in files:
                if fnmatch.fnmatch(file, '*.c') or fnmatch.fnmatch(file, '*.cpp') or fnmatch.fnmatch(file, '*.cxx'):
                    source_files.append(os.path.join(root, file))
        source_files = list(set(source_files))
        print("Fallback scan found {} source files.".format(len(source_files)))

   
    # --- Add files from dynamic app directory ---
    dynamic_app_dir = os.path.join(SIFLI_SOLUTION, 'solution', 'examples', '_dynamic_app', 'c')
    print(dynamic_app_dir);
    if os.path.exists(dynamic_app_dir):
        added = 0
        for root, dirs, files in os.walk(dynamic_app_dir):
            for file in files:
                if file.endswith('.c') or file.endswith('.h'):
                    source_files.append(os.path.join(root, file))
                    added += 1
        source_files = list(set(source_files))
        print("Added {} files from dynamic app directory.".format(added))
    else:
        print("Dynamic app directory not found: {}".format(dynamic_app_dir))
  
    # Remove duplicates
    source_files = list(set(source_files))

    # Exclude files matching certain path patterns
    exclude_patterns = ['/test/', '/build/', '/sdk/middleware/app_mem/']  # Files containing these strings in path will be excluded
    source_files = [f for f in source_files if not any(p in str(f).replace('\\', '/') for p in exclude_patterns)]
  
    # Generate compile_commands.json
    generate_compile_commands(env, source_files)