import pickle
import uuid
from qgis.core import QgsMapLayer, QgsRasterLayer, QgsVectorLayer, QgsProject, QgsReadWriteContext, \
    QgsMimeDataUtils, QgsLayerTree, QgsLayerTreeLayer
from PyQt5.QtCore import *
from PyQt5.QtXml import *

from enmapbox import debugLog
from enmapbox.gui import SpectralLibrary
from enmapbox.gui.datasources import DataSource, DataSourceSpatial
from enmapbox.gui.datasources import DataSourceFactory, DataSourceSpectralLibrary
from ..externals.qps.layerproperties import defaultRasterRenderer

MDF_RASTERBANDS = 'application/enmapbox.rasterbanddata'

MDF_DATASOURCETREEMODELDATA = 'application/enmapbox.datasourcetreemodeldata'
MDF_DATASOURCETREEMODELDATA_XML = 'data_source_tree_model_data'

MDF_ENMAPBOX_LAYERTREEMODELDATA = 'application/enmapbox.layertreemodeldata'
MDF_QGIS_LAYERTREEMODELDATA = 'application/qgis.layertreemodeldata'
MDF_QGIS_LAYERTREEMODELDATA_XML = 'layer_tree_model_data'

MDF_PYTHON_OBJECTS = 'application/enmapbox/objectreference'
MDF_SPECTRALLIBRARY = 'application/hub-spectrallibrary'

MDF_URILIST = 'text/uri-list'
MDF_TEXT_HTML = 'text/html'
MDF_TEXT_PLAIN = 'text/plain'

MDF_QGIS_LAYER_STYLE = 'application/qgis.style'
QGIS_URILIST_MIMETYPE = "application/x-vnd.qgis.qgis.uri"


def attributesd2dict(attributes: QDomNamedNodeMap) -> str:
    d = {}
    assert isinstance(attributes, QDomNamedNodeMap)
    for i in range(attributes.count()):
        attribute = attributes.item(i)
        d[attribute.nodeName()] = attribute.nodeValue()
    return d


def fromDataSourceList(dataSources):
    from enmapbox.gui.datasources import DataSource
    if not isinstance(dataSources, list):
        dataSources = [dataSources]

    mimeData = QMimeData()

    doc = QDomDocument()
    node = doc.createElement(MDF_DATASOURCETREEMODELDATA_XML)
    doc.appendChild(node)

    for ds in dataSources:
        assert isinstance(ds, DataSource)
        ds.writeXml(node)
    mimeData.setData(MDF_DATASOURCETREEMODELDATA, doc.toByteArray())
    return mimeData


def toDataSourceList(mimeData):
    assert isinstance(mimeData, QMimeData)

    dataSources = []

    if MDF_DATASOURCETREEMODELDATA in mimeData.formats():
        doc = QDomDocument()
        doc.setContent(mimeData.data(MDF_DATASOURCETREEMODELDATA))
        node = doc.firstChildElement(MDF_DATASOURCETREEMODELDATA_XML)
        childs = node.childNodes()

        from enmapbox.gui.datasources import DataSource, DataSourceFactory
        from enmapbox.gui.datasourcemanager import DataSourceManager
        from uuid import UUID
        dsm = DataSourceManager.instance()
        b = isinstance(dsm, DataSourceManager)

        for i in range(childs.count()):
            child = childs.at(i).toElement()

            if child.tagName() == 'enmpabox_datasource':
                attributes = attributesd2dict(child.attributes())
                if isinstance(dsm, DataSourceManager):
                    dataSource = dsm.findSourceFromUUID(UUID(attributes['uuid']))
                    if isinstance(dataSource, DataSource):
                        dataSources.append(dataSource)
                        continue
                dataSources.extend(DataSourceFactory.create(attributes['source'], name=attributes['name']))

    return dataSources


def fromLayerList(mapLayers):
    """
    Converts a list of QgsMapLayers into a QMimeData object
    :param mapLayers: [list-of-QgsMapLayers]
    :return: QMimeData
    """
    for lyr in mapLayers:
        assert isinstance(lyr, QgsMapLayer)

    tree = QgsLayerTree()
    mimeData = QMimeData()

    urls = []
    for l in mapLayers:
        tree.addLayer(l)
        urls.append(QUrl.fromLocalFile(l.source()))
    doc = QDomDocument()
    context = QgsReadWriteContext()
    node = doc.createElement(MDF_QGIS_LAYERTREEMODELDATA_XML)
    doc.appendChild(node)
    for c in tree.children():
        c.writeXml(node, context)

    mimeData.setData(MDF_QGIS_LAYERTREEMODELDATA, doc.toByteArray())

    return mimeData


def containsMapLayers(mimeData: QMimeData) -> bool:
    """
    Checks if the mimeData contains any format suitable to describe QgsMapLayers
    :param mimeData:
    :return:
    """
    valid = [MDF_RASTERBANDS, MDF_DATASOURCETREEMODELDATA, MDF_QGIS_LAYERTREEMODELDATA, QGIS_URILIST_MIMETYPE,
             MDF_URILIST]

    for f in valid:
        if f in mimeData.formats():
            return True
    return False


