# -*- coding: utf-8 -*-
"""
/***************************************************************************
 Qgis2threejs
                                 A QGIS plugin
 export terrain data, map canvas image and vector data to web browser
                              -------------------
        begin                : 2014-01-16
        copyright            : (C) 2014 Minoru Akagi
        email                : akaginch@gmail.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
"""
import os

from PyQt5.QtCore import Qt, QSize, QUrl
from PyQt5.QtGui import QColor, QImage, QPainter
from qgis.core import QgsMapLayer

from . import qgis2threejstools as tools
from .qgis2threejstools import logMessage


class DataManager:
    """ manages a list of unique items """

    def __init__(self):
        self._list = []

    def count(self):
        return len(self._list)

    def _index(self, data):
        if data in self._list:
            return self._list.index(data)

        index = len(self._list)
        self._list.append(data)
        return index


class ImageManager(DataManager):

    IMAGE_FILE = 1
    CANVAS_IMAGE = 2
    MAP_IMAGE = 3
    LAYER_IMAGE = 4

    def __init__(self, exportSettings):
        DataManager.__init__(self)
        self.exportSettings = exportSettings
        self._renderer = None

    def imageIndex(self, path):
        img = (self.IMAGE_FILE, path)
        return self._index(img)

    def canvasImageIndex(self, transp_background):
        img = (self.CANVAS_IMAGE, transp_background)
        return self._index(img)

    def mapImageIndex(self, width, height, extent, transp_background):
        img = (self.MAP_IMAGE, (width, height, extent, transp_background))
        return self._index(img)

    def layerImageIndex(self, layerids, width, height, extent, transp_background):
        img = (self.LAYER_IMAGE, (layerids, width, height, extent, transp_background))
        return self._index(img)

    def mapCanvasImage(self, transp_background=False):
        """ returns base64 encoded map canvas image """
        size = self.exportSettings.mapSettings.outputSize()
        return self.renderedImage(size.width(), size.height(), self.exportSettings.baseExtent, transp_background)

    def renderedImage(self, width, height, extent, transp_background=False, layerids=None):
        # render layers with QgsMapRendererCustomPainterJob
        from qgis.core import QgsMapRendererCustomPainterJob
        antialias = True
        settings = self.exportSettings.mapSettings

        # store old map settings
        old_outputSize = settings.outputSize()
        old_extent = settings.extent()
        old_rotation = settings.rotation()
        old_layers = settings.layers()
        old_backgroundColor = settings.backgroundColor()

        # map settings
        settings.setOutputSize(QSize(width, height))
        settings.setExtent(extent.unrotatedRect())
        settings.setRotation(extent.rotation())

        if layerids:
            settings.setLayers(tools.getLayersByLayerIds(layerids))

        if transp_background:
            settings.setBackgroundColor(QColor(Qt.transparent))

        has_pluginlayer = False
        for layer in settings.layers():
            if layer and layer.type() == QgsMapLayer.PluginLayer:
                has_pluginlayer = True
                break

        # create an image
        image = QImage(width, height, QImage.Format_ARGB32_Premultiplied)
        painter = QPainter()
        painter.begin(image)
        if antialias:
            painter.setRenderHint(QPainter.Antialiasing)

        # rendering
        job = QgsMapRendererCustomPainterJob(settings, painter)
        if has_pluginlayer:
            job.renderSynchronously()   # use this method so that TileLayerPlugin layer is rendered correctly
        else:
            job.start()
            job.waitForFinished()
        painter.end()

        # restore map settings
        settings.setOutputSize(old_outputSize)
        settings.setExtent(old_extent)
        settings.setRotation(old_rotation)
        settings.setLayers(old_layers)
        settings.setBackgroundColor(old_backgroundColor)

        return image

    def image(self, index):
        image = self._list[index]
        imageType = image[0]
        if imageType == self.IMAGE_FILE:
            image_path = image[1]
            if os.path.isfile(image_path):
                return QImage(image_path)
            else:
                logMessage("Image file not found: {0}".format(image_path))
                image = QImage(1, 1, QImage.Format_RGB32)
                image.fill(Qt.lightGray)
                return image

        if imageType == self.MAP_IMAGE:
            width, height, extent, transp_background = image[1]
            return self.renderedImage(width, height, extent, transp_background)

        if imageType == self.LAYER_IMAGE:
            layerids, width, height, extent, transp_background = image[1]
            return self.renderedImage(width, height, extent, transp_background, layerids)

        # imageType == self.CANVAS_IMAGE:
        transp_background = image[1]
        return self.mapCanvasImage(transp_background)

    def base64image(self, index):
        image = self.image(index)
        if image:
            return tools.base64image(image)
        return None

    def write(self, index, path):
        self.image(index).save(path)

    def writeAll(self, pathRoot):
        for i in range(self.count()):
            self.image(i).save("{0}{1}.png".format(pathRoot, i))


