'''
 rmtoo
   Free and Open Source Requirements Management Tool

 LaTeX output with jinja templating engine.

 (c) 2017 Kristoffer Nordstrom

 For licensing details see COPYING
'''
from __future__ import unicode_literals

import io
import jinja2
from six import iteritems

from rmtoo.lib.Constraints import Constraints
from rmtoo.lib.TestCases import collect
from rmtoo.lib.StdOutputParams import StdOutputParams
from rmtoo.lib.ExecutorTopicContinuum import ExecutorTopicContinuum
from rmtoo.lib.logging import tracer
from rmtoo.lib.CreateMakeDependencies import CreateMakeDependencies


class LatexJinja2(StdOutputParams, ExecutorTopicContinuum,
                  CreateMakeDependencies):
    default_config = {"req_attributes":
                      ["Id", "Priority", "Owner", "Invented on",
                       "Invented by", "Status", "Class"]}

    level_names = [
        "chapter",
        "section",
        "subsection",
        "subsubsection",
        "subsubsubsection",
    ]

    def __init__(self, oconfig):
        '''Create a graph output object.'''
        tracer.info("Called.")
        StdOutputParams.__init__(self, oconfig)
        CreateMakeDependencies.__init__(self)
        self.__ce3set = None
        self.__fd = None
        self.__constraints_reqs_ref = {}
        self.__testcases = None

        # Jinja2 initialisation
        template_loader = jinja2.FileSystemLoader(
                searchpath=oconfig['template_path'])
        template_env_unmodded = jinja2.Environment(loader=template_loader)
        self._template_env = template_env_unmodded.overlay(
            block_start_string='((*',
            block_end_string='*))',
            variable_start_string='(((',
            variable_end_string=')))',
            comment_start_string='((=',
            comment_end_string='=))')

        if not self._config.is_available('req_attributes'):
            self._config.set_value(
                'req_attributes',
                ["Id", "Priority", "Owner", "Invented on",
                 "Invented by", "Status", "Class"])
        self.__level = -1

    @staticmethod
    def __strescape(string):
        '''Escapes a string: hexifies it.'''
        result = ""
        for fchar in string:
            if ord(fchar) >= 32 and ord(fchar) < 127:
                result += fchar
            else:
                result += "%02x" % ord(fchar)
        return result

    def topic_set_pre(self, _topics_set):
        '''Prepare the output file.'''
        self.__fd = io.open(self._output_filename, "w", encoding="utf-8")

    def __output_latex_one_constraint(self, cname, cnstrt):
        '''Output one constraint.'''
        cname = LatexJinja2.__strescape(cname)
        tracer.debug("Output constraint [%s]." % cname)
        self.__fd.write(u"%% CONSTRAINT '%s'\n" % cname)

        self.__fd.write(u"\\%s{%s}\\label{CONSTRAINT%s}\n"
                        "\\textbf{Description:} %s\n"
                        % (self.level_names[1],
                           cnstrt.get_value("Name").get_content(),
                           cname, cnstrt.get_value(
                               "Description").get_content()))

        if cnstrt.is_val_av_and_not_null("Rationale"):
            self.__fd.write(u"\n\\textbf{Rationale:} %s\n"
                            % cnstrt.get_value("Rationale").get_content())

        if cnstrt.is_val_av_and_not_null("Note"):
            self.__fd.write(u"\n\\textbf{Note:} %s\n"
                            % cnstrt.get_value("Note").get_content())

        # Write out the references to the requirements

        reqs_refs = []
        for req in self.__constraints_reqs_ref[cname]:
            refid = LatexJinja2.__strescape(req)
            refctr = "\\ref{%s} \\nameref{%s}" \
                     % (refid, refid)
            reqs_refs.append(refctr)
        self.__fd.write(u"\n\\textbf{Requirements:} %s\n" %
                        ", ".join(reqs_refs))

        tracer.debug("Finished.")

    def __output_latex_constraints(self, constraints):
        '''Write out all constraints for the topic set.'''
        if len(constraints) == 0:
            tracer.debug("No constraints to output.")
            return

        self.__fd.write(u"\\%s{Constraints}\n" % self.level_names[0])
        for cname, cnstrt in sorted(iteritems(constraints)):
            self.__output_latex_one_constraint(cname, cnstrt)

    # TODO: Code duplication from constraints
    def __output_latex_one_testcase(self, cname, cnstrt):
        '''Output one testcase.'''
        cname = LatexJinja2.__strescape(cname)
        tracer.debug("Output testcase [%s]." % cname)
        self.__fd.write(u"%% TEST-CASE '%s'\n" % cname)

        self.__fd.write(u"\\%s{%s}\\label{TESTCASE%s}\n"
                        "\\hypertarget{TESTCASE%s}{}"
                        "\\textbf{Description:} %s\n"
                        % (self.level_names[1],
                           cnstrt.get_value("Name").get_content(),
                           cnstrt.get_value("Name").get_content(),
                           cname, cnstrt.get_value(
                               "Description").get_content()))

        if cnstrt.is_val_av_and_not_null("Expected Result"):
            self.__fd.write(u"\n\\textbf{Expected Result:} %s\n"
                            % cnstrt.get_value(
                                "Expected Result").get_content())

        if cnstrt.is_val_av_and_not_null("Rationale"):
            self.__fd.write(u"\n\\textbf{Rationale:} %s\n"
                            % cnstrt.get_value("Rationale").get_content())

        if cnstrt.is_val_av_and_not_null("Note"):
            self.__fd.write(u"\n\\textbf{Note:} %s\n"
                            % cnstrt.get_value("Note").get_content())
        tracer.debug("Finished.")

    def __output_latex_testcases(self, testcases):
        '''Write out all testcases for the topic set.'''
        if not len(testcases):
            tracer.debug("No testcases to output.")
            return

        self.__fd.write(u"\\%s{Test Cases}\n" % self.level_names[0])
        for cname, cnstrt in sorted(iteritems(testcases)):
            self.__output_latex_one_testcase(cname, cnstrt)

    def topic_set_post(self, topic_set):
        '''Print out the constraints and clean up file.'''
        tracer.debug("Called; output constraints.")
        assert topic_set is not None
        constraints = Constraints.collect(topic_set)
        self.__output_latex_constraints(constraints)
        testcases = collect(topic_set)
        self.__output_latex_testcases(testcases)
        tracer.debug("Clean up file.")
        self.__fd.close()
        tracer.debug("Finished.")

    def topic_pre(self, topic):
        '''Output one topic.'''
        self.__level += 1
        self.__fd.write(u"%% Output topic '%s'\n" % topic.name)

    def topic_post(self, _topic):
        '''Cleanup things for topic.'''
        self.__level -= 1

    def topic_name(self, name):
        '''Output the topic name.'''
        req_template = self._template_env.get_template("topicName.tex")
        template_vars = {'level': self.__level, 'name': name}
        self.__fd.write(req_template.render(template_vars))

    def topic_text(self, text):
        '''Write out the given text.'''
        req_template = self._template_env.get_template("topicText.tex")
        template_vars = {'text': text}
        self.__fd.write(req_template.render(template_vars))

    def requirement_set_pre(self, rset):
        '''Prepare the requirements set output.'''
        self.__ce3set = rset.get_ce3set()
        self.__testcases = rset.get_testcases()

    def requirement_set_sort(self, list_to_sort):
        '''Sort by id.'''
        return sorted(list_to_sort, key=lambda r: r.get_id())

    def __add_constraint_req_ref(self, constraint, requirement):
        if constraint not in self.__constraints_reqs_ref:
            self.__constraints_reqs_ref[constraint] = []
        self.__constraints_reqs_ref[constraint].append(requirement)

    def requirement(self, req):
        self.__fd.write(self._get_requirement(req))

    def _get_requirement(self, req):
        '''Write out one requirement.'''
        req_template = self._template_env.get_template("singleReq.tex")
        template_vars = (
            {'req_id': self.__strescape(req.get_id()),
             'name':  req.get_value("Name").get_content(),
             'description':  req.get_value("Description").get_content(),
             'req_status': req.get_status().get_output_string(),
             'hash': req.get_hash()}
        )

        if req.is_val_av_and_not_null("Rationale"):
            template_vars['rationale'] = (
                req.get_value("Rationale").get_content()
            )
        if req.is_val_av_and_not_null("Note"):
            template_vars['note'] = (
                req.get_value("Note").get_content()
            )

        if len(req.outgoing) > 0:
            # Create links to the corresponding dependency nodes.
            inc = [d.get_id() for d in
                   sorted(req.outgoing, key=lambda r: r.get_id())]
            template_vars['solvedby'] = inc

        if len(req.incoming) > 0:
            # Only output the depends on when there are fields for output.
            inc = [d.get_id() for d in
                   sorted(req.incoming, key=lambda r: r.get_id())]
            template_vars['dependson'] = inc

        try:
            template_vars['status'] = (
                    req.get_status().get_output_string())
        except KeyError:
            pass
        try:
            template_vars['clstr'] = (
                    req.get_value("Class").get_output_string())
        except KeyError:
            pass
        try:
            template_vars['rtype'] = req.get_value("Type").as_string()
        except KeyError:
            pass
        try:
            template_vars['prio'] = req.get_value("Priority") * 10
        except KeyError:
            pass
        try:
            template_vars['owner'] = req.get_value("Owner")
        except KeyError:
            pass
        try:
            template_vars['inventedon'] = (
                    req.get_value("Invented on").strftime("%Y-%m-%d"))
        except KeyError:
            pass
        try:
            template_vars['inventedby'] = req.get_value("Invented by")
        except KeyError:
            pass

        # The following has not been ported yet (TODO)
        if self.__ce3set is not None:
            cnstrt = self.__ce3set.get(req.get_id())
            if cnstrt is not None and len(cnstrt) > 0:
                raise NotImplementedError(
                        'Not yet defined, use latex2 output instead!')
                self.__fd.write(u"\n\\textbf{Constraints:} ")
                cstrs = []
                for key, val in sorted(iteritems(cnstrt)):
                    refid = LatexJinja2.__strescape(key)
                    refctr = "\\ref{CONSTRAINT%s} \\nameref{CONSTRAINT%s}" \
                             % (refid, refid)
                    description = val.description()
                    if description is not None:
                        refctr += " [" + description + "] "
                    cstrs.append(refctr)
                    # Also put a reference (for later use) in the
                    # constraints to requirements ref.
                    self.__add_constraint_req_ref(refid, req.get_id())

                self.__fd.write(u", ".join(cstrs))
                self.__fd.write(u"\n")

        testcases = req.get_value_default("Test Cases")
        if testcases is not None:
            inc = [LatexJinja2.__strescape(testcase)
                   for testcase in testcases]
            template_vars['testcases'] = inc

        return req_template.render(template_vars)

    def cmad_topic_continuum_pre(self, _):
        '''Write out the one and only dependency to all the requirements.'''
        tracer.debug("Called.")
        CreateMakeDependencies.write_reqs_dep(self._cmad_file,
                                              self._output_filename)
        self._cmad_file.write(u"REQS_LATEX2=%s\n" %
                              self._output_filename)
