# -*- coding: utf-8 -*-
"""
Copyright (c) 2016 Biuro Urządzania Lasu i Geodezji Leśnej

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 
to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 
OTHER DEALINGS IN THE SOFTWARE.
"""

from qgis.core import QgsVectorLayer,QgsProject, QgsFeatureRequest, QgsVectorFileWriter, QgsMapLayer 
from qgis.core import QgsField, QgsFields, QgsSpatialIndex, QgsGeometry, QgsFeature
from PyQt5.QtCore import QVariant

from shapely.geometry import Polygon, MultiPolygon 
from shapely.ops import cascaded_union
#from shapely.wkb import loads
from shapely.wkt import loads
from shapely.wkt import dumps
from Mapa_PU.kontroler import komunikaty
import processing

"""Pakiet rozszerzajacy obiekty warstwy o metody"""
#marcin

def rozszerzWarstwe():
        """Rozszerza warstwę o dodatkowe metody"""
        #Marcin Śliwa
        try:
           
            QgsVectorLayer.jestAtrybut = jestAtrybut
            QgsVectorLayer.listaNazwPol= listaNazwPol
            QgsVectorLayer.kontrolaObecnosciPola = kontrolaObecnosciPola
            QgsVectorLayer.listaWartosciPola = listaWartosciPola
            QgsVectorLayer.listaNazwPol = listaNazwPol
            QgsVectorLayer.liczbaRekordow = liczbaRekordow
            QgsVectorLayer.idxPola = idxPola
            QgsVectorLayer.usunRekordy=usunRekordy
            QgsVectorLayer.listaFeatureId = listaFeatureId
            QgsVectorLayer.listaFeatureId2 = listaFeatureId
            QgsVectorLayer.kasujWszystkieObiekty = kasujWszystkieObiekty
            QgsVectorLayer.usunWszystkieRekordy=usunWszystkieRekordy
            QgsVectorLayer.liczbaFeatureow=liczbaFeatureow
            QgsVectorLayer.pytanieLikeIdRekordy=pytanieLikeIdRekordy         
            QgsVectorLayer.featureListById=featureListById
            QgsVectorLayer.slownikPol=slownikPol
          
            QgsVectorLayer.wybierzFeaturey=wybierzFeaturey
            QgsVectorLayer.utworzKopieWarstwy=utworzKopieWarstwy
            QgsVectorLayer.utworzKopieWarstwyNazwa=utworzKopieWarstwyNazwa
            QgsVectorLayer.agregujWarstwe=agregujWarstwe
          
        except Exception as e:
            komunikaty.blad_systemowy(e)

def jestAtrybut(self,nazwa):
        """Sprawdza czy warstwa zawiera dany atrybut
        :param nazwa:nazwa warstwy
        :return wynik kontroli
        """
        #Marcin Śliwa
        try:
            if self==None:
                return False
            for pole in self.fields():
                if pole.name()==nazwa:
                    return True
            return False
        except Exception as e:
            komunikaty.blad_systemowy(e)

def listaNazwPol(self):
        """Lista Nazw Pol w Warstwie"""
        if self.isValid():
            for field in self.fields():
                yield  field.name()

def kontrolaObecnosciPola(self, nazwaPola=''):
        """Sprawdza obecnosc pola w warstwie o podanej nazwie
        :param nazwaPola: nazwa pola kontrolowanego
        :return wynik kontroli
        """
        try:
            if self.fields().indexFromName(nazwaPola) > 0:
                return True
            return False
        except Exception as e:
            komunikaty.blad_systemowy(e)
  
def listaWartosciPola(self,nazwa_pola):
        """lista wartosci pola dla podanej nazwy atrybutu warstwy bez wartości NULL
        :param nazwa_pola:nazwa pola
        """
        # Michał
        request = QgsFeatureRequest()
        request.setFlags(QgsFeatureRequest.NoGeometry)   #bez geometrii
        for feature in self.getFeatures(request):
            if feature.isValid():
                if feature[nazwa_pola]!=None:
                    yield feature[nazwa_pola]

def listaNazwPol(self):
        # Michał
        """Lista Nazw Pol w Warstwie"""
        for field in self.fields():
            yield  field.Name()

def liczbaRekordow(self):
        """Liczba Rekordow w Warstwie
        :return liczba rekordów
        """
        request = QgsFeatureRequest()
        request.setFlags(QgsFeatureRequest.NoGeometry)
        return self.featureCount()

def liczbaFeatureow(self):
        """Liczba feature'ow w warstwie.
           Oryginalna metoda featureCount() nie daje poprawnej wartosci po operacji kasowania obiektów
           :return liczba obiektów
           """
           # Michał
        count = 0
        request = QgsFeatureRequest()
        request.setFlags(QgsFeatureRequest.NoGeometry)
        for feature in self.getFeatures(request):
            count += 1
            
        return count
        
