#!/usr/bin/env python3

import os
import shutil
import glob
import subprocess
import sys
from textwrap import dedent

def map_copy(mappings, target_base):
    """
    Helper function to map input files/directories to output files/directories.
    mappings: list of dicts with 'src' (glob pattern) and 'dst' (relative to target_base or absolute)
    'src' can be a single file or a directory (if it ends with /).
    """
    for mapping in mappings:
        src_pattern = mapping['src']
        dst_rel = mapping['dst']
        dst_path = os.path.join(target_base, dst_rel)

        # To preserve directory structure, we need to know where the wildcard starts
        # or have a way to determine the "base" of the search.
        # We'll split the pattern into a fixed base and a pattern part.
        
        # Simple heuristic: find the first occurrence of '*' or '?'
        wildcard_idx = -1
        for i, char in enumerate(src_pattern):
            if char in '*?':
                wildcard_idx = i
                break
        
        if wildcard_idx != -1:
            # Found a wildcard. The base is the directory containing it.
            pattern_base = os.path.dirname(src_pattern[:wildcard_idx])
        else:
            # No wildcard. If it's a directory, we might want to preserve its name?
            # For now, let's treat no-wildcard as no relative structure needed.
            pattern_base = None

        src_files = glob.glob(src_pattern, recursive=True)
        if not src_files:
            continue

        for src in src_files:
            if os.path.isdir(src):
                continue
            
            if pattern_base and src.startswith(pattern_base):
                # Calculate relative path from the base of the glob pattern
                rel_src = os.path.relpath(src, pattern_base)
                # If dst_rel ends with /, it's a target directory
                if dst_rel.endswith('/') or os.path.isdir(dst_path):
                    final_dst = os.path.join(dst_path, rel_src)
                else:
                    # If dst_rel is a file, we can't really preserve structure 
                    # unless we join it. But usually it's a dir if structure is preserved.
                    final_dst = dst_path
            else:
                final_dst = dst_path if not (dst_rel.endswith('/') or os.path.isdir(dst_path)) else os.path.join(dst_path, os.path.basename(src))

            os.makedirs(os.path.dirname(final_dst), exist_ok=True)
            shutil.copy2(src, final_dst)
            
def get_driver_mappings(driver_name):
    return [
        {'src': f'Drivers/{driver_name}/include/**', 'dst': f'Drivers/{driver_name}/include/'},
        {'src': f'Drivers/{driver_name}/*.md', 'dst': f'Drivers/{driver_name}/'},
        {'src': f'build/esp-idf/{driver_name}/lib{driver_name}.a', 'dst': f'Drivers/{driver_name}/binary/lib{driver_name}.a'},
    ]

def get_module_mappings(module_name):
    return [
        {'src': f'Modules/{module_name}/include/**', 'dst': f'Modules/{module_name}/include/'},
        {'src': f'Modules/{module_name}/*.md', 'dst': f'Modules/{module_name}/'},
        {'src': f'build/esp-idf/{module_name}/lib{module_name}.a', 'dst': f'Modules/{module_name}/binary/lib{module_name}.a'},
    ]

def create_module_cmakelists(module_name):
    return dedent(f'''
    cmake_minimum_required(VERSION 3.20)
    idf_component_register(
        INCLUDE_DIRS "include"
    )
    add_prebuilt_library({module_name} "binary/lib{module_name}.a")
    '''.format(module_name=module_name))

def write_module_cmakelists(path, content):
    with open(path, 'w') as f:
        f.write(content)

def driver_is_available(driver_name):
    """
    Some drivers only build for certain chip targets (e.g. sc2356-module is ESP32-P4 only,
    since it depends on esp_video/esp_cam_sensor/PPA which are themselves chip-restricted).
    Build output presence is the single source of truth for "does this driver support the
    current target" - no separate manifest to keep in sync with the real CMakeLists.txt
    REQUIRES/Kconfig guards.
    """
    binary_pattern = f'build/esp-idf/{driver_name}/lib{driver_name}.a'
    return bool(glob.glob(binary_pattern))

def add_driver(target_path, driver_name):
    mappings = get_driver_mappings(driver_name)
    map_copy(mappings, target_path)
    cmakelists_content = create_module_cmakelists(driver_name)
    write_module_cmakelists(os.path.join(target_path, f"Drivers/{driver_name}/CMakeLists.txt"), cmakelists_content)

def add_module(target_path, module_name):
    mappings = get_module_mappings(module_name)
    map_copy(mappings, target_path)
    cmakelists_content = create_module_cmakelists(module_name)
    write_module_cmakelists(os.path.join(target_path, f"Modules/{module_name}/CMakeLists.txt"), cmakelists_content)

def discover_all_drivers():
    """
    Discover all *-module directories under Drivers/ (not Modules/ - those are handled
    separately via add_module). Sorted for deterministic output across OS/filesystem order.
    """
    pattern = os.path.join('Drivers', '*-module')
    return sorted(
        os.path.basename(p) for p in glob.glob(pattern) if os.path.isdir(p)
    )

def generate_tactility_sdk_cmake(target_path, available_drivers):
    src = os.path.join('Buildscripts', 'TactilitySDK', 'TactilitySDK.cmake')
    with open(src) as f:
        content = f.read()
    placeholder = "        # DRIVER_COMPONENTS_PLACEHOLDER"
    assert placeholder in content, \
        f"Placeholder '{placeholder.strip()}' not found in {src} - template drifted, generator needs updating"
    components = "\n".join(f"        {d}" for d in available_drivers)
    new_content = content.replace(placeholder, components)
    assert placeholder not in new_content, \
        f"Placeholder '{placeholder.strip()}' still present after replacement in {src}"
    with open(os.path.join(target_path, 'TactilitySDK.cmake'), 'w') as f:
        f.write(new_content)

