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

"""
/***************************************************************************
Name                 : DB Manager
Description          : Database manager plugin for QuantumGIS
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.                                   *
 *                                                                         *
 ***************************************************************************/
"""

# this will disable the dbplugin if the connector raise an ImportError
from .connector import PostGisDBConnector

from PyQt4.QtCore import *
from PyQt4.QtGui import *

from ..plugin import ConnectionError, DBPlugin, Database, Schema, Table, VectorTable, RasterTable, TableField, TableConstraint, TableIndex, TableTrigger, TableRule
try:
	from . import resources_rc
except ImportError:
	pass

from ..html_elems import HtmlParagraph, HtmlList, HtmlTable


def classFactory():
	return PostGisDBPlugin

class PostGisDBPlugin(DBPlugin):

	@classmethod
	def icon(self):
		return QIcon(":/db_manager/postgis/icon")

	@classmethod
	def typeName(self):
		return 'postgis'

	@classmethod
	def typeNameString(self):
		return 'PostGIS'

	@classmethod
	def providerName(self):
		return 'postgres'

	@classmethod
	def connectionSettingsKey(self):
		return '/PostgreSQL/connections'

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

	def connect(self, parent=None):
		conn_name = self.connectionName()
		settings = QSettings()
		settings.beginGroup( u"/%s/%s" % (self.connectionSettingsKey(), conn_name) )

		if not settings.contains( "database" ): # non-existent entry?
			raise InvalidDataException( u'there is no defined database connection "%s".' % conn_name )

		from qgis.core import QgsDataSourceURI
		uri = QgsDataSourceURI()

		settingsList = ["service", "host", "port", "database", "username", "password"]
		service, host, port, database, username, password = map(lambda x: settings.value(x).toString(), settingsList)

		# qgis1.5 use 'savePassword' instead of 'save' setting
		savedPassword = settings.value("save", False).toBool() or settings.value("savePassword", False).toBool()

		useEstimatedMetadata = settings.value("estimatedMetadata", False).toBool()
		sslmode = settings.value("sslmode", QgsDataSourceURI.SSLprefer).toInt()[0]

		settings.endGroup()

		if not service.isEmpty():
			uri.setConnection(service, database, username, password, sslmode)
		else:
			uri.setConnection(host, port, database, username, password, sslmode)
			
		uri.setUseEstimatedMetadata(useEstimatedMetadata)

		err = QString()
		try:
			return self.connectToUri(uri)
		except ConnectionError, e:
			err = QString( str(e) )

		hasCredentialDlg = True
		try:
			from qgis.gui import QgsCredentials
		except ImportError:	# no credential dialog
			hasCredentialDlg = False

		# ask for valid credentials
		max_attempts = 3
		for i in range(max_attempts):
			if hasCredentialDlg:
				(ok, username, password) = QgsCredentials.instance().get(uri.connectionInfo(), username, password, err)
			else:
				(password, ok) = QInputDialog.getText(parent, u"Enter password", u'Enter password for connection "%s":' % conn_name, QLineEdit.Password)

			if not ok:
				return False

			if not service.isEmpty():
				uri.setConnection(service, database, username, password, sslmode)
			else:
				uri.setConnection(host, port, database, username, password, sslmode)

			try:
				self.connectToUri(uri)
			except ConnectionError, e:
				if i == max_attempts-1:	# failed the last attempt
					raise e
				err = QString( str(e) )
				continue

			if hasCredentialDlg:
				QgsCredentials.instance().put(uri.connectionInfo(), username, password)
			return True

		return False


class PGDatabase(Database):
	def __init__(self, connection, uri):
		Database.__init__(self, connection, uri)

	def connectorsFactory(self, uri):
		return PostGisDBConnector(uri)

	def dataTablesFactory(self, row, db, schema=None):
		return PGTable(row, db, schema)

	def vectorTablesFactory(self, row, db, schema=None):
		return PGVectorTable(row, db, schema)

	def rasterTablesFactory(self, row, db, schema=None):
		return PGRasterTable(row, db, schema)

	def schemasFactory(self, row, db):
		return PGSchema(row, db)

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


	def registerDatabaseActions(self, mainWindow):
		Database.registerDatabaseActions(self, mainWindow)

		# add a separator
		separator = QAction(self); 
		separator.setSeparator(True)
		mainWindow.registerAction( separator, "&Table" )

		action = QAction("Run &Vacuum Analyze", self)
		mainWindow.registerAction( action, "&Table", self.runVacuumAnalyzeActionSlot )

	def runVacuumAnalyzeActionSlot(self, item, action, parent):
		QApplication.restoreOverrideCursor()
		try:
			if not isinstance(item, Table) or item.isView:
				QMessageBox.information(parent, "Sorry", "Select a TABLE for vacuum analyze.")
				return
		finally:
			QApplication.setOverrideCursor(Qt.WaitCursor)

		item.runVacuumAnalyze()


class PGSchema(Schema):
	def __init__(self, row, db):
		Schema.__init__(self, db)
		self.oid, self.name, self.owner, self.perms, self.comment = row