def idxPola(self,nazwa=''):
    """Zwraca indeks pola w warstwie
    :param nazwa:nazwa pola
    :return indeks pola
    """
    #marcin
    try:
        return self.fields().indexFromName(nazwa)
    except Exception as e:
        komunikaty.blad_systemowy(e)
        
def listaFeatureId(self):
        """lista Id feture'ów w danej warstwie"""
        for feature in self.getFeatures():
            if feature.isValid():
                yield feature.id()
                
def listaFeatureId2(self):
        """lista Id feture'ów w danej warstwie
        :return lista
        """
        return [ id for id in self.allFeatureIds() ]
            
def kasujWszystkieObiekty(self,opis=u'Kasuję wszystko w warstwie.'):
        """Kasuje wszystko w warstwie. 
        :param opis: opis wyświetlany
        
        """
        #Michał 
        if self.startEditing():    
            
            self.beginEditCommand(opis)
            self.selectAll()
            fcount = self.selectedFeatureCount()
            
            self.deleteSelectedFeatures()
            
            self.endEditCommand()
            self.commitChanges()
            self.updateExtents()

        return fcount
    
def usunWszystkieRekordy(self):
    """Usówa wszystkie rekordy z wartswy"""
    #Marcin
    try:
        self.dataProvider().deleteFeatures(self.allFeatureIds())
        self.updateExtents()
    except Exception as e:
        komunikaty.blad_systemowy(e)

def usunRekordy(self,zakres):
    """Usówa rekordy w podanym zakresie id
    :param zakres:zakres id
    """
    #marcin
    try:
       self.dataProvider().deleteFeatures(zakres)
       
    except Exception as e:
        komunikaty.blad_systemowy(e)

# wzorzec zapytania do setFilterExpression  : (  u'"ADR_BDL" LIKE \'S610110032-052%\' '  )

def pytanieLikeIdRekordy(self, nazwaPola=u'', wartoscPola=u'', typ=u''):
    """ na podstawie nazwy jednego pola oraz wartości w nim wystepujacej zwraca generator id feature'ow 
    :param nazwaPola: nazwa pola
    :param wartoscPola: wartosc pola
    :param typ: typ
    """
    
    if  not nazwaPola or not wartoscPola or typ not in [u'',u'lewy',u'prawy'] :
        yield None
    
    def getLikeExpString():
        if typ == u'':
            return u' "'+nazwaPola+u'" LIKE \'%'+ wartoscPola +u'%\''   # ' "pole" LIKE '%string%' '
         
        elif typ == u'lewy':
            return u' "'+nazwaPola+u'" LIKE \'%'+ wartoscPola +u'\''    # ' "pole" LIKE '%string' '
        
        elif typ == u'prawy':
            return u' "'+nazwaPola+u'" LIKE \''+ wartoscPola +u'%\''    # ' "pole" LIKE 'string%' '
            
    request = QgsFeatureRequest().setFilterExpression( getLikeExpString() )
    for feature in self.dataProvider().getFeatures(request):
        yield feature.id()

def featureListById(self,fIds):
    
    """ na podstawie listy Id featureów daje generator featureów z warstwy """
    request = QgsFeatureRequest().setFilterFids(fIds)    
    for feature in self.dataProvider().getFeatures(request):
        yield feature

def slownikPol(self):
    
    """Tworzy słownik { nazwa atrybutu : id atrybutu wartstwy }
    :return słownik
    """
    return { nazwa:id for nazwa,id in self.dataProvider().fieldNameMap().items() }
  

def wybierzFeaturey(self,listaFeatures):
    """ Na podstawie listy Id ficzerów zwraca słownik z featurami 
    :return warstwa
    """
    #Michał
    if self.isValid():
        #_slownikpol = { nazwa:id for nazwa,id in self.dataProvider().fieldNameMap().items() }         
                       
        _warstwa = {}
        _warstwa.update({ self.name() : { 'features':{} }  }) 
               
        #pola_nazwy_dic =  self.dataProvider().fieldNameMap()           
        _warstwa.update({ 'fnames' : self.dataProvider().fieldNameMap()  }) 
    
        for feat in listaFeatures:
            _warstwa[self.name()]['features'][feat.id()]={}
            _warstwa[self.name()]['features'][feat.id()]['attributes']={}
                            
            for idx,attr in zip( self.attributeList() , feat.attributes()):
                
                _warstwa[self.name()]['features'][feat.id()]['attributes'][idx]=attr
                        
            _warstwa[self.name()]['features'][feat.id()]['geometry'] = feat.geometry()
                                            
    return _warstwa
            