def generate_tactility_sdk_top_cmakelists(target_path, available_drivers):
    src = os.path.join('Buildscripts', 'TactilitySDK', 'CMakeLists.txt')
    with open(src) as f:
        content = f.read()
    placeholder = "        # DRIVER_INCLUDE_DIRS_PLACEHOLDER"
    assert placeholder in content, \
        f"Placeholder '{placeholder.strip()}' not found in {src} - template drifted, generator needs updating"
    include_dirs = "\n".join(f'        "Drivers/{d}/include"' for d in available_drivers)
    new_content = content.replace(placeholder, include_dirs)
    assert placeholder not in new_content, \
        f"Placeholder '{placeholder.strip()}' still present after replacement in {src}"
    with open(os.path.join(target_path, 'CMakeLists.txt'), 'w') as f:
        f.write(new_content)

def main():
    if len(sys.argv) < 2:
        print("Usage: release-sdk.py [target_path]")
        print("Example: release-sdk.py release/TactilitySDK")
        sys.exit(1)

    target_path = os.path.abspath(sys.argv[1])
    os.makedirs(target_path, exist_ok=True)

    # Mapping logic
    mappings = [
        {'src': 'version.txt', 'dst': ''},
        # TactilityC
        {'src': 'build/esp-idf/TactilityC/libTactilityC.a', 'dst': 'Libraries/TactilityC/binary/'},
        {'src': 'TactilityC/Include/*', 'dst': 'Libraries/TactilityC/include/'},
        {'src': 'TactilityC/CMakeLists.txt', 'dst': 'Libraries/TactilityC/'},
        {'src': 'TactilityC/LICENSE*.*', 'dst': 'Libraries/TactilityC/'},
        # TactilityFreeRtos
        {'src': 'TactilityFreeRtos/Include/**', 'dst': 'Libraries/TactilityFreeRtos/include/'},
        {'src': 'TactilityFreeRtos/CMakeLists.txt', 'dst': 'Libraries/TactilityFreeRtos/'},
        {'src': 'TactilityFreeRtos/LICENSE*.*', 'dst': 'Libraries/TactilityFreeRtos/'},
        # TactilityKernel
        {'src': 'build/esp-idf/TactilityKernel/libTactilityKernel.a', 'dst': 'Libraries/TactilityKernel/binary/'},
        {'src': 'TactilityKernel/include/**', 'dst': 'Libraries/TactilityKernel/include/'},
        {'src': 'TactilityKernel/CMakeLists.txt', 'dst': 'Libraries/TactilityKernel/'},
        {'src': 'TactilityKernel/*.md', 'dst': 'Libraries/TactilityKernel/'},
        # lvgl (basics)
        {'src': 'build/esp-idf/lvgl__lvgl/liblvgl__lvgl.a', 'dst': 'Libraries/lvgl/binary/liblvgl.a'},
        {'src': 'Libraries/lvgl/lvgl.h', 'dst': 'Libraries/lvgl/include/'},
        {'src': 'Libraries/lvgl/lv_version.h', 'dst': 'Libraries/lvgl/include/'},
        {'src': 'Libraries/lvgl/LICENCE*.*', 'dst': 'Libraries/lvgl/'},
        {'src': 'Libraries/lvgl/src/lv_conf_kconfig.h', 'dst': 'Libraries/lvgl/include/lv_conf.h'},
        {'src': 'Libraries/lvgl/src/**/*.h', 'dst': 'Libraries/lvgl/include/src/'},
        # elf_loader
        {'src': 'Libraries/elf_loader/elf_loader.cmake', 'dst': 'Libraries/elf_loader/'},
        {'src': 'Libraries/elf_loader/license.txt', 'dst': 'Libraries/elf_loader/'},
        # minmea
        {'src': 'build/esp-idf/minmea/libminmea.a', 'dst': 'Libraries/minmea/binary/'},
        {'src': 'Libraries/minmea/Include/**', 'dst': 'Libraries/minmea/include/'},
        {'src': 'Libraries/minmea/CMakeLists.txt', 'dst': 'Libraries/minmea/'},
        {'src': 'Libraries/minmea/README.md', 'dst': 'Libraries/minmea/'},
        {'src': 'Libraries/minmea/LICENSE*.*', 'dst': 'Libraries/minmea/'},
        {'src': 'Libraries/minmea/COPYING', 'dst': 'Libraries/minmea/'},
    ]

    map_copy(mappings, target_path)

    # Modules
    add_module(target_path, "lvgl-module")
    add_module(target_path, "crypt-module")
    add_module(target_path, "gps-module")

    # Drivers - only ones actually built for this target (chip-restricted drivers like
    # sc2356-module won't have a .a outside ESP32-P4)
    available_drivers = [d for d in discover_all_drivers() if driver_is_available(d)]
    for driver_name in available_drivers:
        add_driver(target_path, driver_name)

    # Final scripts - generated (not copied verbatim) so COMPONENTS/INCLUDE_DIRS only list
    # drivers actually available for this target
    generate_tactility_sdk_cmake(target_path, available_drivers)
    generate_tactility_sdk_top_cmakelists(target_path, available_drivers)

    # Output ESP-IDF SDK version to file
    esp_idf_version = os.environ.get("ESP_IDF_VERSION", "")
    with open(os.path.join(target_path, "idf-version.txt"), "a") as f:
        f.write(esp_idf_version)

if __name__ == "__main__":
    main()