def extractMapLayers(mimeData: QMimeData) -> list:
    """
    Extracts available QgsMapLayer from QMimeData
    :param mimeData: QMimeData
    :return: [list-of-QgsMapLayers]
    """
    assert isinstance(mimeData, QMimeData)

    newMapLayers = []

    QGIS_LAYERTREE_FORMAT = None
    if MDF_ENMAPBOX_LAYERTREEMODELDATA in mimeData.formats():
        QGIS_LAYERTREE_FORMAT = MDF_ENMAPBOX_LAYERTREEMODELDATA
    elif MDF_QGIS_LAYERTREEMODELDATA in mimeData.formats():
        QGIS_LAYERTREE_FORMAT = MDF_QGIS_LAYERTREEMODELDATA

    if QGIS_LAYERTREE_FORMAT in mimeData.formats():
        doc = QDomDocument()
        doc.setContent(mimeData.data(QGIS_LAYERTREE_FORMAT))
        node = doc.firstChildElement(MDF_QGIS_LAYERTREEMODELDATA_XML)
        context = QgsReadWriteContext()
        # context.setPathResolver(QgsProject.instance().pathResolver())
        layerTree = QgsLayerTree.readXml(node, context)

        attributesLUT = {}
        childs = node.childNodes()

        for i in range(childs.count()):
            child = childs.at(i).toElement()
            if child.tagName() == 'layer-tree-layer':
                attributesLUT[child.attribute('id')] = attributesd2dict(child.attributes())

        for treeLayer in layerTree.findLayers():
            assert isinstance(treeLayer, QgsLayerTreeLayer)
            id = treeLayer.layerId()
            mapLayer = QgsProject.instance().mapLayer(id)

            if isinstance(mapLayer, QgsMapLayer) and QGIS_LAYERTREE_FORMAT == MDF_QGIS_LAYERTREEMODELDATA:
                # clone the layer if it comes from the QGIS Application
                mapLayer = mapLayer.clone()

            if not isinstance(mapLayer, QgsMapLayer) and id in attributesLUT.keys():
                attributes = attributesLUT[id]
                name = attributes.get('name')
                src = attributes['source']
                providerKey = attributes.get('providerKey')

                if providerKey in ['gdal', 'wms']:
                    mapLayer = QgsRasterLayer(src, name, providerKey)

                elif providerKey in ['ogr', 'WFS']:
                    mapLayer = QgsVectorLayer(src, name, providerKey)

                if isinstance(mapLayer, QgsMapLayer):
                    mapLayer.setName(attributes['name'])

            if isinstance(mapLayer, (QgsRasterLayer, QgsVectorLayer)):
                newMapLayers.append(mapLayer)


    elif MDF_RASTERBANDS in mimeData.formats():
        data = pickle.loads(mimeData.data(MDF_RASTERBANDS))

        for t in data:
            uri, baseName, providerKey, band = t
            lyr = QgsRasterLayer(uri, baseName=baseName, providerType=providerKey)
            lyr.setRenderer(defaultRasterRenderer(lyr, bandIndices=[band]))
            newMapLayers.append(lyr)

    elif MDF_DATASOURCETREEMODELDATA in mimeData.formats():
        # this drop comes from the datasource tree
        dsUUIDs = pickle.loads(mimeData.data(MDF_DATASOURCETREEMODELDATA))

        for uuid4 in dsUUIDs:
            assert isinstance(uuid4, uuid.UUID)
            dataSource = DataSource.fromUUID(uuid4)
            if isinstance(dataSource, DataSourceSpatial):
                lyr = dataSource.createUnregisteredMapLayer()
                if isinstance(lyr, QgsRasterLayer):
                    lyr.setRenderer(defaultRasterRenderer(lyr))
                newMapLayers.append(lyr)

    elif MDF_ENMAPBOX_LAYERTREEMODELDATA in mimeData.formats():
        # this drop comes from the dock tree

        s = ""

    elif QGIS_URILIST_MIMETYPE in mimeData.formats():
        for uri in QgsMimeDataUtils.decodeUriList(mimeData):
            dataSources = DataSourceFactory.create(uri)
            for dataSource in dataSources:
                if isinstance(dataSource, DataSourceSpatial):
                    lyr = dataSource.createUnregisteredMapLayer()
                    if isinstance(lyr, QgsRasterLayer):
                        lyr.setRenderer(defaultRasterRenderer(lyr))
                    newMapLayers.append(lyr)

    elif MDF_URILIST in mimeData.formats():
        for url in mimeData.urls():
            dataSources = DataSourceFactory.create(url)
            for dataSource in dataSources:
                if isinstance(dataSource, DataSourceSpatial) and not isinstance(dataSource, DataSourceSpectralLibrary):
                    lyr = dataSource.createUnregisteredMapLayer()
                    if isinstance(lyr, QgsRasterLayer):
                        lyr.setRenderer(defaultRasterRenderer(lyr))
                    newMapLayers.append(lyr)
    else:
        s = ""

    info = ['Extract map layers from QMimeData']
    info.append('Formats:' + ','.join(mimeData.formats()))
    info.append(f' {len(newMapLayers)} Map Layers: ' + '\n\t'.join([f'{l}' for l in newMapLayers]))
    debugLog('\n'.join(info))

    return newMapLayers


def extractSpectralLibraries(mimeData: QMimeData) -> list:
    """Reads spectral libraries that may be defined in mimeData"""
    results = []
    slib = SpectralLibrary.readFromMimeData(mimeData)
    if isinstance(slib, SpectralLibrary):
        results.append(slib)

    return results


def textToByteArray(text):
    """
    Converts input into a QByteArray
    :param text: bytes or str
    :return: QByteArray
    """

    if isinstance(text, QDomDocument):
        return textToByteArray(text.toString())
    else:
        data = QByteArray()
        data.append(text)
        return data


def textFromByteArray(data):
    """
    Decodes a QByteArray into a str
    :param data: QByteArray
    :return: str
    """
    assert isinstance(data, QByteArray)
    s = data.data().decode()
    return s