def utworzKopieWarstwy(self):
    """Tworzy kopię warstwy na dysku oraz dodaje ją do QgsMapLayerRegistry"""
    #Michał
    dataProv = self.dataProvider()
    _newFilePath = self.source()[:-4]+'_COPY'+self.source()[-4:]
    QgsVectorFileWriter.writeAsVectorFormat(self, _newFilePath, "system", dataProv.crs(), driverName = "ESRI Shapefile")


def utworzKopieWarstwyNazwa(self,nowaNazwaWarstwy='NEW_COPY'):
    """Tworzy kopię warstwy na dysku oraz dodaje ją do QgsMapLayerRegistry"""
    #Michał
    dataProv = self.dataProvider()
    dirpath = '/'.join(self.source().split('/')[:-1])
    _newFilePath = dirpath + '/' +  nowaNazwaWarstwy + '.shp'          
    QgsVectorFileWriter.writeAsVectorFormat(self, _newFilePath, "system", dataProv.crs(), driverName = "ESRI Shapefile")

    
def agregujWarstwe(self,inpLayer,outLayer,nazwaPola='',sufixPola='',listaWartosciPola=[], nowePola = QgsFields(), pasekWiadomosc = None ):
    
    
        if nowePola.isEmpty():
            #nowePola = QgsFields()
            nowePola.append( QgsField("ID", QVariant.Int,"Integer",6) )
            nowePola.append( QgsField('ADR_BDL', QVariant.String,"String",25) )
        
        #Michał
        """ Agregacja po polu o zadanej nazwie na podstawie listy jego wartosci.
            Argumenty: 
                    -  warstwa wejsciowa,
                    -  warstwa wynikowa,
                    -  nazwa pola wg. którego będzie wyk. agregacja,
                    -  dodatkowy łancuch znakow dla nowych wartosci pola ( dopisek )
            Funkcja zaklada utworzenie pola o nazwie Id typu QInt oraz nowego pola typu QString o zadanej nazwie. 
        """
       
        def filtrPrawyLike(nazwaPola='', wartoscPola=''):
            """ "pole" =  'string%' 
            :return wynik zapytania
            """
            if len(nazwaPola)>0 and len(wartoscPola)>0:
                request = QgsFeatureRequest().setFilterExpression(u'"'+nazwaPola+u'" LIKE \''+wartoscPola+u'%\'')
                return request
            else: return     
        
        def areJoints(inpdct):
            """sprawdza czy sa nakladki wartosci w setach w slowniku {id:set([id,id,..])}
            :return wynik kontroli
            """
            for idz,setz in inpdct.items():
                for idw,setw in inpdct.items():
                    if idz != idw :
                        if not setz.isdisjoint(setw):
                            return True
            return False
        
        def joinSets(inpdct):
            """ Pobiera słownik id sasiedztw {id1: set([id1,id2,id3,id4,...]), id2: set([id1,id2,id3,id4,...]) }
                Zwraca set frozensetow id źródłowych przyleglości do scalenia funckją cascaded_union() """
            if inpdct:
                resdct = inpdct.copy()
                
                # set na wyniki:
                newsets = set()
                for idz,setz in resdct.items():
                
                    jnset = set()
                    jnset.update(setz)
                    
                    for idw,setw in resdct.items():    
                        if (idz != idw):
                            if not jnset.isdisjoint(setw):
                                jnset.update(setw)
            
                    newsets.add(frozenset(jnset))

                return newsets
        
        def przygotujNowePola(vector_layer,nowe_pola):
            
            #kasuje stare atrybuty w outLayer
            try:
                #if vector_layer.isValid() and not nowe_pola.isEmpty():
                if vector_layer.isValid(): 
                    vector_layer.startEditing()
                    layer_dp = vector_layer.dataProvider()
                    layer_dp.deleteAttributes(vector_layer.attributeList())
                    layer_dp.addAttributes(nowe_pola)
                    vector_layer.commitChanges()
                    
                    #outLayerDP.addAttributes([QgsField("ID", QVariant.Int,"Integer",6), QgsField('ADR_BDL', QVariant.String,"String",25) ])
                    #definicja pol dla tworzonych feature'ów.
                    #outFields = QgsFields()
                    #outFields.append(QgsField("ID", QVariant.Int,"Integer",6))
                    #outFields.append(QgsField(nazwaPola, QVariant.String,"String",25))
            
            except Exception as excp:
                #print u"Nie udalo sie przygotowac pol !\n" + excp 
                komunikaty.blad_systemowy(e,u"Nie udalo sie przygotowac pol !")            

        def zapiszFeature(warstwa,geometria,pola,**atrybuty):
            
            try:
                if warstwa.isValid() and geometria and atrybuty and not pola.isEmpty():    
                    # Nowy feature.
                    ofeat = QgsFeature()
                    ofeat.setFields(pola)
                
                    # Ustawiamy atrybuty.
                    for nazwa,wartosc in atrybuty.items():
                        if pola.exists(pola.indexFromName(nazwa)):
                            ofeat.setAttribute(nazwa,wartosc)
                                
                    # geometria dla nowego feature'a.
                    ofeat.setGeometry( geometria )
                    ofeat.setValid(True)
                
                    # ZAPIS feature'a:
                    warstwa.dataProvider().addFeatures([ofeat])
                    progressbar.setValue(newId)
                    
            except Exception as excp:
                #print u"Nie udało się zapisać feature'a" 
                komunikaty.blad_systemowy(e,u"Nie udało się zapisać feature'a")
                
        if inpLayer.isValid() and nazwaPola in [field.name() for field in inpLayer.fields()]  and outLayer.isValid() and nazwaPola and listaWartosciPola and not nowePola.isEmpty() and pasekWiadomosc:
                    
            przygotujNowePola(outLayer,nowePola)
            
            progressbar = pasekWiadomosc.utworzPasekPostepu(minimum=0, maximum=int(len(listaWartosciPola)/4))
            progressbar.setValue(0)
            
            # nowy ID     
            newId = 0
            for  wpola in  listaWartosciPola:
                #request = QgsFeatureRequest().setFilterExpression(u'"' + nazwaPola + u'" = \'' + wartpola + u'\'')
                     
                request = filtrPrawyLike(nazwaPola,wpola)
                features = { feature.id():feature for feature in inpLayer.getFeatures(request) }
                
                # utworzenie SPIndexu: 
                spindex1 = QgsSpatialIndex()
                                         
                # zaladunek SP indexu:
                for id,feat in features.items():
                    if not (feat.geometry().isEmpty()):    
                        spindex1.insertFeature(feat)
                        
                #  { id : set() } slownik setow sasiedztw z powtorzeniami:
                tidsets = {}    
                for id1,feature in features.items():
                    
                    ids_in_the_box = spindex1.intersects(feature.geometry().boundingBox())
                    tset = set()
                    tset.add(id1)
                    
                    for id2 in ids_in_the_box:
                        if feature.geometry().touches(features[id2].geometry()):
                            tset.add(id2)
                            
                    tidsets.update({id1:tset})

                del spindex1

                # wykrywanie wspolnych czesci i scalanie setow w grupy,
                # set(frozenset(), ... ) - struktura set frozensetow do usuniecia powtorzen (w srodku musi byc frozenset)
                tfsets = set()
                if (areJoints(tidsets)):           
                    tfsets = joinSets(tidsets)
                else:
                    #tfsets = joinSets(tidsets)
                    for setv in iter(tidsets.values()):#tidsets.itervalues():
                        tfsets.add(frozenset(setv))
            
                # obrobka geometrii na podstawie zbiorow id w strukturze set(frozenset(), ... )                            
                # docelowa lista poligonow do zapisu geometrii 
                mpolygons=[]
                for setids in list(tfsets):
                    
                    # lista poligonow do dissolve'a
                    polygons=[]
                    for id in list(setids):
                        
                        # zaladunek geometrii ze slownika features{} ( QGSGeometry -> Wkb -> obj. shapely )
                         #.asWkb()
                        
                        geom1 = loads(features[id].geometry().asWkt())
                        if geom1.geom_type == 'Polygon' or 'MultiPolygon':
                            polygons.append( geom1 )
                                            
                    # Złączam i dodaje do listy mpolygon wynik funkcji cascaded_union() ( z modułu shapely.ops )
                    if polygons:
                        mpolygons.append( cascaded_union(polygons) )
                    
                # Złączenie poligonów i/lub multipoligonów w jeden obiekt shapely. W zależności od argumentów jest to poligon lub multipoligon
                if mpolygons:
                    mpolygon = cascaded_union(mpolygons)
                
                # Serializacja wczesniej utworzonego obiektu do Wkt nast. utworzenie QgsGeometry.
                newAggrQgsPolygon = QgsGeometry.fromWkt(dumps(mpolygon))
                    
                # Ustawiamy atrybuty.
                # Nowy ID z licznika, ADR_BDL to oryginalny adres z wejsciowej listy adresow + sufix (string '-    -  ').
                newId += 1
                zapiszFeature(outLayer,newAggrQgsPolygon,nowePola,**{ 'ID': newId, nazwaPola : wpola + sufixPola })
                
            
            #pasekWiadomosc.wiadomoscInfo(u"Udało się",u"Poszło !")
            pasekWiadomosc.clear()
            return True
                  
        else:
           
            return False
   
