from __future__ import print_function, absolute_import
from builtins import str

import os
from os.path import sep, join, exists
from collections import namedtuple
from subprocess import Popen, PIPE
import shutil
import re
import sys

from tools.targets import TARGET_MAP
from tools.export.exporters import Exporter, TargetNotSupportedException
import json
from tools.export.cmsis import DeviceCMSIS
from tools.utils import NotSupportedException
from multiprocessing import cpu_count


def _supported(mcu, iar_targets):
    if "IAR" not in mcu.supported_toolchains:
        return False
    if hasattr(mcu, 'device_name') and mcu.device_name in iar_targets:
        return True
    if mcu.name in iar_targets:
        return True
    return False


_iar_defs = os.path.join(
    os.path.dirname(os.path.abspath(__file__)), 'iar_definitions.json')

with open(_iar_defs, 'r') as f:
    _GUI_OPTIONS = json.load(f)


class IAR(Exporter):
    NAME = 'iar'
    TOOLCHAIN = 'IAR'

    @classmethod
    def is_target_supported(cls, target_name):
        target = TARGET_MAP[target_name]
        return _supported(target, _GUI_OPTIONS.keys())


    def iar_groups(self, grouped_src):
        """Return a namedtuple of group info
        Positional Arguments:
        grouped_src: dictionary mapping a group(str) to sources
            within it (list of file names)
        Relevant part of IAR template
        {% for group in groups %}
	    <group>
	        <name>group.name</name>
	        {% for file in group.files %}
	        <file>
	        <name>$PROJ_DIR${{file}}</name>
	        </file>
	        {% endfor %}
	    </group>
	    {% endfor %}
        """
        IARgroup = namedtuple('IARgroup', ['name','files'])
        groups = []
        for name, files in grouped_src.items():
            groups.append(IARgroup(name,files))
        return groups

    def iar_device(self):
        """Retrieve info from iar_definitions.json"""
        tgt = TARGET_MAP[self.target]
        device_name = (tgt.device_name if hasattr(tgt, "device_name") else
                       tgt.name)
        device_info = _GUI_OPTIONS[device_name]
        iar_defaults ={
            "OGChipSelectEditMenu": "",
            "CoreVariant": '',
            "GFPUCoreSlave": '',
            "GFPUCoreSlave2": 40,
            "GBECoreSlave": 35,
            "GBECoreSlave2": '',
            "FPU2": 0,
            "NrRegs": 0,
            "NEON": '',
            "CExtraOptionsCheck": 0,
            "CExtraOptions": "",
            "CMSISDAPJtagSpeedList": 0,
        }

        iar_defaults.update(device_info)
        IARdevice = namedtuple('IARdevice', iar_defaults.keys())
        return IARdevice(**iar_defaults)

    def format_file(self, file):
        """Make IAR compatible path"""
        return join('$PROJ_DIR$',file)

    def format_src(self, srcs):
        """Group source files"""
        grouped = self.group_project_files(srcs)
        for group, files in grouped.items():
            grouped[group] = [self.format_file(src) for src in files]
        return grouped

    def generate(self):
        """Generate the .eww, .ewd, and .ewp files"""
        if not self.resources.linker_script:
            raise NotSupportedException("No linker script found.")
        srcs = self.resources.headers + self.resources.s_sources + \
               self.resources.c_sources + self.resources.cpp_sources + \
               self.resources.objects + self.libraries
        flags = self.flags
        c_flags = list(set(flags['common_flags']
                           + flags['c_flags']
                           + flags['cxx_flags']))
        # Flags set in template to be set by user in IDE
        template = ["--vla", "--no_static_destruction"]
        # Flag invalid if set in template
        # Optimizations are also set in template
        invalid_flag = lambda x: x in template or re.match("-O(\d|time|n|hz?)", x)
        flags['c_flags'] = [flag for flag in c_flags if not invalid_flag(flag)]

        try:
            debugger = DeviceCMSIS(self.target).debug.replace('-','').upper()
        except TargetNotSupportedException:
            debugger = "CMSISDAP"

        ctx = {
            'name': self.project_name,
            'groups': self.iar_groups(self.format_src(srcs)),
            'linker_script': self.format_file(self.resources.linker_script),
            'include_paths': [self.format_file(src) for src in self.resources.inc_dirs],
            'device': self.iar_device(),
            'ewp': sep+self.project_name + ".ewp",
            'debugger': debugger,
        }
        ctx.update(flags)

        self.gen_file('iar/eww.tmpl', ctx, self.project_name + ".eww")
        self.gen_file('iar/ewd.tmpl', ctx, self.project_name + ".ewd")
        self.gen_file('iar/ewp.tmpl', ctx, self.project_name + ".ewp")

    @staticmethod
    def clean(project_name):
        os.remove(project_name + ".ewp")
        os.remove(project_name + ".ewd")
        os.remove(project_name + ".eww")
        # legacy output file location
        if exists('.build'):
            shutil.rmtree('.build')
        if exists('BUILD'):
            shutil.rmtree('BUILD')

    @staticmethod
    def build(project_name, log_name="build_log.txt", cleanup=True):
        """ Build IAR project """
        # > IarBuild [project_path] -build [project_name]
        proj_file = project_name + ".ewp"
        cmd = ["IarBuild", proj_file, '-build', project_name]

        # IAR does not support a '0' option to automatically use all
        # available CPUs, so we use Python's multiprocessing library
        # to detect the number of CPUs available
        cpus_available = cpu_count()
        jobs = cpus_available if cpus_available else None

        # Only add the parallel flag if we're using more than one CPU
        if jobs:
            cmd += ['-parallel', str(jobs)]

        # Build the project
        p = Popen(cmd, stdout=PIPE, stderr=PIPE)
        out, err = p.communicate()
        ret_code = p.returncode

        out_string = "=" * 10 + "STDOUT" + "=" * 10 + "\n"
        out_string += out
        out_string += "=" * 10 + "STDERR" + "=" * 10 + "\n"
        out_string += err

        if ret_code == 0:
            out_string += "SUCCESS"
        else:
            out_string += "FAILURE"

        print(out_string)

        if log_name:
            # Write the output to the log file
            with open(log_name, 'w+') as f:
                f.write(out_string)

        # Cleanup the exported and built files
        if cleanup:
            IAR.clean(project_name)

        if ret_code !=0:
            # Seems like something went wrong.
            return -1
        else:
            return 0
