import base64
import json
import re
import os
import shutil
import sys
import zipfile

try:
    import zlib

    compression = zipfile.ZIP_DEFLATED
except:
    compression = zipfile.ZIP_STORED

# -----------------------------------------------------------------------------


def convertDirectoryToZipFile(directoryPath):
    if os.path.isfile(directoryPath):
        return

    zipFilePath = "%s.zip" % directoryPath
    zf = zipfile.ZipFile(zipFilePath, mode="w")

    try:
        for dirName, subdirList, fileList in os.walk(directoryPath):
            for fname in fileList:
                fullPath = os.path.join(dirName, fname)
                relPath = "%s" % (os.path.relpath(fullPath, directoryPath))
                zf.write(fullPath, arcname=relPath, compress_type=compression)
    finally:
        zf.close()

    shutil.rmtree(directoryPath)
    shutil.move(zipFilePath, directoryPath)


# -----------------------------------------------------------------------------


def addDataToViewer(dataPath, srcHtmlPath, disableGirder=False):
    if os.path.isfile(dataPath) and os.path.exists(srcHtmlPath):
        dstDir = os.path.dirname(dataPath)
        dstHtmlPath = os.path.join(dstDir, "%s.html" % os.path.basename(dataPath)[:-6])

        # Extract data as base64
        with open(dataPath, "rb") as data:
            dataContent = data.read()
            base64Content = base64.b64encode(dataContent)
            base64Content = base64Content.decode().replace("\n", "")

        # Create new output file
        with open(srcHtmlPath, mode="r", encoding="utf-8") as srcHtml:
            with open(dstHtmlPath, mode="w", encoding="utf-8") as dstHtml:
                for line in srcHtml:
                    if disableGirder and "</title>" in line:
                        dstHtml.write(
                            """
    <script>
        // Force reloading the page if we want to disable girder before anything else.
        const urlParams = new URLSearchParams(window.location.search);
        if (urlParams.get('noGirder') != 'true') {
            urlParams.set('noGirder', 'true');
            window.location.search = urlParams;
        }
    </script>
                                      """
                        )
                    if "</body>" in line:
                        dstHtml.write("<script>\n")
                        dstHtml.write('var contentToLoad = "%s";\n\n' % base64Content)
                        dstHtml.write(
                            'Glance.importBase64Dataset("%s" , contentToLoad, glanceInstance.proxyManager);\n'
                            % os.path.basename(dataPath)
                        )
                        dstHtml.write("glanceInstance.showApp();\n")
                        dstHtml.write("</script>\n")

                    dstHtml.write(line)


# -----------------------------------------------------------------------------


def numericSorted(l):
    """Numerically sort a list of strings."""

    # pattern to split name into numeric and non-numeric parts
    splitter_pattern = re.compile('([0-9]+|[^0-9]+)')

    def keyfunc(name):
        """Sorting key for numeric sorting."""
        split_name = re.findall(splitter_pattern, name)
        # one-liner to convert numeric parts into integers
        split_name = list(map(lambda x: int(x) if x.isdigit() else x, split_name))
        # ensure that list begins with a string to avoid string<->int compare
        if split_name and isinstance(split_name[0], int):
            split_name.insert(0, '')
        return split_name

    # return the numerically sorted list
    return sorted(l, key=keyfunc)


# -----------------------------------------------------------------------------


