from pathlib import Path
import os
import globals
from distutils.dir_util import copy_tree
import shutil
from bson import json_util, ObjectId
import io
import pymongo
import re
from flask_restful import Resource
from webargs.flaskparser import use_kwargs
from webargs import fields
from src import auth
from marshmallow import Schema
from fontTools import ttLib
from contextlib import redirect_stderr
from zipfile import ZipFile, ZIP_DEFLATED

fileOutputFolder = ""
fileRedactedFolder = ""
fileOutputImageFolder = ""
fileOutputFontFolder = ""
fileOutputStylesFolder = ""
fileOutputTextFolder = ""
SAMPLEXHTML = ""
SAMPLECSS = ""
fileOutputOPFFile = ""
fileOutputTocNcxFile = ""
fileOutputTocFile = ""
SAMPLEXHTMLTEXT = ""
SAMPLECSSTEXT = ""
fileOutputBodtFile = ""


# COPY TEMPLATE TO STATIC FOLDER
def templateCopy(fileID):
    global fileOutputFolder
    global fileRedactedFolder
    global fileOutputImageFolder
    global fileOutputFontFolder
    global fileOutputStylesFolder
    global fileOutputTextFolder
    global SAMPLEXHTML
    global SAMPLECSS
    global fileOutputOPFFile
    global fileOutputTocNcxFile
    global fileOutputTocFile
    global SAMPLEXHTMLTEXT
    global SAMPLECSSTEXT
    global fileOutputBodtFile

    fileOutputFolder = os.path.join(globals.FILES_FOLDER, fileID, "output")
    fileRedactedFolder = os.path.join(globals.FILES_FOLDER, fileID, "redacted")
    fileOutputImageFolder = os.path.join(globals.FILES_FOLDER, fileID, "output", "EPUB", "Images")
    fileOutputFontFolder = os.path.join(globals.FILES_FOLDER, fileID, "output", "EPUB", "Fonts")
    fileOutputStylesFolder = os.path.join(globals.FILES_FOLDER, fileID, "output", "EPUB", "Styles")
    fileOutputTextFolder = os.path.join(globals.FILES_FOLDER, fileID, "output", "EPUB", "Text")

    SAMPLEXHTML = os.path.join(fileOutputTextFolder, "SAMPLE.xhtml")
    SAMPLECSS = os.path.join(fileOutputStylesFolder, "SAMPLE.css")

    fileOutputOPFFile = os.path.join(globals.FILES_FOLDER, fileID, "output", "EPUB", "package.opf")
    fileOutputTocNcxFile = os.path.join(globals.FILES_FOLDER, fileID, "output", "EPUB", "toc.ncx")
    fileOutputTocFile = os.path.join(globals.FILES_FOLDER, fileID, "output", "EPUB", "TOC.xhtml")

    fileOutputBodtFile = os.path.join(fileOutputStylesFolder, "body.css")

    Path(fileOutputFolder).mkdir(parents=True, exist_ok=True)
    Path(fileOutputFontFolder).mkdir(parents=True, exist_ok=True)
    Path(fileOutputStylesFolder).mkdir(parents=True, exist_ok=True)
    Path(fileOutputTextFolder).mkdir(parents=True, exist_ok=True)

    copy_tree(globals.TEMPLATE_FOLDER, fileOutputFolder)

    # COPY REDACTED IMAGES
    copy_tree(fileRedactedFolder, fileOutputImageFolder)


auth_args = {"Authorization": fields.Str(required=True)}


