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.resources.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 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:
            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')

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


