from docutils import nodes
from docutils.parsers.rst import Directive
import json
import logging
from os import path, makedirs
from shutil import copytree, rmtree
from sphinx.application import Sphinx
from sphinx.locale import _
from typing import Optional

from hwt.synthesizer.unit import Unit
from hwt.synthesizer.utils import synthesised
from hwtGraph.elk.containers.idStore import ElkIdStore
from hwtGraph.elk.fromHwt.convertor import UnitToLNode
from hwtGraph.elk.fromHwt.defauts import DEFAULT_PLATFORM, \
    DEFAULT_LAYOUT_OPTIMIZATIONS
from sphinx_hwt.utils import get_absolute_name_of_class_of_node, \
    get_constructor_name, construct_hwt_obj


class SchematicPaths():
    SCHEMATIC_VIEWER_URL = "schematic_viewer/schematic_viewer.html"
    SCHEMATIC_DIR_PREFIX = "../../"  # relative path from SCHEMATIC_VIEWER_URL
    SCHEMATIC_FILES_DIR = "hwt_schematics"  # path relative to static dir
    SCHEMATIC_FILES_EXTENSION = "json"
    SCHEMATIC_VIEWER_SRC_DIR = path.join(path.dirname(__file__), "html")

    @classmethod
    def get_sch_file_name_absolute(cls, document, absolute_name, serialno):
        return path.join(document.settings.env.app.builder.outdir,
                         cls.get_sch_file_name(document, absolute_name, serialno))

    @classmethod
    def get_static_path(cls, document):
        return cls.get_static_path_from_env(document.settings.env)

    @classmethod
    def get_static_path_from_env(cls, env):
        static_paths = env.config.html_static_path
        if not static_paths:
            return "_static"
        return static_paths[0]

    @classmethod
    def get_sch_file_name(cls, document, absolute_name, serialno):
        sp = cls.get_static_path(document)
        return "%s-%s.%s" % (
            path.join(sp, cls.SCHEMATIC_FILES_DIR, absolute_name),
            serialno,
            cls.SCHEMATIC_FILES_EXTENSION)

    @classmethod
    def get_sch_viewer_link(cls, document):
        return path.join(cls.get_static_path(document),
                         cls.SCHEMATIC_VIEWER_URL)

    @classmethod
    def get_sch_viewer_dir(cls, env):
        return path.join(env.app.builder.outdir,
                         SchematicPaths.get_static_path_from_env(env),
                         "schematic_viewer")


class SchematicLink(nodes.General, nodes.Inline, nodes.TextElement):

    def __init__(self, constructor_fn_name: Optional[str], *args, **kwargs):
        """
        :param constructor_fn_name: optional name of explicit constructor function
        """
        super(SchematicLink, self).__init__(*args, **kwargs)
        self["constructor_fn_name"] = constructor_fn_name

    @staticmethod
    def visit_html(self, node):
        """
        Generate html elements and schematic json
        """
        absolute_name = get_absolute_name_of_class_of_node(node)
        constructor_fn_name = node["constructor_fn_name"]
        serialno = node["serialno"]

        try:
            schem_file = SchematicPaths.get_sch_file_name_absolute(
                self.document, absolute_name, serialno)
            makedirs(path.dirname(schem_file), exist_ok=True)
            u = construct_hwt_obj(absolute_name, constructor_fn_name, Unit, "hwt-schematic")
            with open(schem_file, "w") as f:
                synthesised(u, DEFAULT_PLATFORM)
                g = UnitToLNode(u, optimizations=DEFAULT_LAYOUT_OPTIMIZATIONS)
                idStore = ElkIdStore()
                data = g.toElkJson(idStore)
                json.dump(data, f)

            viewer = SchematicPaths.get_sch_viewer_link(self.document)
            sch_name = SchematicPaths.get_sch_file_name(
                self.document, absolute_name, serialno)
            ref = nodes.reference(text=_("schematic"),  # internal=False,
                                  refuri="%s?schematic=%s" % (
                                      viewer,
                                      path.join(SchematicPaths.SCHEMATIC_DIR_PREFIX,
                                                sch_name)))
            node += ref
        except Exception as e:
            logging.error(e, exc_info=True)
            raise Exception(
                f"Error occured while processing of {absolute_name:s}")

    @staticmethod
    def depart_html(self, node):
        pass


class HwtSchematicDirective(Directive):
    optional_arguments = 1
    final_argument_whitespace = False
    has_content = False

    def run(self):
        # build dir path
        # https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/sphinxext/plot_directive.py#L699
        constructor_fn_name = get_constructor_name(self)
        env = self.state.document.settings.env
        serialno = env.new_serialno('SchematicLink')
        schema_node = SchematicLink(constructor_fn_name=constructor_fn_name,
                                    serialno=serialno)

        self.state.nested_parse(self.content,
                                self.content_offset,
                                schema_node)

        return [schema_node, ]


def copy_scheme_viewer_static_files(app: Sphinx, env, docnames):
    viewer_dir = SchematicPaths.get_sch_viewer_dir(env)
    if path.exists(viewer_dir):
        rmtree(viewer_dir)

    copytree(SchematicPaths.SCHEMATIC_VIEWER_SRC_DIR, viewer_dir)


def setup(app: Sphinx):
    app.add_node(SchematicLink,
                 html=(SchematicLink.visit_html,
                       SchematicLink.depart_html))
    app.add_directive('hwt-schematic', HwtSchematicDirective)
    app.connect('env-before-read-docs', copy_scheme_viewer_static_files)
