#! /usr/bin/env python3

import os
import argparse
import config
import config.user_input.console
from build_all_examples import run_program

from config.boards import list_of_boards, load_board_config
from config.devices import list_of_devices, list_of_vendors, \
     list_of_families, load_device_config
from config.archs import list_of_archs, load_cpu_config

script_dir = os.path.dirname(__file__)
ADL_root_dir = os.path.abspath(os.path.join(script_dir, ".."))
cwd = os.getcwd()

interactive = True
use_default = True


def ensure_dir(directory):
    if not os.path.exists(directory):
        os.makedirs(directory)


def board_config(config):
    config.query_enum_key("Board", list_of_boards(), "Custom_Board")

    if config.get_config("Board") != "Custom_Board":
        load_board_config(config)


def mcu_config(config, source_dir):

    config.query_enum_key("Architecture", list_of_archs(config))

    if config.get_config("Architecture") != "Native":
        config.query_enum_key("Vendor", list_of_vendors(config))
        config.query_enum_key("Device_Family", list_of_families(config))
        config.query_enum_key("Device_Name", list_of_devices(config))

        device = config.get_config("Device_Name")

        if device.startswith("STM32F4"):
            core = "ARM Cortex-M4F"
        elif device.startswith("STM32F7"):
            core = "ARM Cortex-M7F"
        elif device.startswith("nRF51"):
            core = "ARM Cortex-M0"
        elif device.startswith("nRF52"):
            core = "ARM Cortex-M4F"
        elif device.startswith("FE3"):
            core = "RISC-V32"
        elif device.startswith("U5"):
            core = "RISC-V64"

        config.set_config_string_key("CPU_Core", core, "mcu definition")

        family = config.get_config("Device_Family")

        if family.startswith("STM32"):
            config.query_integer_key("High_Speed_External_Clock",
                                     1000000, 32000000)
        elif family.startswith("FE3"):
            config.query_integer_key("hifive1_uart_root")
            config.query_bool_key("qemu_sifive_test_exit")

        load_cpu_config(config)
        load_device_config(config, source_dir)

        config.query_integer_key("Number_Of_Interrupts", default = 0)

    # TDOD: Is the SVD mapping available?
    # TODO: Does the user want to generate it?


def linker_script_config(config):
    config.query_bool_key('Use_Startup_Gen', default="False")

    if config.get_config('Use_Startup_Gen') == "True":
        config.query_bool_key('Has_Custom_Memory_Area_1', default="False")

        cnt = 1
        while config.get_config('Has_Custom_Memory_Area_%d' % cnt) == "True":
            config.query_enum_key('Custom_Mem_%d_Kind' % cnt, ['ROM', 'RAM'])
            config.query_integer_key('Custom_Mem_%d_Addr' % cnt, 0)
            config.query_integer_key('Custom_Mem_%d_Size' % cnt, 0)
            config.query_string_key('Custom_Mem_%d_Name' % cnt,
                                    default="custom_mem_%d" % cnt)

            config.add_memory(config.get_config('Custom_Mem_%d_Kind' % cnt),
                              config.get_config('Custom_Mem_%d_Name' % cnt),
                              config.get_config('Custom_Mem_%d_Addr' % cnt),
                              config.get_config('Custom_Mem_%d_Size' % cnt))

            cnt += 1
            config.query_bool_key('Has_Custom_Memory_Area_%d' % cnt,
                                  default="False")

    if len(config.memory_names()) > 0:
        config.query_string_key('Boot_Memory', default=config.memory_names()[0])


def middleware_config(config):
    config.query_integer_key('Max_Path_Length', 0, default="1024")
    config.query_integer_key('Max_Mount_Points', 0, default="2")
    config.query_integer_key('Max_Mount_Name_Length', 0, default="128")

    origin = "middleware config"
    config.add_source_dir('middleware/src/filesystem', origin)
    config.add_source_dir('middleware/src/BLE', origin)
    config.add_source_dir('middleware/src/utils', origin)
    config.add_source_dir('middleware/src/audio', origin)
    config.add_source_dir('middleware/src/monitor', origin)
    config.add_source_dir('middleware/src/bitmap', origin)
    config.add_source_dir('middleware/src/command_line', origin)
    config.add_source_dir('middleware/src/sdmmc', origin)
    config.add_source_dir('middleware/src/neopixel', origin)

    runtime_profile = config.get_config("Runtime_Profile")

    if runtime_profile is None:
        runtime_profile = ''

    if runtime_profile.startswith("light-tasking") or runtime_profile.startswith("embedded"):
        config.add_source_dir('middleware/src/ravenscar-common', origin)


