"""
mbed SDK
Copyright (c) 2011-2017 ARM Limited

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from __future__ import print_function, absolute_import
from builtins import str

from os.path import splitext, basename, relpath, join
import shutil
from tools.utils import mkdir
from tools.export.gnuarmeclipse import GNUARMEclipse
from tools.export.gnuarmeclipse import UID
from tools.build_api import prepare_toolchain
from tools.targets import TARGET_MAP
from sys import flags, platform

# Global random number generator instance.
u = UID()


class Sw4STM32(GNUARMEclipse):
    """
    Sw4STM32 class
    """
    NAME = 'Sw4STM32'
    TOOLCHAIN = 'GCC_ARM'

    BOARDS = {
        'B96B_F446VE':
        {
            'name': 'B96B-F446VE',
            'mcuId': 'STM32F446VETx'
        },
        'DISCO_F051R8':
        {
            'name': 'STM32F0DISCOVERY',
            'mcuId': 'STM32F051R8Tx'
        },
        'DISCO_F303VC':
        {
            'name': 'STM32F3DISCOVERY',
            'mcuId': 'STM32F303VCTx'
        },
        'DISCO_F334C8':
        {
            'name': 'STM32F3348DISCOVERY',
            'mcuId': 'STM32F334C8Tx'
        },
        'DISCO_F401VC':
        {
            'name': 'STM32F401C-DISCO',
            'mcuId': 'STM32F401VCTx'
        },
        'DISCO_F407VG':
        {
            'name': 'STM32F4DISCOVERY',
            'mcuId': 'STM32F407VGTx'
        },
        'DISCO_F413ZH':
        {
            'name': 'DISCO_F413',
            'mcuId': 'STM32F413ZHTx'
        },
        'DISCO_F429ZI':
        {
            'name': 'STM32F429I-DISCO',
            'mcuId': 'STM32F429ZITx'
        },
        'DISCO_F469NI':
        {
            'name': 'DISCO-F469NI',
            'mcuId': 'STM32F469NIHx'
        },
        'DISCO_F746NG':
        {
            'name': 'STM32F746G-DISCO',
            'mcuId': 'STM32F746NGHx'
        },
        'DISCO_F769NI':
        {
            'name': 'DISCO-F769NI',
            'mcuId': 'STM32F769NIHx'
        },
        'DISCO_L053C8':
        {
            'name': 'STM32L0538DISCOVERY',
            'mcuId': 'STM32L053C8Tx'
        },
        'DISCO_L072CZ_LRWAN1':
        {
            'name': 'DISCO-L072CZ-LRWAN1',
            'mcuId': 'STM32L072CZTx'
        },
        'MTB_MURATA_ABZ':
        {
            'name': 'MTB-MURATA-ABZ',
            'mcuId': 'STM32L0x2xZ'
        },
        'DISCO_L475VG_IOT01A':
        {
            'name': 'STM32L475G-DISCO',
            'mcuId': 'STM32L475VGTx'
        },
        'DISCO_L476VG':
        {
            'name': 'STM32L476G-DISCO',
            'mcuId': 'STM32L476VGTx'
        },
        'NUCLEO_F030R8':
        {
            'name': 'NUCLEO-F030R8',
            'mcuId': 'STM32F030R8Tx'
        },
        'NUCLEO_F031K6':
        {
            'name': 'NUCLEO-F031K6',
            'mcuId': 'STM32F031K6Tx'
        },
        'NUCLEO_F042K6':
        {
            'name': 'NUCLEO-F042K6',
            'mcuId': 'STM32F042K6Tx'
        },
        'NUCLEO_F070RB':
        {
            'name': 'NUCLEO-F070RB',
            'mcuId': 'STM32F070RBTx'
        },
        'NUCLEO_F072RB':
        {
            'name': 'NUCLEO-F072RB',
            'mcuId': 'STM32F072RBTx'
        },
        'NUCLEO_F091RC':
        {
            'name': 'NUCLEO-F091RC',
            'mcuId': 'STM32F091RCTx'
        },
        'NUCLEO_F103RB':
        {
            'name': 'NUCLEO-F103RB',
            'mcuId': 'STM32F103RBTx'
        },
        'NUCLEO_F207ZG':
        {
            'name': 'NUCLEO-F207ZG',
            'mcuId': 'STM32F207ZGTx'
        },
        'NUCLEO_F302R8':
        {
            'name': 'NUCLEO-F302R8',
            'mcuId': 'STM32F302R8Tx'
        },
        'NUCLEO_F303K8':
        {
            'name': 'NUCLEO-F303K8',
            'mcuId': 'STM32F303K8Tx'
        },
        'NUCLEO_F303RE':
        {
            'name': 'NUCLEO-F303RE',
            'mcuId': 'STM32F303RETx'
        },
        'NUCLEO_F303ZE':
        {
            'name': 'NUCLEO-F303ZE',
            'mcuId': 'STM32F303ZETx'
        },
        'NUCLEO_F334R8':
        {
            'name': 'NUCLEO-F334R8',
            'mcuId': 'STM32F334R8Tx'
        },
        'NUCLEO_F401RE':
        {
            'name': 'NUCLEO-F401RE',
            'mcuId': 'STM32F401RETx'
        },
        'NUCLEO_F410RB':
        {
            'name': 'NUCLEO-F410RB',
            'mcuId': 'STM32F410RBTx'
        },
        'NUCLEO_F411RE':
        {
            'name': 'NUCLEO-F411RE',
            'mcuId': 'STM32F411RETx'
        },
        'NUCLEO_F413ZH':
        {
            'name': 'NUCLEO-F413ZH',
            'mcuId': 'STM32F413ZHTx'
        },
        'NUCLEO_F429ZI':
        {
            'name': 'NUCLEO-F429ZI',
            'mcuId': 'STM32F429ZITx'
        },
        'NUCLEO_F446RE':
        {
            'name': 'NUCLEO-F446RE',
            'mcuId': 'STM32F446RETx'
        },
        'NUCLEO_F446ZE':
        {
            'name': 'NUCLEO-F446ZE',
            'mcuId': 'STM32F446ZETx'
        },
        'NUCLEO_F746ZG':
        {
            'name': 'NUCLEO-F746ZG',
            'mcuId': 'STM32F746ZGTx'
        },
        'NUCLEO_F767ZI':
        {
            'name': 'NUCLEO-F767ZI',
            'mcuId': 'STM32F767ZITx'
        },
        'NUCLEO_L011K4':
        {
            'name': 'NUCLEO-L011K4',
            'mcuId': 'STM32L011K4Tx'
        },
        'NUCLEO_L031K6':
        {
            'name': 'NUCLEO-L031K6',
            'mcuId': 'STM32L031K6Tx'
        },
        'NUCLEO_L053R8':
        {
            'name': 'NUCLEO-L053R8',
            'mcuId': 'STM32L053R8Tx'
        },
        'NUCLEO_L073RZ':
        {
            'name': 'NUCLEO-L073RZ',
            'mcuId': 'STM32L073RZTx'
        },
        'MTB_RAK811':
        {
            'name': 'MTB-RAK-811',
            'mcuId': 'STM32L151CBUxA'
        },
        'NUCLEO_L152RE':
        {
            'name': 'NUCLEO-L152RE',
            'mcuId': 'STM32L152RETx'
        },
        'NUCLEO_L432KC':
        {
            'name': 'NUCLEO-L432KC',
            'mcuId': 'STM32L432KCUx'
        },
        'MTB_ADV_WISE_1510':
        {
            'name': 'MTB-ADV-WISE-1510',
            'mcuId': 'STM32L443xC'
        },
        'NUCLEO_L476RG':
        {
            'name': 'NUCLEO-L476RG',
            'mcuId': 'STM32L476RGTx'
        },
        'NUCLEO_L486RG':
        {
            'name': 'NUCLEO-L486RG',
            'mcuId': 'STM32L486RGTx'
        },
        'NUCLEO_L496ZG':
        {
            'name': 'NUCLEO-L496ZG',
            'mcuId': 'STM32L496ZGTx'
        },
        'NUCLEO_L496ZG_P':
        {
            'name': 'NUCLEO-L496ZG',
            'mcuId': 'STM32L496ZGTx'
        },
    }

    @classmethod
    def is_target_supported(cls, target_name):
        target = TARGET_MAP[target_name]
        target_supported = bool(set(target.resolution_order_names)
                                .intersection(set(cls.BOARDS.keys())))
        toolchain_supported = cls.TOOLCHAIN in target.supported_toolchains
        return target_supported and toolchain_supported

    def __gen_dir(self, dir_name):
        """
        Method that creates directory
        """
        settings = join(self.export_dir, dir_name)
        mkdir(settings)

    def get_fpu_hardware(self, fpu_unit):
        """
        Convert fpu unit name into hardware name.
        """
        hw = ''
        fpus = {
            'fpv4spd16': 'fpv4-sp-d16',
            'fpv5d16': 'fpv5-d16',
            'fpv5spd16': 'fpv5-sp-d16'
        }
        if fpu_unit in fpus:
            hw = fpus[fpu_unit]
        return hw

    def process_sw_options(self, opts, flags_in):
        """
        Process System Workbench specific options.

        System Workbench for STM32 has some compile options, which are not recognized by the GNUARMEclipse exporter.
        Those are handled in this method.
        """
        opts['c']['preprocess'] = False
        if '-E' in flags_in['c_flags']:
            opts['c']['preprocess'] = True
        opts['cpp']['preprocess'] = False
        if '-E' in flags_in['cxx_flags']:
            opts['cpp']['preprocess'] = True
        opts['c']['slowflashdata'] = False
        if '-mslow-flash-data' in flags_in['c_flags']:
            opts['c']['slowflashdata'] = True
        opts['cpp']['slowflashdata'] = False
        if '-mslow-flash-data' in flags_in['cxx_flags']:
            opts['cpp']['slowflashdata'] = True
        if opts['common']['optimization.messagelength']:
            opts['common']['optimization.other'] += ' -fmessage-length=0'
        if opts['common']['optimization.signedchar']:
            opts['common']['optimization.other'] += ' -fsigned-char'
        if opts['common']['optimization.nocommon']:
            opts['common']['optimization.other'] += ' -fno-common'
        if opts['common']['optimization.noinlinefunctions']:
            opts['common']['optimization.other'] += ' -fno-inline-functions'
        if opts['common']['optimization.freestanding']:
            opts['common']['optimization.other'] += ' -ffreestanding'
        if opts['common']['optimization.nobuiltin']:
            opts['common']['optimization.other'] += ' -fno-builtin'
        if opts['common']['optimization.spconstant']:
            opts['common']['optimization.other'] += ' -fsingle-precision-constant'
        if opts['common']['optimization.nomoveloopinvariants']:
            opts['common']['optimization.other'] += ' -fno-move-loop-invariants'
        if opts['common']['warnings.unused']:
            opts['common']['warnings.other'] += ' -Wunused'
        if opts['common']['warnings.uninitialized']:
            opts['common']['warnings.other'] += ' -Wuninitialized'
        if opts['common']['warnings.missingdeclaration']:
            opts['common']['warnings.other'] += ' -Wmissing-declarations'
        if opts['common']['warnings.pointerarith']:
            opts['common']['warnings.other'] += ' -Wpointer-arith'
        if opts['common']['warnings.padded']:
            opts['common']['warnings.other'] += ' -Wpadded'
        if opts['common']['warnings.shadow']:
            opts['common']['warnings.other'] += ' -Wshadow'
        if opts['common']['warnings.logicalop']:
            opts['common']['warnings.other'] += ' -Wlogical-op'
        if opts['common']['warnings.agreggatereturn']:
            opts['common']['warnings.other'] += ' -Waggregate-return'
        if opts['common']['warnings.floatequal']:
            opts['common']['warnings.other'] += ' -Wfloat-equal'
        opts['ld']['strip'] = False
        if '-s' in flags_in['ld_flags']:
            opts['ld']['strip'] = True
        opts['ld']['shared'] = False
        if '-shared' in flags_in['ld_flags']:
            opts['ld']['shared'] = True
        opts['ld']['soname'] = ''
        opts['ld']['implname'] = ''
        opts['ld']['defname'] = ''
        for item in flags_in['ld_flags']:
            if item.startswith('-Wl,-soname='):
                opts['ld']['soname'] = item[len('-Wl,-soname='):]
            if item.startswith('-Wl,--out-implib='):
                opts['ld']['implname'] = item[len('-Wl,--out-implib='):]
            if item.startswith('-Wl,--output-def='):
                opts['ld']['defname'] = item[len('-Wl,--output-def='):]
        opts['common']['arm.target.fpu.hardware'] = self.get_fpu_hardware(
            opts['common']['arm.target.fpu.unit'])
        opts['common']['debugging.codecov'] = False
        if '-fprofile-arcs' in flags_in['common_flags'] and '-ftest-coverage' in flags_in['common_flags']:
            opts['common']['debugging.codecov'] = True
        # Passing linker options to linker with '-Wl,'-prefix.
        for index in range(len(opts['ld']['flags'])):
            item = opts['ld']['flags'][index]
            if not item.startswith('-Wl,'):
                opts['ld']['flags'][index] = '-Wl,' + item
        # Strange System Workbench feature: If first parameter in Other flags is a
        # define (-D...), Other flags will be replaced by defines and other flags
        # are completely ignored. Moving -D parameters to defines.
        for compiler in ['c', 'cpp', 'as']:
            tmpList = opts[compiler]['other'].split(' ')
            otherList = []
            for item in tmpList:
                if item.startswith('-D'):
                    opts[compiler]['defines'].append(str(item[2:]))
                else:
                    otherList.append(item)
            opts[compiler]['other'] = ' '.join(otherList)
        # Assembler options
        for as_def in opts['as']['defines']:
            if '=' in as_def:
                opts['as']['other'] += ' --defsym ' + as_def
            else:
                opts['as']['other'] += ' --defsym ' + as_def + '=1'

    def generate(self):
        """
        Generate the .project and .cproject files.
        """
        options = {}

        if not self.resources.linker_script:
            raise NotSupportedException("No linker script found.")

        print('\nCreate a System Workbench for STM32 managed project')
        print('Project name: {0}'.format(self.project_name))
        print('Target:       {0}'.format(self.toolchain.target.name))
        print('Toolchain:    {0}'.format(self.TOOLCHAIN) + '\n')

        self.resources.win_to_unix()

        libraries = []
        for lib in self.libraries:
            library, _ = splitext(basename(lib))
            libraries.append(library[3:])

        self.system_libraries = [
            'stdc++', 'supc++', 'm', 'c', 'gcc', 'nosys'
        ]

        profiles = self.get_all_profiles()
        self.as_defines = [s.replace('"', '&quot;')
                           for s in self.toolchain.get_symbols(True)]
        self.c_defines = [s.replace('"', '&quot;')
                          for s in self.toolchain.get_symbols()]
        self.cpp_defines = self.c_defines

        self.include_path = []
        for s in self.resources.inc_dirs:
            self.include_path.append("../" + self.filter_dot(s))
        print('Include folders: {0}'.format(len(self.include_path)))

        self.compute_exclusions()

        print('Exclude folders: {0}'.format(len(self.excluded_folders)))

        ld_script = self.filter_dot(self.resources.linker_script)
        print('Linker script:   {0}'.format(ld_script))

        lib_dirs = [self.filter_dot(s) for s in self.resources.lib_dirs]

        preproc_cmd = basename(self.toolchain.preproc[0]) + " " + " ".join(self.toolchain.preproc[1:])

        for id in ['debug', 'release']:
            opts = {}
            opts['common'] = {}
            opts['as'] = {}
            opts['c'] = {}
            opts['cpp'] = {}
            opts['ld'] = {}

            opts['id'] = id
            opts['name'] = opts['id'].capitalize()

            profile = profiles[id]

            # A small hack, do not bother with src_path again,
            # pass an empty string to avoid crashing.
            src_paths = ['']
            toolchain = prepare_toolchain(
                src_paths, "", self.toolchain.target.name, self.TOOLCHAIN, build_profile=[profile])

            # Hack to fill in build_dir
            toolchain.build_dir = self.toolchain.build_dir

            flags = self.toolchain_flags(toolchain)

            # Most GNU ARM Eclipse options have a parent,
            # either debug or release.
            if '-O0' in flags['common_flags'] or '-Og' in flags['common_flags']:
                opts['parent_id'] = 'debug'
            else:
                opts['parent_id'] = 'release'

            self.process_options(opts, flags)

            opts['c']['defines'] = self.c_defines
            opts['cpp']['defines'] = self.cpp_defines
            opts['as']['defines'] = self.as_defines

            self.process_sw_options(opts, flags)

            opts['ld']['library_paths'] = [
                self.filter_dot(s) for s in self.resources.lib_dirs]

            opts['ld']['user_libraries'] = libraries
            opts['ld']['system_libraries'] = self.system_libraries
            opts['ld']['script'] = "linker-script-" + id + ".ld"

            # Unique IDs used in multiple places.
            uid = {}
            uid['config'] = u.id
            uid['tool_c_compiler'] = u.id
            uid['tool_c_compiler_input'] = u.id
            uid['tool_cpp_compiler'] = u.id
            uid['tool_cpp_compiler_input'] = u.id

            opts['uid'] = uid

            options[id] = opts

        ctx = {
            'name': self.project_name,
            'platform': platform,
            'include_paths': self.include_path,
            'config_header': self.config_header_ref.name,
            'exclude_paths': '|'.join(self.excluded_folders),
            'ld_script': ld_script,
            'library_paths': lib_dirs,
            'object_files': self.resources.objects,
            'libraries': libraries,
            'board_name': self.BOARDS[self.target.upper()]['name'],
            'mcu_name': self.BOARDS[self.target.upper()]['mcuId'],
            'cpp_cmd': preproc_cmd,
            'options': options,
            # id property of 'u' will generate new random identifier every time
            # when called.
            'u': u
        }

        self.__gen_dir('.settings')
        self.gen_file('sw4stm32/language_settings_commom.tmpl',
                      ctx, '.settings/language.settings.xml')
        self.gen_file('sw4stm32/project_common.tmpl', ctx, '.project')
        self.gen_file('sw4stm32/cproject_common.tmpl', ctx, '.cproject')
        self.gen_file('sw4stm32/makefile.targets.tmpl', ctx,
                      'makefile.targets', trim_blocks=True, lstrip_blocks=True)
        self.gen_file('sw4stm32/launch.tmpl', ctx, self.project_name +
                      ' ' + options['debug']['name'] + '.launch')

    @staticmethod
    def clean(_):
        shutil.rmtree(".settings")
