"""
Convert pd.DataFrame with coordinates to KML file

Created on 12-05-2013. Migrated to Acequia on 27-06-2020
Depends on package SimpleKML

"""

import os
import os.path
import warnings
from collections import OrderedDict

import pandas as pd
from pandas import DataFrame, Series
import numpy as np
#import simplekml as skml
from acequia.geo.coordinate_conversion import CrdCon as CrdCon
import acequia as aq

sep = ','


class WpKml:
    """ Save Pandas DataFrame with waypoints in RD-coordinates to KML """

    iconshapes = {'square' : 'http://maps.google.com/mapfiles/kml/shapes/placemark_square.png',
                  'circle' : 'http://maps.google.com/mapfiles/kml/shapes/placemark_circle.png',
                  'cross'  : 'http://maps.google.com/mapfiles/kml/shapes/cross-hairs.png'}

    colors = {
      'white'       : '#FFFFFF',
      'brightgreen' : '#33FF00',
      'mossgreen'   : '#CCFF00',
      'deepyellow'  : '#FFFF00',
      'darkyellow'  : '#FFCC00',
      'orange'      : '#FF6600',
      'lightblue'   : '#99CCFF',
      'deepblue'    : '#0000FF',
      'brightpurple': '#CC00CC',
      'darkpurple'  : '#6600CC',
      'pink'        : '#FF00FF',
      'brightred'   : '#FF0000'}


    _default_styledict = OrderedDict([
        ('1',{'iconshape':'square','iconscale':1.0,'iconcolor':'#0c7cac',
              'labelscale':0.7,'labelcolor':'#FFFFFF'}),
        ('2',{'iconshape':'square','iconscale':1.0,'iconcolor':'#ac0b35',
              'labelscale':0.7,'labelcolor':'#FFFFFF'}),
        ('3',{'iconshape':'square','iconscale':1.0,'iconcolor':'#9e00b5',
              'labelscale':0.7,'labelcolor':'#FFFFFF'}),
        ('4',{'iconshape':'square','iconscale':1.0,'iconcolor':'#54a910',
              'labelscale':0.7,'labelcolor':'#FFFFFF'}),
        ('5',{'iconshape':'square','iconscale':1.0,'iconcolor':'#f69601',
              'labelscale':0.7,'labelcolor':'#FFFFFF'}),
        ('6',{'iconshape':'square','iconscale':1.0,'iconcolor':'#0011ff',
              'labelscale':0.7,'labelcolor':'#FFFFFF'}),
        ('7',{'iconshape':'square','iconscale':1.0,'iconcolor':'#ff00c1',
              'labelscale':0.7,'labelcolor':'#FFFFFF'}),
        ('9',{'iconshape':'square','iconscale':1.0,'iconcolor':'#ffff00',
              'labelscale':0.0,'labelcolor':'#FFFFFF'}),
        ])


    missing_style = { 'iconshape':'cross',
                      'iconscale':1.0,
                      'iconcolor':'#FFFFFF',
                      'labelscale':0.7,
                      'labelcolor':'#FFFFFF'}


    def __repr__(self):
        return 'Save Waypoints to KML'


    def __init__(self,wplist,label=None,xcoor=None,ycoor=None,
        styledict=None, stylecol=None):
        """Create KML file object from pd.DataFrame with waypoints


        Parameters
        ----------
        wplist : pd.DataFrame
            points with coordinates given in Standard Dutch Grid
        label : str
            columnname in wplist used for naming kml waypoints
        xcoor : float, optinal
            xcoordinate in RD
        ycoor : float, optional
            ycoordinate in RD
        styledic : dictionary, optional
            style definitions for each marker
        stylecol : str, optional
            column name for assigning styles


        Examples
        --------
        >>>sourcepath = '.\\testdata\\waypoints\\pmgloc.csv'
        >>>pmgloc = pd.read_csv(sourcepath)
        >>>pts = WpKml(pmgloc,label="nitgcode",xcoor="xcr",ycoor="ycr")
        >>>outdir = '.\\output\\kml\\'
        >>>pts.writekml(f'{outdir}pmv2019-noformatting.kml')

        >>>styledict_truefalse = {
                "true":
                    {"iconshape":"circle","iconscale":1.0,
                     "iconcolor":"#0000FF","labelscale":0.7,
                     "labelcolor":"FFFFFF"}),
                {"false":
                    {"iconshape":"circle","iconscale":1.0,
                     "iconcolor":"#FF00FF","labelscale":0.7,
                     "labelcolor":"FFFFFF"},}
        >>>pts = WpKml(pmgloc,label="nitgcode",xcoor="xcr",ycoor="ycr",
                       styledict=styledict_truefalse, stylecol="ispmg")
        >>>pts.writekml(f'{outdir}pmv-styledict-and-stylecol.kml')


        Notes
        -----
        If no values for xcoor,ycoor are given, column names with
        coordinates will be inferred from wplist column names.
        
        """

        self.kml = skml.Kml()
        self.defaultstyle = self._basestyle()

        if not isinstance(wplist, pd.DataFrame):
            raise TypeError('Variable wplist must be a pandas dataframe.')        

        if wplist.empty:
            raise ValueError('Variable wplist cannot be empty.')

        self.wplist = wplist.copy()
        self.wplist.columns = self.wplist.columns.str.lower()

        # set coordinates
        self.xcoor,self.ycoor = self._coordinate_columns(xcoor,ycoor)

        notlon = 'lon' not in self.wplist.keys()
        notlat = 'lat' not in self.wplist.keys()
        if notlon or notlat:
            self.wplist['lon'],self.wplist['lat'] = self.wgs84_coords()

        # set label column name
        if label is None:
            self.label = self.wplist.keys()[0]

        if label.lower() in self.wplist.keys():
            self.label = label.lower()
        else:
            firstcol = self.wplist.keys()[0]
            self.label = firstcol
            msg = ''.join(
                  f'Value {label} for label column not found in ',  
                  f'{self.wplist} column names. Column {firstcol} is ',
                  f'used for KML labels.')
            warnings.warn(msg,UserWarning)

        if stylecol is not None:
            stylecol = stylecol.lower()

        # set kml waypoint styles
        # TODO: Test function _validate_styledict
        self.styledict, self.stylecol = self._validate_styledict(styledict,stylecol)
        self.pointstyles = OrderedDict()
        for iconkey,icondef in self.styledict.items():
            basestyle = self._basestyle()
            self.pointstyles[iconkey] = self._changestyle(basestyle,icondef)


    def _coordinate_columns(self,xcoor=None,ycoor=None):
        """Return names of columns with xcoor,ycoor """

        if xcoor is None:
            xlist = ['xcoor','xrd','x']
            for x in xlist:
                if x in self.wplist.keys(): 
                    xcol = x
        else:
            if xcoor.lower() in self.wplist.keys(): 
                xcol = xcoor.lower()
            else:
                msg = f'No column with xcoor value available.'
                raise ValueError(msg)

        if ycoor is None:
            ylist = ['ycoor','yrd','y']
            for y in ylist:
                if y in self.wplist.keys(): 
                    yxol = y
        else:
            if ycoor.lower() in self.wplist.keys():
                ycol = ycoor.lower()
            else:
                msg = f'No column with ycoor value available.'
                raise ValueError(msg)

        return xcol,ycol


    def wgs84_coords(self):
        """Return lon and lat as ps.Series

        Returns
        -------
        lon,lat
        """
        crdcon = CrdCon()
        lon = self.wplist.apply(
                lambda x: crdcon.RDtoWGS84(float(x[self.xcoor]),
                float(x[self.ycoor]))['Lon'],axis=1)
        lat = self.wplist.apply(
                lambda x: crdcon.RDtoWGS84(float(x[self.xcoor]),
                float(x[self.ycoor]))['Lat'],axis=1)
        return lon,lat


    def default_styledict(self):
        """Return default styledict """
        return self._default_styledict


    def _validate_styledict(self,styledict,stylecol):
        """ validate styledict and stylecol """

        if (stylecol is not None) and (styledict is not None):

            # fill nans in column stylecol
            nanrows = self.wplist[self.wplist[stylecol].isnull()]
            if len(nanrows) !=0:
                fillnans = self.wplist[stylecol].replace(
                            np.nan,'missing',inplace=False)
                self.wplist[stylecol] = fillnans
                styledict['missing'] = self.missing_style


            # check styledict for missing keys
            # change stylecol to None if keys are missing from styledict
            missing = list(set(self.wplist[stylecol].unique()) 
                           - set(styledict.keys()))
            if len(missing)!=0: #not all(elem in wplabels for elem in styledict.keys()):               
                msg = ''.join([
                        f'Custom style dictionary styledict is missing ',
                        f'labels from waypoint column {stylecol}. ',
                        f'No formatting applied.\\n',
                        f'Missing labels are: {missing}'])
                warnings.warn(msg,UserWarning)
                stylecol = None


        if (stylecol is not None) and (styledict is None):

            wplabels = self.wplist[stylecol].unique()
            defaultkeys = list(self._default_styledict.keys())
            if len(wplabels) <= len(defaultkeys):
                newstyles = []
                for i,wplabel in enumerate(wplabels):
                    newstyles.append((wplabel,self._default_styledict[defaultkeys[i]]))
                styledict = OrderedDict(newstyles)
                #self.pointstyles = self._styles_from_dict(self.styledict)

            else:
                lenwp = len(wplabels)
                lenkeys = len(defaultkeys)
                message = ''.join(
                  [f'\nTo many unique values in waypoint stylecolumn '
                   f'{stylecol}.\n',
                   f'Stylecolumn {stylecol} contains {lenwp} values.\n',
                   f'Default style definitions contains {lenkeys} values.\n',
                   f'No formatting of waypoints applied.\n',])
                warnings.warn(message,UserWarning)
                styledict = {'default' : self._default_styledict['1']}
                stylecol = None


        if (stylecol is None) and (styledict is not None):
            styledict = next(iter(styledict.values())) # abitrary single element from styledict

        if (stylecol is None) and (styledict is None):
                styledict = {'default' : self._default_styledict['1']}


        """
        elif ((self.stylecol is None) and isinstance(self.styledict,dict)):
        if (stylecol is None) and (styledict is not None):
        if isinstance(styledict,dict):
            self.pointstyles = self._styles_from_dict(styledict)
        """
        return styledict,stylecol


    def _styles_from_dict(self,icondict):
        """Return OrderedDict of KML pointstyles with keys from icondict
        as point style names 

        Parameters
        ----------
        icondict : dict
            valid iconstyle dictionary


        Returns
        -------
        Ordereddict with KML pointstyles

        """
        pointstyles = OrderedDict()
        for iconkey,icondef in icondict.items():
            pointstyles[iconkey] = self._changestyle(
                                   kmlstyle=self._basestyle(),
                                   icondef=icondef)
        return pointstyles


    def _basestyle(self,icondict=None):
        """ Return default KML icon style """

        basestyle = skml.Style()
        basestyle.labelstyle.color = skml.Color.hex(self.colors['white'])
        basestyle.labelstyle.scale = 0.7
        basestyle.iconstyle.icon.href = self.iconshapes['square']
        basestyle.iconstyle.scale = 0.9
        basestyle.iconstyle.color= skml.Color.hex('33FF33')

        # only show desciption in balloon
        basestyle.balloonstyle.text = r'$[description]' 

        if icondict is not None:
            basestyle = self._changestyle(kmlstyle=basestyle,
                                          icondef=icondict)

        return basestyle


    def _changestyle(self,kmlstyle=None,icondef={}):
        """ Change existing kml style with definitions given in dictionary

        Parameters
        ----------
        kmlstyle : skml.style
            existing skml style object
        icondef : dict
            dictionary with items and values to change

        Returns
        -------
        KML style object

        """

        if not isinstance(kmlstyle,skml.styleselector.Style):
            msg = f'Variable {kmlstyle} must be a simplekml style object.'
            raise TypeError(msg)

        if not isinstance(icondef,dict):
            msg = f'Variable {icondef} must be a dictionary not {type(icondef)}.'
            raise TypeError(msg)

        for itemkey in icondef.keys():

            if itemkey=='iconshape':
                icon_name = icondef['iconshape']
                kmlstyle.iconstyle.icon.href = self.iconshapes[icon_name]

            elif itemkey=='iconscale':
                kmlstyle.iconstyle.scale = icondef['iconscale']

            elif itemkey=='iconcolor':
                mycolor = icondef['iconcolor']
                if mycolor.startswith('#'): 
                    mycolor = mycolor[1:]               
                kmlstyle.iconstyle.color= skml.Color.hex(mycolor)

            elif itemkey=='labelcolor':
                labelcolor = icondef['labelcolor']
                if labelcolor.startswith('#'): 
                    mycolor = mycolor[1:]               
                kmlstyle.labelstyle.color = skml.Color.hex(labelcolor)

            elif itemkey=='labelscale':
                kmlstyle.labelstyle.scale = float(icondef['labelscale'])

        return kmlstyle


    def _addcustomstyle(self,name,colorname=None,hexcolor=None,
        iconshape=None,styledic=None):
        """Add new custom KML waypoint style to customstyles"""

        newstyle = self.basestyle()

        if isinstance(styledic,dict):

            newstyle.iconstyle.icon.href = self.icons[styledic['iconshape']]
            newstyle.iconstyle.scale = styledic['iconscale']

            mycolor = styledic['iconcolor']
            if mycolor.startswith('#'): 
                mycolor = mycolor[1:]               
            newstyle.iconstyle.color= skml.Color.hex(mycolor)

            labelcolor = styledic['labelcolor']
            if labelcolor.startswith('#'): 
                mycolor = mycolor[1:]               
            newstyle.labelstyle.color = skml.Color.hex(labelcolor)
            newstyle.labelstyle.scale = float(styledic['labelscale'])

        if colorname is not None:

            if colorname in self.colors.keys(): 
                hexcolor = self.colors[colorname]

            if hexcolor.startswith('#'): 
                hexcolor = int(hexcolor[1:])

            newstyle.iconstyle.color= skml.Color.hex(hexcolor)

        if iconshape is not None:

            if iconshape in self.icons.keys(): 
                iconshape = self.icons[iconshape]
            else: 
                iconshape = self.icons['square']
            newstyle.iconstyle.icon.href = iconshape

        self.customstyles[name] = newstyle
        return self.customstyles[name]


    def writekml(self,kmlfile,metadata=True,cols=None):
        """ Write list of waypoints in RD to KML

        Parameters
        ----------
        kmlname : str
            name of kml file
        metadata : True,False, optional
            add metadata in bollontext (True) or not (False)
        cols : list of str, optional
            list of column names to add to balloontext of waypoints
        """
        #TODO: check if kmlfile path exists

        # Make list of fields to write to balloontext
        ##self.ballooncols = cols


        for idx, wp in self.wplist.iterrows():
   
            pnt = self.kml.newpoint(name=wp[self.label], 
                        coords=[(wp['lon'],wp['lat'])])  # lon, lat optional height
            pnt.snippet.maxlines = 0
            pointname = r'<font color='#000' size='+1'><strong>'+wp[self.label]+'</strong></font><br>'

            # assign waypoint style
            if (isinstance(self.stylecol,str) and isinstance(self.styledict,dict)):
                pnt.style = self.pointstyles[wp[self.stylecol]]
            else:
                pnt.style = self._basestyle(icondict=self.styledict)

            if metadata is True:

                if cols is None:
                    cols = list(wp.index)
                else:
                    cols = [x.lower() for x in cols]
                fields = zip(wp[cols].index, wp[cols].values)

                for ifield,field in enumerate(fields):
                    if ifield==0: 
                        tekst = field[0]+': '+str(field[1])+'<br>'
                    else: 
                        tekst = tekst + field[0]+': '+str(field[1])+'<br>'
            
                # save balloontext to kml
                pnt.description = tekst

        self.kml.save(kmlfile)

        return self.kml

