# Copyright 2015 0xc0170
#
# 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.

import os
import logging
from collections import OrderedDict

from os.path import join, dirname, abspath, normpath
from jinja2 import Template, FileSystemLoader
from jinja2.environment import Environment

from ..util import SOURCE_KEYS

logger = logging.getLogger('progen.tools')


def get_tool_template():
    """ Internal project data

    a dict with the following keys is returned:
    source_paths - a list of source paths derived from s
    include_paths - a list of include paths derived from sources
    include_files - a dict of include files used in the copy function mapping
                    groups to the files with each group (a list)
    source_files_c - a dict of c source files mapping groups to a list of files
                     within the group
    source_files_cpp - a dict of c++ source files mapping groups to a list of
                       files within the group
    source_files_s - a dict of groups to lists of assembly source files within
                     each group
    source_files_obj - dict of groups to lists of object files within each group
    source_files_lib - a dict of libraries mapping a group to a list of
                       libraries
    singular - a Boolean value indicating whether this is a singular project or
               part of a workspace
    output_dir - a dict containing:
      path - directory that output files are generated into by the exported
             project, relative to the current directory
      rel_path - the relative path to the root
      rel_count - the number of steps to the root
    macros - a list of c pre-processor macros
    template - a filename of an external template file
    misc - a dict of miscellaneous tool options

    """

    internal_template = {
        'source_paths': [],
        'include_paths': [],
        'include_files': {},
        'source_files_c': {},
        'source_files_cpp': {},
        'source_files_s': {},
        'source_files_obj': {},
        'source_files_lib': {},
        'singular': True,
        'output_dir': {
            'path': '',
            'rel_path': '',
            'rel_count': '',
        },
        "macros": [],
        "template": None,
        "misc": {}
    }
    return internal_template


# Each new tool should at least support this Tool class methods
# and Exporter class. The build is optional as not every tool supports building
# via command line.

# Basic class to provide information about a tool like a name , toolchains
class Tool(object):
    """ Just a tool template for subclassing"""

    @staticmethod
    def get_toolnames():
        raise NotImplementedError

    @staticmethod
    def get_toolchain():
        raise NotImplementedError

# Builder class for building a project
class Builder:
    """ Just a builder template to be subclassed """

    def build_project(self, **kwargs):
        """ Should return 0 if built, otherwise return -1 """
        raise NotImplementedError

# Exporter class for exporting a project or a workspace
class Exporter(object):
    """Just an exporter template for subclassing"""

    TEMPLATE_DIR = abspath(join(dirname(__file__), '..', 'templates'))

    # Any tool which exports should implement these methods 3 methods
    def export_workspace(self):
        raise NotImplementedError

    def export_project(self):
        raise NotImplementedError

    def get_generated_project_files(self):
        raise NotImplementedError

    # Exporter supports currently 2 methods for exporting. Raw data, which can be
    # for example xml. Or jinja2 templates, not always possible to generate valid
    # xml/json/etc.. output, then jinja2 helps as it's just injects data to predefined
    # valid format. The easiest way is use raw, as we can template a project, take a valid
    # project, parse it and inject progen data into and generate a file which tool understands

    # jinja2 is quite a work for more advanced tools as there are many options and support all of
    # them requires lot of time. On the other hand, use a template file provided by a user (expecting
    # a valid template project file) is easier to implement and then we can support 100 percent of what
    # tool provides.

    def gen_file_raw(self, target_text, output, dest_path):
        if not os.path.exists(dest_path):
            os.makedirs(dest_path)
        output = join(dest_path, output)
        logger.debug("Generating: %s" % output)

        open(output, "w").write(target_text)
        return dirname(output), output

    def gen_file_jinja(self, template_file, data, output, dest_path):
        if not os.path.exists(dest_path):
            os.makedirs(dest_path)
        output = join(dest_path, output)
        logger.debug("Generating: %s" % output)

        """ Fills data to the project template, using jinja2. """
        env = Environment()
        env.loader = FileSystemLoader(self.TEMPLATE_DIR)
        # TODO: undefined=StrictUndefined - this needs fixes in templates
        template = env.get_template(template_file)
        target_text = template.render(data)

        open(output, "w").write(target_text)
        return dirname(output), output

    def fixup_executable(self, exe_path):
        return exe_path

    @staticmethod
    def is_supported_by_default(target):
        return False

    def _expand_data(self, old_data, new_data, group):
        """ data expansion - uvision needs filename and path separately. """
        for file in old_data:
            if file:
                extension = file.split(".")[-1].lower()
                if extension in self.file_types.keys():
                    new_data['groups'][group].append(self._expand_one_file(normpath(file),
                                                                           new_data, extension))
                else:
                    logger.debug("Filetype for file %s not recognized" % file)
        if hasattr(self, '_expand_sort_key'):
            new_data['groups'][group] = sorted(new_data['groups'][group],
                                               key=self._expand_sort_key)

    def _get_groups(self, data):
        """ Get all groups defined """
        groups = []
        for attribute in SOURCE_KEYS:
            for k, v in data[attribute].items():
                if k == None:
                    k = 'Sources'
                if k not in groups:
                    groups.append(k)
        for k, v in data['include_files'].items():
            if k == None:
                k = 'Includes'
            if k not in groups:
                groups.append(k)
        return groups

    def _iterate(self, data, expanded_data):
        """ _Iterate through all data, store the result expansion in extended dictionary """
        for attribute in SOURCE_KEYS:
            for k, v in data[attribute].items():
                if k == None:
                    group = 'Sources'
                else:
                    group = k
                if group in data[attribute].keys():
                    self._expand_data(data[attribute][group], expanded_data, group)
        for k, v in data['include_files'].items():
            if k == None:
                group = 'Includes'
            else:
                group = k
            self._expand_data(data['include_files'][group], expanded_data, group)

        # sort groups
        expanded_data['groups'] = OrderedDict(sorted(expanded_data['groups'].items(), key=lambda t: t[0]))