class MaterialManager(DataManager):

    # following six material types are defined also in JS
    # first three types are basic material types
    MESH_LAMBERT = 0
    MESH_PHONG = 1
    MESH_TOON = 2

    LINE_BASIC = 3
    LINE_DASHED = 4
    SPRITE_IMAGE = 5
    POINT = 6

    # other material types for internal use
    MESH_MATERIAL = 10
    MESH_FLAT = 11
    WIREFRAME = 12

    CANVAS_IMAGE = 20
    MAP_IMAGE = 21
    LAYER_IMAGE = 22
    IMAGE_FILE = 23

    ERROR_COLOR = "0"

    def __init__(self, imageManager, basicType=MESH_LAMBERT):
        DataManager.__init__(self)

        self.imageManager = imageManager
        self.basicMaterialType = basicType

    def _indexCol(self, type, color, opacity=1, doubleSide=False, opts=None):
        if color[0:2] != "0x":
            color = self.ERROR_COLOR
        mtl = (type, color, opacity, doubleSide, opts)
        return self._index(mtl)

    def getMeshMaterialIndex(self, color, opacity=1, doubleSide=False):
        return self._indexCol(self.MESH_MATERIAL, color, opacity, doubleSide)

    def getFlatMeshMaterialIndex(self, color, opacity=1, doubleSide=False):
        return self._indexCol(self.MESH_FLAT, color, opacity, doubleSide)

    def getPointMaterialIndex(self, color, opacity=1, size=1):
        return self._indexCol(self.POINT, color, opacity, False, size)

    def getBasicLineIndex(self, color, opacity=1):
        return self._indexCol(self.LINE_BASIC, color, opacity)

    def getDashedLineIndex(self, color, opacity=1):
        return self._indexCol(self.LINE_DASHED, color, opacity)

    def getWireframeIndex(self, color, opacity=1):
        return self._indexCol(self.WIREFRAME, color, opacity)

    def getCanvasImageIndex(self, opacity=1, transp_background=False):
        mtl = (self.CANVAS_IMAGE, None, opacity, True, transp_background)
        return self._index(mtl)

    def getMapImageIndex(self, width, height, extent, opacity=1, transp_background=False):
        mtl = (self.MAP_IMAGE, None, opacity, True, (width, height, extent, transp_background))
        return self._index(mtl)

    def getLayerImageIndex(self, layerids, width, height, extent, opacity=1, transp_background=False):
        mtl = (self.LAYER_IMAGE, None, opacity, True, (layerids, width, height, extent, transp_background))
        return self._index(mtl)

    def getImageFileIndex(self, path, opacity=1, transp_background=False, doubleSide=False):
        mtl = (self.IMAGE_FILE, None, opacity, doubleSide, (path, transp_background))
        return self._index(mtl)

    def getSpriteImageIndex(self, path_url, opacity=1):
        transp_background = True
        mtl = (self.SPRITE_IMAGE, None, opacity, False, (path_url, transp_background))
        return self._index(mtl)

    def build(self, index, filepath=None, url=None, base64=False):

        mt, color, opacity, doubleSide, opts = self._list[index]
        transp_background = False

        m = {
            "type": mt if mt in [self.POINT, self.LINE_BASIC, self.LINE_DASHED, self.SPRITE_IMAGE] else self.basicMaterialType
        }

        if color is None:
            if mt == self.CANVAS_IMAGE:
                transp_background = opts
                imgIndex = self.imageManager.canvasImageIndex(transp_background)
            elif mt == self.MAP_IMAGE:
                width, height, extent, transp_background = opts
                imgIndex = self.imageManager.mapImageIndex(width, height, extent, transp_background)
            elif mt == self.LAYER_IMAGE:
                layerids, width, height, extent, transp_background = opts
                imgIndex = self.imageManager.layerImageIndex(layerids, width, height, extent, transp_background)
            elif mt == self.IMAGE_FILE:
                imagepath, transp_background = opts
                imgIndex = self.imageManager.imageIndex(imagepath)
            elif mt == self.SPRITE_IMAGE:
                path_url, transp_background = opts
                if path_url.startswith("http:") or path_url.startswith("https:"):
                    url = path_url
                    filepath = None
                else:
                    imgIndex = self.imageManager.imageIndex(path_url)

            if url is None:
                if base64:
                    m["image"] = {"base64": self.imageManager.base64image(imgIndex)}
                else:
                    m["image"] = {"object": self.imageManager.image(imgIndex)}
            else:
                m["image"] = {"url": url}

                if filepath:
                    # write image to a file
                    self.imageManager.write(imgIndex, filepath)
        else:
            m["c"] = int(color, 16)

            if mt == self.POINT:
                m["s"] = opts   # size

        if transp_background:
            m["t"] = 1

        if mt == self.WIREFRAME:
            m["w"] = 1

        if mt == self.MESH_FLAT:
            m["flat"] = 1

        if opacity < 1:
            m["o"] = opacity

        if doubleSide:
            m["ds"] = 1

        return m

    def buildAll(self, pathRoot=None, urlRoot=None, base64=False):
        mList = []
        for i in range(len(self._list)):
            if pathRoot is None:
                filepath = url = None
            else:
                filepath = "{0}{1}.png".format(pathRoot, i)
                url = "{0}{1}.png".format(urlRoot, i)
            mList.append(self.build(i, filepath, url, base64))
        return mList