def components_config(config):
    config.add_source_dir('components/src/**', "components config")


def runtime_config(config):
    config.query_string_key('Has_ZFP_Runtime', default="False")
    config.query_string_key('Has_Ravenscar_SFP_Runtime', default="False")
    config.query_string_key('Has_Ravenscar_Full_Runtime', default="False")

    profiles = []
    if config.get_config("Has_Ravenscar_Full_Runtime") == "True":
        profiles.append("embedded")
    if config.get_config("Has_Ravenscar_SFP_Runtime") == "True":
        profiles.append("light-tasking")
    if config.get_config("Has_ZFP_Runtime") == "True":
        profiles.append("light")

    if len(profiles) > 0:
        config.query_enum_key('Runtime_Profile',  profiles,
                              default=profiles[0])

        config.query_string_key('Runtime_Name_Suffix')

        suffix = config.get_config("Runtime_Name_Suffix")
        profile = config.get_config("Runtime_Profile")
        config.query_string_key('Runtime_Name', default=profile + "-" + suffix)


def ADL_configuration(config, project_directory, project_name,
                      source_dir_name, object_dir_name):

    relative_ADL_root_path = os.path.relpath(ADL_root_dir, project_directory)
    source_dir = os.path.join(project_directory, source_dir_name)
    ada_config_path = os.path.join(source_dir, "adl_config.ads")
    gpr_path = os.path.join(project_directory, "%s.gpr" % project_name.lower())

    ensure_dir(source_dir)

    # HAL is always incuded
    config.add_source_dir('hal/src/', "HAL config")

    board_config(config)
    mcu_config(config, source_dir)
    runtime_config(config)
    linker_script_config(config)
    middleware_config(config)
    components_config(config)

    print("The configuration is now finished.")
    print("Let's generate some files:")


    #  GPR file
    gpr = "--  This project file was generated by the " + \
          "Ada_Drivers_Library project wizard script\n"
    gpr += "library project %s is\n" % project_name

    gpr += """
   type Build_Type is ("Debug", "Production");
   Build : Build_Type := external ("ADL_BUILD", "Debug");

   type Disabled_Or_Enabled_Type is ("Disabled", "Enabled");
   Build_Checks : Disabled_Or_Enabled_Type := external ("ADL_BUILD_CHECKS", "Disabled");
   Callgraphs : Disabled_Or_Enabled_Type := external ("CALLGRAPHS", "Enabled");

   --  Target architecture
"""

    gpr += '   for Target use \"%s\";' % config.target()

    gpr += """
   Target := Project'Target;

   --  Callgraph info is not available on all architectures, and not always
   --  desired
   Callgraph_Switch := ();
   case Callgraphs is
      when "Enabled" =>
         case Target is
            when "riscv32-unknown-elf" => null;
            when others => Callgraph_Switch := ("-fcallgraph-info=su");
         end case;
      when "Disabled" =>
         null;
   end case;

   Build_Checks_Switches := ();
   case Build_Checks is
      when "Disabled" => null;
      when others =>
         Build_Checks_Switches :=
           ("-gnaty", "-gnatyM120", "-gnatyO", --  Style checks
            "-gnatwe"); --  Warnings as errors
   end case;

   package Compiler is
      case Build is
         when "Production" =>
            for Default_Switches ("Ada") use
              ("-O3",     -- Optimization
               "-gnatp",  -- Supress checks
               "-gnatn"); -- Enable inlining
         when "Debug" =>
            for Default_Switches ("Ada") use
              ("-O0",    -- No optimization
               "-gnata") -- Enable assertions
              & Callgraph_Switch;
      end case;

      for Default_Switches ("ada") use Compiler'Default_Switches ("Ada") &
        Callgraph_Switch &
        Build_Checks_Switches &
        ("-g",       -- Debug info
         "-gnatwa",  -- All warnings
         "-gnatw_A", -- Turn off warnings for anonymous allocators
         "-gnatQ",   -- Don't quit. Generate ALI and tree files even if illegalities
         "-gnatw.X", -- Disable warnings for No_Exception_Propagation
         "-ffunction-sections", -- Create a linker section for each function
         "-fdata-sections");  -- Create a linker section for each data
   end Compiler;


"""
    if config.get_config ("Use_Startup_Gen") == "True":
        gpr += '   for Languages use ("Ada", "Asm_CPP");\n'
    else:
        gpr += '   for Languages use ("Ada");\n'

    gpr += "   for Create_Missing_Dirs use \"True\";\n"
    gpr += "   for Object_Dir use \"%s_\" & Build;\n" % object_dir_name
    gpr += "   for Library_Dir use \"%s_lib_\" & Build;\n" % object_dir_name
    gpr += "   for Library_Kind use \"static\";\n"
    gpr += "   for Library_Name use \"ada_drivers_library\";\n"

    if config.get_config ("Use_Startup_Gen") == "True":
        gpr += '\n   Linker_Switches := ' + \
               '("-T", Project\'Project_dir & "/%s/link.ld");\n' % source_dir_name
    else:
        gpr += '\n   Linker_Switches := ();\n'

    gpr += config.gpr_configuration(relative_ADL_root_path, source_dir_name)
    gpr += "end %s;\n" % project_name
    print(" -> Writing gprbuild project file.")
    with open(gpr_path, "w") as f:
        f.write(gpr)

    # Ada config package
    print(" -> Writing the Ada configuration file.")

    ada = "--  This package was generated by the " + \
          "Ada_Drivers_Library project wizard script\n"
    ada += "package ADL_Config is\n"
    ada += config.ada_configuration()
    ada += "end ADL_Config;\n"
    with open(ada_config_path, "w") as f:
        f.write(ada)

    config.print_remaining_pre_defined()

    # Run startup-gen
    if config.get_config ("Use_Startup_Gen") == "True":
        print(" -> Generating startup files.")
        run_program ('startup-gen',
                     '-P', gpr_path,
                     '-l', os.path.join(source_dir, "link.ld"),
                     '-s', os.path.join(source_dir, "crt0.S"))

    print("Your Ada Drivers Library project is now ready to use.")