class PGTable(Table):
	def __init__(self, row, db, schema=None):
		Table.__init__(self, db, schema)
		self.name, schema_name, self.isView, self.owner, self.estimatedRowCount, self.pages, self.comment = row
		self.estimatedRowCount = int(self.estimatedRowCount)

	def runVacuumAnalyze(self):
		self.aboutToChange()
		self.database().connector.runVacuumAnalyze( (self.schemaName(), self.name) )
		# TODO: change only this item, not re-create all the tables in the schema/database
		self.schema().refresh() if self.schema() else self.database().refresh()

	def runAction(self, action):
		action = unicode(action)

		if action.startswith( "vacuumanalyze/" ):
			if action == "vacuumanalyze/run":
				self.runVacuumAnalyze()
				return True

		elif action.startswith( "rule/" ):
			parts = action.split('/')
			rule_name = parts[1]
			rule_action = parts[2]

			msg = u"Do you want to %s rule %s?" % (rule_action, rule_name)
			QApplication.restoreOverrideCursor()
			try:
				if QMessageBox.question(None, "Table rule", msg, QMessageBox.Yes|QMessageBox.No) == QMessageBox.No:
					return False
			finally:
				QApplication.setOverrideCursor(Qt.WaitCursor)

			if rule_action == "delete":
				self.aboutToChange()
				self.database().connector.deleteTableRule(rule_name, (self.schemaName(), self.name))
				self.refreshRules()
				return True

		return Table.runAction(self, action)

	def tableFieldsFactory(self, row, table):
		return PGTableField(row, table)

	def tableConstraintsFactory(self, row, table):
		return PGTableConstraint(row, table)

	def tableIndexesFactory(self, row, table):
		return PGTableIndex(row, table)

	def tableTriggersFactory(self, row, table):
		return PGTableTrigger(row, table)

	def tableRulesFactory(self, row, table):
		return PGTableRule(row, table)

	def info(self):
		from .info_model import PGTableInfo
		return PGTableInfo(self)

	def tableDataModel(self, parent):
		from .data_model import PGTableDataModel
		return PGTableDataModel(self, parent)


class PGVectorTable(PGTable, VectorTable):
	def __init__(self, row, db, schema=None):
		PGTable.__init__(self, row[:-4], db, schema)
		VectorTable.__init__(self, db, schema)
		self.geomColumn, self.geomType, self.geomDim, self.srid = row[-4:]

	def info(self):
		from .info_model import PGVectorTableInfo
		return PGVectorTableInfo(self)

	def runAction(self, action):
		if PGTable.runAction(self, action):
			return True
		return VectorTable.runAction(self, action)

class PGRasterTable(PGTable, RasterTable):
	def __init__(self, row, db, schema=None):
		PGTable.__init__(self, row[:-6], db, schema)
		RasterTable.__init__(self, db, schema)
		self.geomColumn, self.pixelType, self.pixelSizeX, self.pixelSizeY, self.isExternal, self.srid = row[-6:]
		self.geomType = 'RASTER'

	def info(self):
		from .info_model import PGRasterTableInfo
		return PGRasterTableInfo(self)

	def gdalUri(self):
		uri = self.database().uri()
		schema = ( u'schema=%s' % self.schemaName() ) if self.schemaName() else ''
		gdalUri = u'PG: dbname=%s host=%s user=%s password=%s port=%s mode=2 %s table=%s' % (uri.database(), uri.host(), uri.username(), uri.password(), uri.port(), schema, self.name)
		return QString( gdalUri )

	def mimeUri(self):
		uri = u"raster:gdal:%s:%s" % (self.name, self.gdalUri())
		return QString( uri )
	
	def toMapLayer(self):
		from qgis.core import QgsRasterLayer 
		rl = QgsRasterLayer(self.gdalUri(), self.name)
		if rl.isValid():
			rl.setContrastEnhancementAlgorithm("StretchToMinimumMaximum")
		return rl

class PGTableField(TableField):
	def __init__(self, row, table):
		TableField.__init__(self, table)
		self.num, self.name, self.dataType, self.charMaxLen, self.modifier, self.notNull, self.hasDefault, self.default, typeStr = row
		self.primaryKey = False

		# get modifier (e.g. "precision,scale") from formatted type string
		trimmedTypeStr = QString(typeStr).trimmed()
		regex = QRegExp( "\((.+)\)$" )
		startpos = regex.indexIn( trimmedTypeStr )
		if startpos >= 0:
			self.modifier = regex.cap(1).trimmed()
		else:
			self.modifier = None

		# find out whether fields are part of primary key
		for con in self.table().constraints():
			if con.type == TableConstraint.TypePrimaryKey and self.num in con.columns:
				self.primaryKey = True
				break


class PGTableConstraint(TableConstraint):
	def __init__(self, row, table):
		TableConstraint.__init__(self, table)
		self.name, constr_type, self.isDefferable, self.isDeffered, columns = row[:5]
		self.columns = map(int, columns.split(' '))
		self.type = TableConstraint.types[constr_type]   # convert to enum

		if self.type == TableConstraint.TypeCheck:
			self.checkSource = row[5]
		elif self.type == TableConstraint.TypeForeignKey:
			self.foreignTable = row[6]
			self.foreignOnUpdate = TableConstraint.onAction[row[7]]
			self.foreignOnDelete = TableConstraint.onAction[row[8]]
			self.foreignMatchType = TableConstraint.matchTypes[row[9]]
			self.foreignKeys = row[10]


class PGTableIndex(TableIndex):
	def __init__(self, row, table):
		TableIndex.__init__(self, table)
		self.name, columns, self.isUnique = row
		self.columns = map(int, columns.split(' '))


class PGTableTrigger(TableTrigger):
	def __init__(self, row, table):
		TableTrigger.__init__(self, table)
		self.name, self.function, self.type, self.enabled = row

class PGTableRule(TableRule):
	def __init__(self, row, table):
		TableRule.__init__(self, table)
		self.name, self.definition = row