# Author: Jan Mrázek
# License: MIT
from pathlib import Path
import sys
import os
import json
import glob
import shutil
import subprocess
import tempfile
import markdown2
from . import pybars
from datetime import datetime

def resolveTemplatePath(path):
    """
    Return a correct template path:
    - if the path matches a directory relative to working directory and the
      directory contains template.json, return that
    - otherwise treat the path as a name into the default template library.
    If none of those are template directories, raise exception.
    """
    if os.path.exists(os.path.join(path, "template.json")):
        return path
    PKG_BASE = os.path.dirname(__file__)
    TEMPLATES = os.path.join(PKG_BASE, "resources/present/templates")
    if os.path.exists(os.path.join(TEMPLATES, path, "template.json")):
        return os.path.join(TEMPLATES, path)
    raise RuntimeError("'{}' is not a name or a path for existing template. Perhaps you miss template.json in the template?".format(path))

def readTemplate(path):
    """
    Resolve template path, read the property file and return a subclass of
    Template which can render the template.
    """
    templateClasses = {
        "HtmlTemplate": HtmlTemplate
    }
    path = resolveTemplatePath(path)
    with open(os.path.join(path, "template.json")) as jsonFile:
        parameters = json.load(jsonFile)
    try:
        tType = parameters["type"]
    except KeyError:
        raise RuntimeError("Invalid template.json - missing 'type'")
    try:
        return templateClasses[tType](path)
    except KeyError:
        raise RuntimeError("Unknown template type '{}'".format(tType))

def copyRelativeTo(sourceTree, sourceFile, outputDir, dry=False):
    sourceTree = os.path.abspath(sourceTree)
    sourceFile = os.path.abspath(sourceFile)
    relPath = os.path.relpath(sourceFile, sourceTree)
    outputDir = os.path.join(outputDir, os.path.dirname(relPath))
    dest = os.path.join(outputDir, os.path.basename(sourceFile))
    if not dry:
        Path(outputDir).mkdir(parents=True, exist_ok=True)
        shutil.copy(sourceFile, outputDir)
    return dest

class Template:
    def __init__(self, directory):
        self.directory = directory
        with open(os.path.join(directory, "template.json")) as jsonFile:
            self.parameters = json.load(jsonFile)
        self.extraResources = []
        self.boards = []
        self.name = None
        self.repository = None

    def _copyResources(self, outputDirectory, dry=False):
        """
        Copy all resource files specified by template.json and further specified
        by addResource to the output directory.
        """
        files = []
        for pattern in self.parameters["resources"]:
            for path in glob.glob(os.path.join(self.directory, pattern), recursive=True):
                files.append(copyRelativeTo(self.directory, path, outputDirectory, dry))
        for pattern in self.extraResources:
            for path in glob.glob(pattern, recursive=True):
                files.append(copyRelativeTo(".", path, outputDirectory, dry))
        return files

    def listResources(self, outputDirectory):
        """
        Returns a list of resources that we will copy.
        """
        return self._copyResources(outputDirectory, dry=True)

    def addResource(self, resource):
        """
        Add a resources. Resource can be specified by a glob pattern. The files
        are treated relative to current working directory.
        """
        self.extraResources.append(resource)

    def addBoard(self, name, comment, boardfile, front, back, gerbers):
        """
        Add board
        """
        self.boards.append({
            "name": name,
            "comment": comment,
            "source": boardfile,
            "source_front": front,
            "source_back": back,
            "source_gerbers": gerbers
        })

    def _renderBoards(self, outputDirectory):
        """
        Convert all boards to images and gerber exports. Enrich self.boards
        with paths of generated files
        """
        dirPrefix = "boards"
        boardDir = os.path.join(outputDirectory, dirPrefix)
        Path(boardDir).mkdir(parents=True, exist_ok=True)
        for boardDesc in self.boards:
            boardName = os.path.basename(boardDesc["source"]).replace(".kicad_pcb", "")
            boardDesc["front"] = os.path.join(dirPrefix, boardName + "-front"+os.path.splitext(boardDesc["source_front"])[1])
            boardDesc["back"] = os.path.join(dirPrefix, boardName + "-back"+os.path.splitext(boardDesc["source_back"])[1])
            boardDesc["gerbers"] = os.path.join(dirPrefix, boardName + "-gerbers"+os.path.splitext(boardDesc["source_gerbers"])[1])
            boardDesc["file"] = os.path.join(dirPrefix, boardName + ".kicad_pcb")
            shutil.copy(boardDesc["source"], os.path.join(outputDirectory, boardDesc["file"]))
            shutil.copy(boardDesc["source_front"], os.path.join(outputDirectory, boardDesc["front"]))
            shutil.copy(boardDesc["source_back"], os.path.join(outputDirectory, boardDesc["back"]))
            shutil.copy(boardDesc["source_gerbers"], os.path.join(outputDirectory, boardDesc["gerbers"]))

    def render(self, outputDirectory):
        self._copyResources(outputDirectory)
        self._renderBoards(outputDirectory)
        self._renderPage(outputDirectory)

    def gitRevision(self):
        """
        Return a git revision string if in git repo, None otherwise
        """
        if self.git_command is None:
            return None
        proc = subprocess.run([self.git_command, "rev-parse", "HEAD"], capture_output=True)
        if proc.returncode:
            return None
        return proc.stdout.decode("utf-8")

    def currentDateTime(self):
        return datetime.now().strftime("%d. %m. %Y %H:%M")

    def setName(self, name):
        self.name = name

    def setRepository(self, rep):
        self.repository = rep


class HtmlTemplate(Template):
    def __init__(self, path):
        super().__init__(path)

    def addDescriptionFile(self, description):
        if not description.endswith(".md"):
            raise RuntimeError("Only markdown descriptions are supported for now")
        self.description = markdown2.markdown_path(description, extras=["fenced-code-blocks"])

    def _renderPage(self, outputDirectory):
        with open(os.path.join(self.directory, "index.html")) as templateFile:
            template = pybars.Compiler().compile(templateFile.read())
        gitRev = self.gitRevision()
        content = template({
            "repo": self.repository,
            "gitRev": gitRev,
            "gitRevShort": gitRev[:7] if gitRev else None,
            "datetime": self.currentDateTime(),
            "name": self.name,
            "boards": self.boards,
            "description": self.description
        })
        with open(os.path.join(outputDirectory, "index.html"),"w") as outFile:
            outFile.write(content)

def boardpage(outdir, description, board, resource, template, repository, name, git_command):
    Path(outdir).mkdir(parents=True, exist_ok=True)
    template = readTemplate(template)
    template.git_command = git_command
    template.addDescriptionFile(description)
    template.setRepository(repository)
    template.setName(name)
    for r in resource:
        template.addResource(r)
    for name, comment, file, front, back, gerbers in board:
        template.addBoard(name, comment, file, front, back, gerbers)
    template.render(outdir)