def valueReplacer(PageNum, pageCSS, pageImg, text, Properties, width, height, TotalPages):
    matches = re.finditer(r"{{(\w+)}}", text, re.MULTILINE)
    for matchNum, match in enumerate(matches, start=1):
        for groups in match.groups():
            if groups == "WIDTH":
                value = width

            elif groups == "HEIGHT":
                value = height

            elif groups == "FILE_REL_CSS":
                value = re.sub(r"(.+)Styles", "../Styles", pageCSS.replace("\\", "/"), 0, re.MULTILINE)

            elif groups == "TITLEPAGE_REL_PATH":
                titlepageList = list(filter(lambda d: d['key'] in "TITLEPAGE_NO", Properties))
                titlepageNum = titlepageList[0]["value"] if len(titlepageList) > 0 else ""
                value = "Text/" + f"page_{str(int(titlepageNum) - 1).zfill(len(str(TotalPages)))}.xhtml"

            elif groups == "CHAPTER1_REL_PATH":
                chapter1List = list(filter(lambda d: d['key'] in "CHAPTER1_NO", Properties))
                chapter1Num = chapter1List[0]["value"] if len(chapter1List) > 0 else ""
                value = "Text/" + f"page_{str(int(chapter1Num) - 1).zfill(len(str(TotalPages)))}.xhtml"

            elif groups == "COPYRIGHT_REL_PATH":
                copyrightList = list(filter(lambda d: d['key'] in "COPYRIGHT_NO", Properties))
                copyrightNum = copyrightList[0]["value"] if len(copyrightList) > 0 else ""
                value = "Text/" + f"page_{str(int(copyrightNum) - 1).zfill(len(str(TotalPages)))}.xhtml"

            elif groups == "COVER_REL_PATH":
                value = "Text/cover.xhtml"

            elif groups == "TOTALPAGES":
                value = TotalPages

            elif groups == "RESOLUTION":
                value = str(width) + "x" + str(height)

            elif groups == "IMAGE_URL":
                value = re.sub(r"(.+)Images", "../Images", pageImg.replace("\\", "/"), 0, re.MULTILINE)

            elif groups == "PAGENUM":
                value = int(PageNum) - 1
                if value == 0:
                    value = "cover"
                else:
                    value = str(value).zfill(len(str(TotalPages)))
            elif groups == "BODY_REL_CSS":
                value = "../Styles/body.css"
            else:
                valueList = list(filter(lambda d: d['key'] in groups, Properties))
                value = valueList[0]["value"] if len(valueList) > 0 else ""
            if value != "":
                text = text.replace("{{" + str(groups) + "}}", str(value))
    return text


def font_style(font_path):
    font = ttLib.TTFont(font_path, ignoreDecompileErrors=True)
    with redirect_stderr(None):
        names = font['name'].names

    details = {}
    for x in names:
        if x.langID == 0 or x.langID == 1033:
            try:
                details[x.nameID] = str.lower(x.toUnicode())
            except UnicodeDecodeError:
                details[x.nameID] = str.lower(x.string.decode(errors='ignore'))

    return details