description = \
"""Welcome to the Ada Drivers Library (ADL) project wizard. This script will
ask you some questions to define the ADL configuration of your project. It will
then generate the different files required to use ADL based on this
configuration."""

parser = argparse.ArgumentParser(description=description)

parser.add_argument('definitions', nargs='*',
                    help="""Pre defined config key=value. Use this to give predefined answer to the
                    configuration question. For instant Board=STM32F407_Discovery""")

parser.add_argument('-i', '--input-config', type=argparse.FileType('r'),
                    action='append',
                    help="""Load pre defined config keys from a file. The file must containt couples of
                    key=value, only one per line and nothing else.""")

parser.add_argument('--script-mode', action="store_true",
                    help="""Enable script mode. In script mode there is no input required from the user.
                    Configuration values will be taken from pre-defined values
                    (--input-config or command line definitions), or default
                    value if any. The configuration will fail if there are no
                    pre-defined or default value for a key. This mode can be
                    used to automatically generated ADL project file from a
                    known configuration.""")

parser.add_argument('--dont-use-defaults', action="store_true",
                    help="""When this switch is used in script mode, the configuration will not take into
                    account default values. This means that the configuration
                    will fail if a key doesn't have a pre-defined value from
                    --input-config or command line definitions. This can be
                    used to automatically detect discrepency when upgrading to
                    a new version of ADL, for instance if a new config key was
                    added.""")


parser.add_argument("-d", "--project-dir",
                    help="""Directory where the project will be created (default is Ada_Drivers_Library).
                    Use '.' to create the project in the current directory.""",
                    default="Ada_Drivers_Library")

parser.add_argument("-p", "--project-name",
                    help="""Name of the project file that will be generated (default is
                    Ada_Drivers_Library)""",
                    default="Ada_Drivers_Library")

parser.add_argument("-s", "--source-dir",
                    help="""Name of the source directory for you ADL project (default is src). This is
                    where the Ada confiuration file will be written. You can
                    also use this directory to add you own package
                    specification and/or package implementation to the ADL
                    project. The source directory will be created inside the
                    project directory""",
                    default="src")

parser.add_argument("-o", "--object-dir",
                    help="""Name of the object output directory that will be used for your ADL project
                    (default is obj). The object directory will be created
                    inside the project directory""",
                    default="obj")

args = parser.parse_args()

print(description)

interactive = not args.script_mode
use_default = not args.dont_use_defaults

ADL_config = config.Database(interactive,
                             use_default,
                             config.user_input.console)

if args.input_config is not None:
    for f in args.input_config:
        ADL_config.parse_input_config(f)

if args.definitions is not None:
    for d in args.definitions:
        key, value = d.partition("=")[::2]
        print("Add pre definition '%s' => '%s' from command line" % \
              (key, value))

        ADL_config.pre_define(key, value, "command line")

ensure_dir(args.project_dir)

ADL_configuration(ADL_config,
                  os.path.join(cwd, args.project_dir),
                  args.project_name,
                  args.source_dir,
                  args.object_dir)