def zipAllTimeSteps(directoryPath):
    if os.path.isfile(directoryPath):
        return

    class UrlCounterDict(dict):
        Counter = 0

        def GetUrlName(self, name):
            if name not in self.keys():
                self[name] = str(objNameToUrls.Counter)
                self.Counter = self.Counter + 1
            return self[name]

    def InitIndex(sourcePath, destObj):
        with open(sourcePath, "r") as sourceFile:
            sourceData = sourceFile.read()
            sourceObj = json.loads(sourceData)
            for key in sourceObj:
                destObj[key] = sourceObj[key]
            # remove vtkHttpDataSetReader information
            for obj in destObj["scene"]:
                obj.pop(obj["type"])
                obj.pop("type")

    def getUrlToNameDictionary(indexObj):
        urls = {}
        for obj in indexObj["scene"]:
            urls[obj[obj["type"]]["url"]] = obj["name"]
        return urls

    def addDirectoryToZip(
        dirname, zipobj, storedData, rootIdx, timeStep, objNameToUrls
    ):
        # Update root index.json file from index.json of this timestep
        with open(os.path.join(dirname, "index.json"), "r") as currentIdxFile:
            currentIdx = json.loads(currentIdxFile.read())
            urlToName = getUrlToNameDictionary(currentIdx)
            rootTimeStepSection = rootIdx["animation"]["timeSteps"][timeStep]
            for key in currentIdx:
                if key == "scene" or key == "version":
                    continue
                rootTimeStepSection[key] = currentIdx[key]
            for obj in currentIdx["scene"]:
                objName = obj["name"]
                rootTimeStepSection[objName] = {}
                rootTimeStepSection[objName]["actor"] = obj["actor"]
                rootTimeStepSection[objName]["actorRotation"] = obj["actorRotation"]
                rootTimeStepSection[objName]["mapper"] = obj["mapper"]
                rootTimeStepSection[objName]["property"] = obj["property"]

        # For every object in the current timestep
        for folder in sorted(os.listdir(dirname)):
            currentItem = os.path.join(dirname, folder)
            if os.path.isdir(currentItem) is False:
                continue
            # Write all data array of the current timestep in the archive
            for filename in os.listdir(os.path.join(currentItem, "data")):
                fullpath = os.path.join(currentItem, "data", filename)
                if os.path.isfile(fullpath) and filename not in storedData:
                    storedData.add(filename)
                    relPath = os.path.join("data", filename)
                    zipobj.write(fullpath, arcname=relPath, compress_type=compression)
            # Write the index.json containing pointers to these data arrays
            # while replacing every basepath as '../../data'
            objIndexFilePath = os.path.join(dirname, folder, "index.json")
            with open(objIndexFilePath, "r") as objIndexFile:
                objIndexObjData = json.loads(objIndexFile.read())
            for elm in objIndexObjData.keys():
                try:
                    if "ref" in objIndexObjData[elm].keys():
                        objIndexObjData[elm]["ref"]["basepath"] = "../../data"
                    if "arrays" in objIndexObjData[elm].keys():
                        for array in objIndexObjData[elm]["arrays"]:
                            array["data"]["ref"]["basepath"] = "../../data"
                except AttributeError:
                    continue
            currentObjName = urlToName[folder]
            objIndexRelPath = os.path.join(
                objNameToUrls.GetUrlName(currentObjName), str(timeStep), "index.json"
            )
            zipobj.writestr(
                objIndexRelPath,
                json.dumps(objIndexObjData, indent=2),
                compress_type=compression,
            )

    # ---

    zipFilePath = "%s.zip" % directoryPath
    currentDirectory = os.path.abspath(os.path.join(directoryPath, os.pardir))
    rootIndexPath = os.path.join(currentDirectory, "index.json")
    rootIndexFile = open(rootIndexPath, "r")
    rootIndexObj = json.loads(rootIndexFile.read())

    zf = zipfile.ZipFile(zipFilePath, mode="w")
    try:
        # We copy the scene from an index of a specific timestep to the root index
        # Scenes should all have the same objects so only do it for the first one
        isSceneInitialized = False
        # currentlyAddedData set stores hashes of every data we already added to the
        # vtkjs archive to prevent data duplication
        currentlyAddedData = set()
        # Regex that folders storing timestep data from paraview should follow
        reg = re.compile(r"^" + os.path.basename(directoryPath) + r"\.[0-9]+$")
        # We assume an object will not be deleted from a timestep to another so we create a generic index.json for each object
        genericIndexObj = {}
        genericIndexObj["series"] = []
        timeStep = 0
        for item in rootIndexObj["animation"]["timeSteps"]:
            genericIndexObj["series"].append({})
            genericIndexObj["series"][timeStep]["url"] = str(timeStep)
            genericIndexObj["series"][timeStep]["timeStep"] = float(item["time"])
            timeStep = timeStep + 1
        # Keep track of the url for every object
        objNameToUrls = UrlCounterDict()

        timeStep = 0
        # zip all timestep directories
        for folder in numericSorted(os.listdir(currentDirectory)):
            fullPath = os.path.join(currentDirectory, folder)
            if os.path.isdir(fullPath) and reg.match(folder):
                if not isSceneInitialized:
                    InitIndex(os.path.join(fullPath, "index.json"), rootIndexObj)
                    isSceneInitialized = True
                addDirectoryToZip(
                    fullPath,
                    zf,
                    currentlyAddedData,
                    rootIndexObj,
                    timeStep,
                    objNameToUrls,
                )
                shutil.rmtree(fullPath)
                timeStep = timeStep + 1

        # Write every index.json holding time information for each object
        for name in objNameToUrls:
            zf.writestr(
                os.path.join(objNameToUrls[name], "index.json"),
                json.dumps(genericIndexObj, indent=2),
                compress_type=compression,
            )

        # Update root index.json urls and write it in the archive
        for obj in rootIndexObj["scene"]:
            obj["id"] = obj["name"]
            obj["type"] = "vtkHttpDataSetSeriesReader"
            obj["vtkHttpDataSetSeriesReader"] = {}
            obj["vtkHttpDataSetSeriesReader"]["url"] = objNameToUrls[obj["name"]]
        zf.writestr(
            "index.json", json.dumps(rootIndexObj, indent=2), compress_type=compression
        )

        rootIndexFile.close()
        os.remove(rootIndexPath)

    finally:
        zf.close()

    shutil.move(zipFilePath, directoryPath)


# -----------------------------------------------------------------------------
# Main
# -----------------------------------------------------------------------------

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(
            "Usage: directoryToFile /path/to/directory.vtkjs [/path/to/ParaViewGlance.html]"
        )
    else:
        fileName = sys.argv[1]
        convertDirectoryToZipFile(fileName)

        if len(sys.argv) == 3:
            addDataToViewer(fileName, sys.argv[2])