def suportingFilesCreation(Properties, width, height, Fonts, TotalPages):
    global fileOutputFolder
    global fileRedactedFolder
    global fileOutputImageFolder
    global fileOutputFontFolder
    global fileOutputStylesFolder
    global fileOutputTextFolder
    global SAMPLEXHTML
    global SAMPLECSS
    global fileOutputOPFFile
    global fileOutputTocNcxFile
    global fileOutputTocFile
    global SAMPLEXHTMLTEXT
    global SAMPLECSSTEXT
    global fileOutputBodtFile
    # TOC.ncx
    with io.open(fileOutputTocNcxFile, 'r', encoding='utf8') as f:
        tocNcxText = f.read()

    tocNcxText = valueReplacer("", "", "", tocNcxText, Properties, width, height, TotalPages)

    with io.open(fileOutputTocNcxFile, 'w', encoding='utf8', newline='\n') as f:
        f.write(tocNcxText.replace("../Text", "Text"))

    # TOC.xHTML
    with io.open(fileOutputTocFile, 'r', encoding='utf8') as f:
        tocXhtmlText = f.read()

    tocXhtmlText = valueReplacer("", "", "", tocXhtmlText, Properties, width, height, TotalPages)

    with io.open(fileOutputTocFile, 'w', encoding='utf8', newline='\n') as f:
        f.write(tocXhtmlText.replace("../Text", "Text"))

    # PACKAGE.OPF
    with io.open(fileOutputOPFFile, 'r', encoding='utf8') as f:
        opfFileText = f.read()

    imgsTemplate = '<item id="{{IMG_ID}}" href="{{IMG_REL_PATH}}" media-type="image/jpeg"/>'
    bodysTemplate = '<item id="BODY{{ID}}" href="{{TEXT_REL_PATH}}" media-type="application/xhtml+xml"/>'
    fontsTemplate = '<item id="font{{FONT_ID}}" href="{{FONT_REL_PATH}}" media-type="application/vnd.ms-opentype"/>'
    bodyRefsTemplate = '<itemref idref="BODY{{ID}}" linear="yes"/>'
    csssTemplate = '<item href="{{CSS_REL_PATH}}" id="{{CSS_FILENAME}}" media-type="text/css" />'

    csss = []
    bodys = []
    imgs = []
    bodyRefs = []
    fonts = []

    for i in range(1, TotalPages + 1):
        coverPage = "cover" if i == 1 else str(i).zfill(len(str(TotalPages)))
        No_of_the_pages = str(i).zfill(len(str(TotalPages)))
        if coverPage == "cover":
            csss.append(csssTemplate
                        .replace("{{CSS_REL_PATH}}", "Styles/body.css")
                        .replace("{{CSS_FILENAME}}", "body.css"))
            csss.append(csssTemplate
                        .replace("{{CSS_REL_PATH}}", "Styles/" + f"{coverPage}.css")
                        .replace("{{CSS_FILENAME}}", f"{coverPage}.css"))

            imgs.append(imgsTemplate
                        .replace("{{IMG_ID}}", "cover-image")
                        .replace("{{IMG_REL_PATH}}", "Images/" + f"{coverPage}.jpg"))
            bodys.append(bodysTemplate
                         .replace("{{ID}}", f"{No_of_the_pages}")
                         .replace("{{TEXT_REL_PATH}}", "Text/" + f"{coverPage}.xhtml"))
        else:
            csss.append(csssTemplate
                        .replace("{{CSS_REL_PATH}}",
                                 "Styles/" + f"page_{str(int(coverPage) - 1).zfill(len(str(TotalPages)))}.css")
                        .replace("{{CSS_FILENAME}}", f"page_{str(int(coverPage) - 1).zfill(len(str(TotalPages)))}.css"))

            imgs.append(imgsTemplate
                        .replace("{{IMG_ID}}", f"img{coverPage}")
                        .replace("{{IMG_REL_PATH}}",
                                 "Images/" + f"page_{str(int(coverPage) - 1).zfill(len(str(TotalPages)))}.jpg"))
            bodys.append(bodysTemplate
                         .replace("{{ID}}", f"{No_of_the_pages}")
                         .replace("{{TEXT_REL_PATH}}",
                                  "Text/" + f"page_{str(int(coverPage) - 1).zfill(len(str(TotalPages)))}.xhtml"))

        bodyRefs.append(bodyRefsTemplate
                        .replace("{{ID}}", f"{No_of_the_pages}"))

    for i in range(0, len(Fonts)):
        if "\\" in str(Fonts[i]["fontPath"]):
            fontName = str(Fonts[i]["fontPath"]).split("\\")[-1]
        else:
            fontName = str(Fonts[i]["fontPath"]).split("/")[-1]
        fontOutputPath = os.path.join(fileOutputFontFolder, fontName)
        fontPath = os.path.join(globals.FONT_FOLDER, fontName)
        shutil.copy2(fontPath, fontOutputPath)
        Fonts[i]["FONT_REL_PATH"] = fontOutputPath.split("output\\EPUB\\")[-1].replace("\\", "/")
        fonts.append(fontsTemplate
                     .replace("{{FONT_ID}}", str(i + 1))
                     .replace("{{FONT_REL_PATH}}", Fonts[i]["FONT_REL_PATH"]))

    opfFileText = opfFileText \
        .replace("<csss/>", "\n".join(csss)) \
        .replace("<imgs/>", "\n".join(imgs)) \
        .replace("<bodys/>", "\n".join(bodys)) \
        .replace("<bodyrefs/>", "\n".join(bodyRefs)) \
        .replace("<fonts/>", "\n".join(fonts))

    opfFileText = valueReplacer("", "", "", opfFileText, Properties, width, height, TotalPages)

    with io.open(fileOutputOPFFile, 'w', encoding='utf8', newline='\n') as f:
        f.write(opfFileText.replace("../Text", "Text"))

    # Body.css

    with io.open(fileOutputBodtFile, 'r', encoding='utf8') as f:
        bodyCssText = f.read()

    fontsCssTemplate = "@font-face {font-family: \"{{FONT_NAME}}\";font-weight: {{FONT_WEIGHT}}; font-style: {{FONT_STYPE}}; src: url({{FONT_URL}});}"
    fontData = []

    for font in Fonts:
        tempFontData = fontsCssTemplate.replace("{{FONT_URL}}", f'../{font["FONT_REL_PATH"]}').replace("{{FONT_NAME}}",font["fontName"])
        style = font_style(font['fontPath'])

        if "bold" in style.values() and "italic" in style.values():
            tempFontData = tempFontData.replace("{{FONT_WEIGHT}}", "bold").replace("{{FONT_STYPE}}", "italic")
        elif "bold" in str.lower(font["cssFontName"]) and "italic" in str.lower(font["cssFontName"]):
            tempFontData = tempFontData.replace("{{FONT_WEIGHT}}", "bold").replace("{{FONT_STYPE}}", "italic")
        elif "bold" in style.values():
            tempFontData = tempFontData.replace("{{FONT_WEIGHT}}", "bold").replace("{{FONT_STYPE}}", "normal")
        elif "bold" in str.lower(font["cssFontName"]):
            tempFontData = tempFontData.replace("{{FONT_WEIGHT}}", "bold").replace("{{FONT_STYPE}}", "normal")
        elif "italic" in style.values():
            tempFontData = tempFontData.replace("{{FONT_STYPE}}", "italic").replace("{{FONT_WEIGHT}}", "normal")
        elif "italic" in str.lower(font["cssFontName"]):
            tempFontData = tempFontData.replace("{{FONT_STYPE}}", "italic").replace("{{FONT_WEIGHT}}", "normal")
        else:
            tempFontData = tempFontData.replace("{{FONT_WEIGHT}}", "normal").replace("{{FONT_STYPE}}", "normal")

        fontData.append(tempFontData)

    bodyCssText = valueReplacer("", "", "", bodyCssText, Properties, width, height, TotalPages).replace("{{FONTS_CSS}}",
                                                                                                        "\n".join(fontData))

    with io.open(fileOutputBodtFile, 'w', encoding='utf8', newline='\n') as f:
        f.write(bodyCssText)