class ModelManager(DataManager):

    def __init__(self, exportSettings):
        DataManager.__init__(self)
        self.exportSettings = exportSettings

    def modelIndex(self, path):
        return self._index(path)

    def build(self, export=True, base64=False):
        a = []
        for path_url in self._list:
            if path_url.startswith("http:") or path_url.startswith("https:"):
                a.append({"url": path_url})
            elif base64:
                _, ext = os.path.splitext(path_url)
                a.append({"base64": tools.base64file(path_url),
                          "ext": ext[1:],
                          "resourcePath": "./data/{}/models/".format(self.exportSettings.outputFileTitle())})
            else:
                if export:
                    url = "./data/{}/models/{}".format(self.exportSettings.outputFileTitle(),
                                                       os.path.basename(path_url))
                else:
                    url = QUrl.fromLocalFile(path_url).toString()

                a.append({"url": url})
        return a

    def hasColladaModel(self):
        for f in self._list:
            _, ext = os.path.splitext(f)
            if ext == ".dae":
                return True
        return False

    def hasGLTFModel(self):
        for f in self._list:
            _, ext = os.path.splitext(f)
            if ext in [".gltf", ".glb"]:
                return True
        return False

    def filesToCopy(self):
        f = []
        if self._list:
            if self.hasColladaModel():
                f.append({"files": ["js/threejs/loaders/ColladaLoader.js"], "dest": "threejs/loaders"})
            if self.hasGLTFModel():
                f.append({"files": ["js/threejs/loaders/GLTFLoader.js"], "dest": "threejs/loaders"})
            f.append({"files": self._list, "dest": "./data/{}/models".format(self.exportSettings.outputFileTitle())})
        return f

    def scripts(self):
        s = []
        if self._list:
            if self.hasColladaModel():
                s.append("./threejs/loaders/ColladaLoader.js")
            if self.hasGLTFModel():
                s.append("./threejs/loaders/GLTFLoader.js")
        return s
