# -*- coding: utf-8 -*-

"""
/***************************************************************************
Name                 : DB Manager
Description          : Database manager plugin for QGIS
Date                 : May 23, 2011
copyright            : (C) 2011 by Giuseppe Sucameli
email                : brush.tyler@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.                                   *
 *                                                                         *
 ***************************************************************************/
"""
from builtins import str
from builtins import range

from qgis.PyQt.QtCore import Qt, QObject, pyqtSignal
from qgis.PyQt.QtWidgets import QApplication, QAction, QMenu, QInputDialog, QMessageBox
from qgis.PyQt.QtGui import QKeySequence, QIcon

from qgis.gui import QgsMessageBar
from qgis.core import (
    Qgis,
    QgsApplication,
    QgsSettings,
    QgsMapLayerType,
    QgsWkbTypes
)
from ..db_plugins import createDbPlugin


class BaseError(Exception):

    """Base class for exceptions in the plugin."""

    def __init__(self, e):
        if isinstance(e, Exception):
            msg = e.args[0] if len(e.args) > 0 else ''
        else:
            msg = e

        if not isinstance(msg, str):
            msg = str(msg, 'utf-8', 'replace')  # convert from utf8 and replace errors (if any)

        self.msg = msg
        Exception.__init__(self, msg)

    def __unicode__(self):
        return self.msg


class InvalidDataException(BaseError):
    pass


class ConnectionError(BaseError):
    pass


class DbError(BaseError):

    def __init__(self, e, query=None):
        BaseError.__init__(self, e)
        self.query = str(query) if query is not None else None

    def __unicode__(self):
        if self.query is None:
            return BaseError.__unicode__(self)

        msg = QApplication.translate("DBManagerPlugin", "Error:\n{0}").format(BaseError.__unicode__(self))
        if self.query:
            msg += QApplication.translate("DBManagerPlugin", "\n\nQuery:\n{0}").format(self.query)
        return msg


class DBPlugin(QObject):
    deleted = pyqtSignal()
    changed = pyqtSignal()
    aboutToChange = pyqtSignal()

    def __init__(self, conn_name, parent=None):
        QObject.__init__(self, parent)
        self.connName = conn_name
        self.db = None

    def __del__(self):
        pass  # print "DBPlugin.__del__", self.connName

    def connectionIcon(self):
        return QgsApplication.getThemeIcon("/mIconDbSchema.svg")

    def con
nectionName(self):
        return self.connName

    d
ef database(self):
        return self.db

    def info(self):
        from .info_model import DatabaseInfo

        return DatabaseInfo(None)

    def connect(self, parent=None):
        raise NotImplementedError('Needs to be implemented by subclasses')

    def connectToUri(self, uri):
        self.db = self.databasesFactory(self, uri)
        if self.db:
            return True
        return False

    def reconnect(self):
        if self.db is not None:
            uri = self.db.uri()
            self.db.deleteLater()
            self.db = None
            return self.connectToUri(uri)
        return self.connect(self.parent())

    def remove(self):
        settings = QgsSettings()
        settings.beginGroup(u"/%s/%s" % (self.connectionSettingsKey(), self.connectionName()))
        settings.remove("")
        self.deleted.emit()
        return True

    @classmethod
    def addConnection(self, conn_name, uri):
        raise NotImplementedError('Needs to be implemented by subclasses')

    @classmethod
    def icon(self):
        return None

    @classmethod
    def typeName(self):
        # return the db typename (e.g. 'postgis')
        pass

    @classmethod
    def typeNameString(self):
        # return the db typename string (e.g. 'PostGIS')
        pass

    @classmethod
    def providerName(self):
        # return the provider's name (e.g. 'postgres')
        pass

    @classmethod
    def connectionSettingsKey(self):
        # return the key used to store the connections in settings
        pass

    @classmethod
    def connections(self):
        # get the list of connections
        conn_list = []
        settings = QgsSettings()
        settings.beginGroup(self.connectionSettingsKey())
        for name in settings.childGroups():
            conn_list.append(createDbPlugin(self.typeName(), name))
        settings.endGroup()
        return conn_list

    def databasesFactory(self, connection, uri):
        return None

    @classmethod
    def addConnectionActionSlot(self, item, action, parent):
        raise NotImplementedError('Needs to be implemented by subclasses')

    def removeActionSlot(self, item, action, parent):
        QApplication.restoreOverrideCursor()
        try:
            res = QMessageBox.question(parent, QApplication.translate("DBManagerPlugin", "DB Manager"),
                                       QApplication.translate("DBManagerPlugin",
                                                              "Really remove connection to {0}?").format(item.connectionName()),
                                       QMessageBox.Yes | QMessageBox.No)
            if res != QMessageBox.Yes:
                return
        finally:
            QApplication.setOverrideCursor(Qt.WaitCursor)

        item.remove()


class DbItemObject(QObject):
    changed = pyqtSignal()
    aboutToChange = pyqtSignal()
    deleted = pyqtSignal()

    def __init__(self, parent=None):
        QObject.__init__(self, parent)

    def database(self):
        return None

    def refresh(self):
        self.changed.emit()  # refresh the item data reading them from the db

    def info(self):
        pass

    def runAction(self):
        pass

    def registerActions(self, mainWindow):
        pass


class Database(DbItemObject):

    def __init__(self, dbplugin, uri):
        DbItemObject.__init__(self, dbplugin)
        self.connector = self.connectorsFactory(uri)

    def connectorsFactory(self, uri):
        return None

    def __del__(self):
        self.connector = None
        pass  # print "Database.__del__", self

    def connection(self):
        return self.parent()

    def dbplugin(self):
        return self.parent()

    def database(self):
        return self

    def uri(self):
        return self.connector.uri()

    def publicUri(self):
        return self.connector.publicUri()

    def delete(self):
        self.aboutToChange.emit()
        ret = self.connection().remove()
        if ret is not False:
            self.deleted.emit()
        return ret

    def info(self):
        from .info_model import DatabaseInfo

        return DatabaseInfo(self)

    def sqlResultModel(self, sql, parent):
        from .data_model import SqlResultModel

        return SqlResultModel(self, sql, parent)

    def sqlResultModelAsync(self, sql, parent):
        from .data_model import SqlResultModelAsync

        return SqlResultModelAsync(self, sql, parent)

    def columnUniqueValuesModel(self, col, table, limit=10):
        l = ""
        if limit is not None:
            l = "LIMIT %d" % limit
        return self.sqlResultModel("SELECT DISTINCT %s FROM %s %s" % (col, table, l), self)

    def uniqueIdFunction(self):
        """Return a SQL function used to generate a unique id for rows of a query"""
        # may be overloaded by derived classes
        return "row_number() over ()"

    def toSqlLayer(self, sql, geomCol, uniqueCol, layerName="QueryLayer", layerType=None, avoidSelectById=False, filter=""):
        from qgis.core import QgsVectorLayer, QgsRasterLayer

        if uniqueCol is None:
            if hasattr(s