def xhtmlAndCssCreation(Properties, Fonts, TotalPages, Pages):
    global fileOutputFolder
    global fileRedactedFolder
    global fileOutputImageFolder
    global fileOutputFontFolder
    global fileOutputStylesFolder
    global fileOutputTextFolder
    global SAMPLEXHTML
    global SAMPLECSS
    global fileOutputOPFFile
    global fileOutputTocNcxFile
    global fileOutputTocFile
    global SAMPLEXHTMLTEXT
    global SAMPLECSSTEXT
    global fileOutputBodtFile

    # GENERATING TEXT AND CSS FILES
    with io.open(SAMPLEXHTML, 'r', encoding='utf8') as f:
        SAMPLEXHTMLTEXT = f.read()

    with io.open(SAMPLECSS, 'r', encoding='utf8') as f:
        SAMPLEXCSSTEXT = f.read()

    for page in Pages:
        height = page["Image"]["Height"]
        width = page["Image"]["Width"]
        pageNum = str(page['PageNum'] - 1).zfill(len(str(TotalPages)))
        boxes = list(globals.boxesDB.find(
            {
                "pageId": page["PageID"]
            }
        ).sort([("boxIndex", pymongo.ASCENDING)])
                     )
        pageImgTmp = os.path.join(fileOutputImageFolder, f"Page{page['PageNum']}.jpeg")
        pageImg = ""
        if page["PageNum"] == 1:
            coverImg = pageImgTmp.replace(f"Page{page['PageNum']}.jpeg", "cover.jpg")
            if not os.path.exists(coverImg):
                os.rename(pageImgTmp, coverImg)
            if os.path.exists(pageImgTmp):
                os.remove(pageImgTmp)
            pageImg = coverImg
            pageXhtml = os.path.join(fileOutputTextFolder, "cover.xhtml")
            pageCSS = os.path.join(fileOutputStylesFolder, "cover.css")
            pageXhtmlText = SAMPLEXHTMLTEXT
            pageCSSText = SAMPLEXCSSTEXT
            # coverXhtmlText = coverXhtmlText.replace()
        else:
            pageImg = pageImgTmp.replace(f"Page{page['PageNum']}.jpeg", f"page_{pageNum}.jpg")
            if not os.path.exists(pageImg):
                os.rename(pageImgTmp, pageImg)
            if os.path.exists(pageImgTmp):
                os.remove(pageImgTmp)
            pageXhtml = os.path.join(fileOutputTextFolder, f"page_{pageNum}.xhtml")
            pageCSS = os.path.join(fileOutputStylesFolder, f"page_{pageNum}.css")
            pageXhtmlText = SAMPLEXHTMLTEXT
            pageCSSText = SAMPLEXCSSTEXT

        bodyData = []
        cssData = []
        cssCounter = 0
        lineCssCounter = 0
        for box in boxes:
            pStyle = f'position: absolute;\n' \
                     f'top: {box["coordinates"][1]}px;\n' \
                     f'left: {box["coordinates"][0]}px;\n' \
                     f'width: {width - box["coordinates"][0]}px;\n' \
                     f'height: {box["coordinates"][3]}px;\n'
            lineCssCounter += 1
            lineCssSelector = "line" + str(lineCssCounter)
            cssData.append(f".{lineCssSelector}" + "{\n" + pStyle + "\n}")

            innerHtml = []
            for line in box["linesData"]:
                innerCssData =f'position: {line["position"]};\n' \
                              f'top: {line["top"]};\n'\
                              f'left: {line["left"]};\n'\
                              f'font-family:##fontfamily##;\n'\
                              f'font-size: {line["fontSize"]};\n'\
                              f'font-weight: {line["fontWeight"]};\n'\
                              f'line-height: {line["lineHeight"]};\n'\
                              f'color: {line["color"]};\n'\
                              f'word-spacing: {line["wordSpacing"]};\n'\
                              f'letter-spacing: {line["letterSpacing"]};\n'\
                              f'font-style: {line["fontStyle"]};\n'\
                              f'text-decoration: {line["textDecoration"]};\n'

                innerCssFontName = list(filter(lambda d: d['cssFontName'] in line["fontFamily"], Fonts))
                if len(innerCssFontName) > 0:
                    innerCssData = innerCssData.replace("##fontfamily##", innerCssFontName[0]["fontName"])
                else:
                    innerCssData = innerCssData.replace("##fontfamily##", line["fontFamily"].strip())
                cssCounter += 1
                innerCssSelector = "span" + str(cssCounter)
                innerHtml.append(f'<span class="{innerCssSelector}">{line["text"]}</span>')
                if line["br"] is True:
                    innerHtml.append("<br>")
                cssData.append(f".{innerCssSelector}" + "{\n" + innerCssData + "\n}")


            bodyData.append(f'<{box["tagName"]} class="{lineCssSelector}">{"".join(innerHtml)}</p>')

        pageXhtmlText = pageXhtmlText.replace("<bodyData/>", "\n".join(bodyData)).replace("<br>", "<br/>")
        cssDataFinal = "\n".join(cssData)
        cssDataFinal = re.sub(r"font-family:(.*?);", "font-family:\"\\1\";", cssDataFinal, 0, re.MULTILINE)
        pageCSSText = pageCSSText.replace("{{BODY_CSS}}", cssDataFinal)

        if len(boxes) == 0:
            pageXhtmlText = pageXhtmlText.replace("\n<bodyData/>", "")
            pageCSSText = pageCSSText.replace("{{BODY_CSS}}", "").replace("{{FONTS_CSS}}", "")

        # REPLACING PROPERTIES IN CSS AND XHTML FILES
        if page['PageNum'] == 1:
            pageNum = "cover"
        pageXhtmlText = valueReplacer(page['PageNum'], pageCSS, pageImg, pageXhtmlText, Properties, width, height,
                                      TotalPages)
        pageCSSText = valueReplacer(page['PageNum'], pageCSS, pageImg, pageCSSText, Properties, width, height,
                                    TotalPages)

        with io.open(pageXhtml, 'w', encoding='utf8', newline='\n') as f:
            f.write(pageXhtmlText)

        with io.open(pageCSS, 'w', encoding='utf8', newline='\n') as f:
            f.write(pageCSSText)

    os.remove(SAMPLEXHTML)
    os.remove(SAMPLECSS)


