# -*- coding: utf-8 -*-
"""
    Utilidades para trabajar con QGis.
    
    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.
    
    @copyright: (C) 2011 by J. Gaspar Mora Navarro
    @contact: upvdelprop@gmail.com
    @author: J. Gaspar Mora navarro.
    @organization: U.P. Valencia. Dep Ing Cart. Geod. y Fotogrametria
    @version: 1
    @summary: Modulo que contiene utilidades de uso general para trabajar con QGis
"""

import sys
import os
import copy

"""
sys.path.append("C:\eclipse\plugins\org.python.pydev.debug_2.3.0.2011121518\pysrc")
from pydevd import *
"""

__docformat__ = "epytext"

from PyQt4 import QtCore, QtGui
import qgis.core
import qgis


class UtilidadesQgs(object):
    """
    Clase con utilidades para trabajar con QGis. Todos los métodos llaman
    antes a self.actualizaIface, que actualiza los datos de qgis.
    """
    def __init__(self, iface):
        """
        Constructor
        
        @type  iface: QgisInterface
        @param iface: Interface expuesta por el plugin. Es la que da acceso QGis.
        """
        self.iface=iface
        self.mapCanvas=iface.mapCanvas()
        self.layers=self.mapCanvas.layers()
    
    def actualizaIface(self):
        """
        Actualiza los datos de qgis. Llamar a este método antes de usar
        las funciones.
        """
        self.iface=qgis.utils.iface
        self.mapCanvas=self.iface.mapCanvas()
        self.layers=self.mapCanvas.layers()
        
    def muestra_mensaje(self, titulo, mensaje):
        """
        Muestra cuadro de diálogo con un mensaje en modo modal
        @type  titulo: string
        @param titulo: Titulo del mensaje.
        @type  mensaje: string
        @param mensaje: mensaje mostrado.
        """
        QtGui.QMessageBox.information(self.iface.mainWindow(),titulo, mensaje,1)
        
    def get_capa_nombre(self, nombreCapa):
        """
        Devuelve la referencia a a capa que coincide con el nombre proporcionado
        No utiliza el objeto self.iface porque puede estar anticuado y no reflejar
        los cambios hechos en qgis. Utilizo self.iface=qgis.utils.iface, con lo que
        actualizo los datos de qgis
        @type  nombreCapa: string
        @param nombreCapa: nombre de la capa a retornar. Dan igual las mayúsculas o minúsculas.
        @return: la referencia a la capa buscada o None, si no la encuentra
        """
        self.actualizaIface()
        for i in range(len(self.layers)):        
            layer=self.layers[i]
            nomCapaQgs=layer.name().toLower()
            nomCapaMin=nombreCapa.lower()
            if nomCapaQgs==nomCapaMin:
                return layer

    def get_capa_nombre2(self, nombreCapa):
        """
        Devuelve la referencia a a capa que coincide con el nombre proporcionado
        La diferencia con get_capa_nombre es que get_capa_nombre no encuentra las capas
        desactivadas y get_capa_nombre2 sí.
        No utiliza el objeto self.iface porque puede estar anticuado y no reflejar
        los cambios hechos en qgis. Utilizo self.iface=qgis.utils.iface, con lo que
        actualizo los datos de qgis
        @type  nombreCapa: string
        @param nombreCapa: nombre de la capa a retornar. Dan igual las mayúsculas o minúsculas.
        @return: la referencia a la capa buscada o None, si no la encuentra
        """

        dicLayers=qgis.core.QgsMapLayerRegistry.instance().mapLayers()
        for layer in dicLayers.values():        
            nomCapaQgs=layer.name().toLower()
            nomCapaMin=nombreCapa.lower()
            if nomCapaQgs==nomCapaMin:
                return layer
            
    def sel_eltos_capa_varias_cond(self,nomCapaQgis,dicCondiciones,seleccionarSoloUno=False):
        """
        Selecciona los elementos de la capa nomCapaQgis que cumplan
        las condiciones de dicCondiciones. Los valores de las condiciones
        deben ses strings unicode, utf-8
        Si dicCondiciones es undiccionario vacío, selecciona todos los elementos
        de la capa.
        Si seleccionarSoloUno es True, selecciona solo el primero y no sigue
        buscando.
        @return: Devuelve una lista con los ids de los elementos seleccionados, None, si no hay ninguno, o 
        genera una excepcion
        """
        n=len(dicCondiciones)
        if n==0:#seleccionar toda la capa
            qSet=self.sel_eltos_capa_una_cond(nomCapaQgis, dicCondicion={}, seleccionarSoloUno=seleccionarSoloUno)
            return qSet
        elementos=dicCondiciones.items()#lista de listas clave-valor
        elemento=elementos.pop(0)#primera condicion nomcampo-valor. pop la elimina de elementos
        dic={}
        dic[elemento[0]]=elemento[1]
        #primera seleccion de elementos de la capa con la primera condicion
        qSet=self.sel_eltos_capa_una_cond(nomCapaQgis, dicCondicion={}, seleccionarSoloUno=seleccionarSoloUno)
        if qSet==None:
            return None
        for elemento in elementos:
            dic={}
            dic[elemento[0]]=elemento[1]
            #primera seleccion de elementos de la capa con la primera condicion
            qSet=self.sel_eltos_capa_una_cond(nomCapaQgis, dicCondicion=dic, seleccionarSoloUno=seleccionarSoloUno)            
            if qSet==None:
                return None       
        return qSet
    
    def sel_eltos_capa_una_cond(self,nomCapaQgis,dicCondicion={},seleccionarSoloUno=False): 
        """
        Selecciona, de los elementos de una capa, los que cumplan una condición.
        Si dicCondicion={}, selecciona todos los elementos.
        Si seleccionarSoloUno=True, cuando seleccione el primero que cumpla, 
        lo selecciona, no sigue comprobando y se sale.
        Devuelve una lista con los ids de los elementos seleccionados, None, si no hay ninguno, o 
        genera una excepcion
        """     
        capa=self.get_capa_nombre(nomCapaQgis)
        if capa==None:
            raise Exception("La capa " + nomCapaQgis + " no existe.")
        provider = capa.dataProvider()
        n=len(dicCondicion)
        if n>1:
            raise Exception("El numero de condiciones en la funcion UtilidadesQgs.sel_eltos_capa_una_cond no puede ser mayor que uno.")
        feat=qgis.core.QgsFeature()
        qSet=[]#lista con los ids de los elementos que se seleccionaran
        if n==0:
            #seleccionar todos los elementos de la capa
            provider.select()
            while provider.nextFeature(feat):
                qSet.append(feat.id())
        else:
            nomCampo=dicCondicion.keys()[0]
            indice = provider.fieldNameIndex(nomCampo)
            if indice == -1:
                raise Exception("El campo " + nomCampo + " no existe en la capa " + nomCapaQgis)
            provider.select([indice])
            while provider.nextFeature(feat):
                dicAtributos = feat.attributeMap()#extrae los atributos a comparar
                #es un  diccionario numero:atributo
                atributo=dicAtributos.get(indice)
                valor=atributo.toString()
                valor=unicode(valor,"utf-8")
                if valor==dicCondicion.get(nomCampo):
                    qSet.append(feat.id())
                    if seleccionarSoloUno==True:
                        break
        n=len(qSet)
        if n>0:
            capa.setSelectedFeatures(qSet)
            return qSet
        else:
            return None
        
    def sel_eltos_de_sel_una_cond(self,nomCapaQgis,dicCondicion,seleccionarSoloUno=False):
        """
        Selecciona, de los elementos seleccionados de una capa, los que
        cumplan una condición
        La el valor de la condición debe ser una cadena unicode.
        Si no hay condición, se quedan seleccionados los mismos elementos.
        Devuelve una lista con los ids de los elementos seleccionados, None, si no hay ninguno, o 
        genera una excepcion
        """
        capa=self.get_capa_nombre(nomCapaQgis)
        if capa==None:
            raise Exception("La capa " + nomCapaQgis + " no existe.")
        n=len(dicCondicion)
        if n>1:
            raise Exception("Solo se permite una condicion en UtilidadesQgs.sel_eltos_de_sel_una_cond")
        if n==0:
            #se deja la misma selección
            return
        nombreCampo=dicCondicion.keys()
        valorCampo=dicCondicion.get(nombreCampo)
        try:
            lDic=self.get_attrSeleccionCapa(nomCapaQgis)
        except Exception,e:
            raise Exception(e.message)
        qSet=[]
        for dic in lDic:
            valor=dic.get(nombreCampo)
            if valor==valorCampo:
                qSet.append(dic.get("featureId"))
                if seleccionarSoloUno==True:
                    break
        n=len(qSet)
        if n>0:
            capa.setSelectedFeatures(qSet)
            return qSet
        else:
            return None
    def get_attrSeleccionCapa(self, nombreCapa, listaCampos,geom=False):
        """
        Devuelve una lista de diccionarios con todos los valores
        de los campos de los elementos seleccionados. Cada elemento
        genera un diccionario con sus valores. El diccionario tiene
        como clave el nombre del campo nombreCampo=valor
        Si geom=True, devuelve tambien las coordenadas de la geometria en wkt
        @type nombreCapa: string
        @param nombreCapa: Nombre de la capa cuyos elementos estan selecc
        @return: lista de diccionarios. Un diccionario por cada elemento
            seleccionado de la capa. Las claves del diccionario son los 
            elementos de la capa. Todos los valores estan en forma de string
        """
        vLayer=self.get_capa_nombre(nombreCapa)
        if vLayer==None:
            raise Exception("No existe la capa " + nombreCapa)            
        nF = vLayer.selectedFeatureCount()
        if nF==0:
            raise Exception("El numero de elementos seleccionados de la capa " + nombreCapa + " es cero.")
        seleccion = vLayer.selectedFeatures()
        lista=[]
        for feat in seleccion:
            dic=self.get_attrElementoCapa(feat, vLayer, listaCampos, geom)
            lista.append(dic)
        return lista

    
    def get_attrElementoCapa(self, feat, vLayer, listaCampos,geom=False):
        """
        Devuelve un diccionario con los valores de los campos solicitados
        en listaCampos. El diccionario tiene
        como clave el nombre del campo nombreCampo=valor
        Si geom=True, devuelve tambien las coordenadas de la geometria en wkt
        @type feat: elemento de qgis
        @param feat: se obtiene de una seleccion de elementos de una capa con seleccion = vLayer.selectedFeatures(). 
            Los elementos de pueden extraer con: for feat in seleccion:
        @type vLayer: qgsVectorLayer
        @param vLayer: capa que contiene el elemento
        @return: lista de diccionarios. Un diccionario por cada elemento
            seleccionado de la capa. Las claves del diccionario son los 
            elementos de la capa. Todos los valores estan en formato unicode.
            Las claves featureId y geom, contienen el identificador del objeto
            y el la geometria en wkt. 
        @raise exception: la descripcion del error
        """       

        provider = vLayer.dataProvider()
        allAttrs = provider.attributeIndexes()
        provider.select(allAttrs)#selecciona todos los atributos de la capa
        attrs = feat.attributeMap()#extrae todos los atributos
        dic={}#creo el diccionario
        dic["featureId"]=feat.id()
        for nomCampo in listaCampos:
            fldDesc = provider.fieldNameIndex(nomCampo)#saco el indice del campo que busco
            if fldDesc==-1:
                raise Exception("El campo " + nomCampo + " no existe en la capa " + vLayer.name())
            attr=attrs.get(fldDesc)#saco el atributo que busco
            valor= unicode(attr.toString(),"utf-8")
            dic[nomCampo]=valor#añado al diccionario la clace y su valor
        if geom==True:#Añado tambien la geometria en wkt
            geom = feat.geometry()
            if geom is None:
                raise Exception("Los elementos deben estar visibles en el mapa")
            geomT=str(geom.exportToWkt())
            dic["geom"]=geomT
        return dic
    
    def get_tipoGeomNomCapa(self,nomCapa):
        """
        Recibe el nombre de una capa vectorial y devuelve Point, Line, Polygon o Exception 
        si no es de ningún tipo anterior.
        """
        capa=self.get_capa_nombre(nomCapa)
        if capa==None:
            return Exception("La capa " + nomCapa + " no esta activada o no existe en este proyecto.")
        return self.get_tipoGeomCapa(capa)
    
    def get_tipoGeomCapa(self,capa):
        """
        Recibe un objeto QgsVectorLayer y devuelve Point, Line, Polygon o Exception 
        si no es de ningún tipo anterior.
        """
        if capa==None:
            return Exception("La capa es None.")

        tipoGeomCapaQgs=str(capa.geometryType())#devuelve 0,1,2. Que sigunifica Point, Line, Polygon
        if tipoGeomCapaQgs=="0":
            return "Point"
        elif tipoGeomCapaQgs=="1":
            return "Line"
        elif tipoGeomCapaQgs=="2":
            return "Polygon"
        else:
            return Exception("El tipo de capa no es ni Point, ni Line, ni Polygon")
        
    def cargarCapaPostgis(self,borrarSiExiste ,host, database,usuario,password,port,nombreCompletoTabla,campoGeom,condicionSeleccion,campoGid,almacenarCredenciales=False):
        """
        Carga una capa de postgis.
        @type borrarSiExiste: boolean
        @param borrarSiExiste: Si es true, si existe la capa, la borra la capa y la vuelve a cargar: 
        @type host: string
        @param host: direccción ip de del servidor que tiene instalado postgresql
        @type database: string
        @param database: nombre de la base de datos
        @type usuario: string
        @param usuario: nombre del usuario
        @type password: string
        @param password: contraseña
        @type port: string
        @param port: puerto de la conexión a postgres
        @type nombreCompletoTabla: string
        @param nombreCompletoTabla: nombre de la tabla, incluído el esquema, aunque sea public.
        @type campoGeom: string
        @param campoGeom: nombre del campo de geometría
        @type condicionSeleccion: string
        @param condicionSeleccion: Condición de selección de los registros de la tabla. Ejemplo:
            consMuni=unicode("id_trabajo in (select id_trabajo from ed_comun.ed_trabajos where municipio='{0}')","utf-8")
            consulta=consMuni.format(municipio)
            Si el valor del campo es numérico hay que eliminar las comillas simples: '{0}' pasa a -->{0}
        @type campoGid: string
        @param campoGid: nombre del campo clave de la tabla
        @type almacenarCredenciales: boolean
        @param almacenarCredenciales: Si es true almacena el usuario y la contraseña en el proyecto
            Qgis. Lo almacena en texto plano, por lo que es peligroso. Si es false,
            no almacena esta información en el proyecto. Solicita usuario y contraseña, una vez, 
            al abrir el proyecto, y luego lo usa para el resto de capas. 
        @return: Si la capa existe, devuelve la capa, sin modificar. Si no existía devuelve la nueva capa.
        @raise exception: Genera un error en caso de error
        """
        nombreCapa=nombreCompletoTabla.split(".")[1]
        capa=self.get_capa_nombre2(nombreCapa)
        if capa !=None:
            if borrarSiExiste==True:
                if capa.isEditable()==True:
                    raise Exception("Debe detener la edicion en la capa " + nombreCapa)
                qgis.core.QgsMapLayerRegistry.instance().removeMapLayer(capa.getLayerID())
            else:
                return capa
            
        uri = qgis.core.QgsDataSourceURI()
        #uri.setConnection(host, port, database, usuario, password)
        if almacenarCredenciales==False:
            uri.setConnection(host, port, database,"","")
        else:
            uri.setConnection(host, port, database, usuario, password)
        # set database schema, table name, geometry column and optionaly subset (WHERE clause)
        nombres=nombreCompletoTabla.split(".")
        if len(nombres) < 2:
            raise Exception("Falta el nombre del esquema en el nombre de la tabla")
        esquema=nombres[0]
        tabla=nombres[1]
        
        uri.setDataSource(esquema, tabla, campoGeom, condicionSeleccion,campoGid)
        #uri.setEncodedUri(uri.uri())
        #urie=uri.encodedUri()
        #vlayer=qgis.core.QgsVectorLayer(urie, tabla, "postgres")
        vlayer = qgis.core.QgsVectorLayer(uri.uri(), tabla, "postgres")
        if not vlayer.isValid():
            raise IOError, "Fallo al abrir la capa espacial"
        # add layer to the registry
        qgis.core.QgsMapLayerRegistry.instance().addMapLayer(vlayer)
        return vlayer
        # set extent to the extent of our layer
        #self.iface.mapCanvas().setExtent(vlayer.extent())