class GenerateFile(Resource):

    @use_kwargs(auth_args, location="headers")
    @use_kwargs({"fileID": fields.Str(required=True)}, location="query")
    def post(self, Authorization, fileID):
        if auth.verify(str(Authorization).split(" ")[1]):
            templateCopy(fileID)
            result = globals.filesDB.find_one(
                {
                    "_id": ObjectId(fileID)
                }
            )

            TotalPages = result["TotalPages"]
            Pages = result["Pages"]
            Fonts = result["Fonts"]
            Properties = result["Properties"]
            width = result["Pages"][0]["Image"]["Width"]
            height = result["Pages"][0]["Image"]["Height"]
            suportingFilesCreation(Properties, width, height, Fonts, TotalPages)
            xhtmlAndCssCreation(Properties, Fonts, TotalPages, Pages)
            isbn = list(filter(lambda d: d['key'] in "ISBN", Properties))
            if len(isbn) > 0:
                isbn = isbn[0]["value"]
            else:
                isbn = "undefined"

            finalFile = os.path.join(fileOutputFolder, f'{isbn}.epub')
            if os.path.exists(finalFile):
                os.remove(finalFile)

            zipobj = ZipFile(finalFile, 'w', ZIP_DEFLATED)
            rootlen = len(fileOutputFolder) + 1
            for base, dirs, files in os.walk(fileOutputFolder):
                for file in files:
                    if ".epub" not in file:
                        fn = os.path.join(base, file)
                        zipobj.write(fn, fn[rootlen:])

            return {"msg": finalFile.replace("\\", "/")}, 200
        else:
            return {"msg": "Unauthorized! Access Denied"}, 401
