# Author:	Jachym Cepicky
#        	http://les-ejk.cz
#               jachym at les-ejk dot cz
# License:
#
# Web Processing Service implementation
# Copyright (C) 2006 Jachym Cepicky
#
# 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 program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301  USA


from pywps import config
import os
import urllib2
import logging
import tempfile

mapscript = False
gdal = False
try:
    from mapscript import *
    mapscript = True
except Exception, e:
    mapscript = False
    logging.info(
        "MapScript could not be loaded, mapserver not supported: %s" % e)

try:
    from osgeo import gdal
    from osgeo import ogr
    from osgeo import osr
except Exception, e:
    gdal = False
    logging.info(
        "osgeo package could not be loaded, mapserver not supported: %s" % e)


class UMN:
    """
    UMN MapServer Mapscript handler

    :param id: Integer process id

    .. attribute :: mapObj

        mapObject

    .. attribute :: mapFileName

        map file name

    .. attribute :: mapFileName

        map file name

    .. attribute :: sessionId

        process id from getSessionId()

    .. attribute :: dataset

        gdal.dataset or ogr.dataset

    .. attribute :: outputs

        associative array of outputs

    .. attribute :: process

        :class:`pywps.Process.WPSProcess`
    """

    mapObj = None
    mapFileName = None
    outputs = None
    process = None
    sessionId = None

    def __init__(self, process, sessId):

        if ((mapscript == False) or (gdal == False)):
            return

        tmp = os.path.basename(tempfile.mkstemp()[1])
        self.outputs = {}
        self.process = process
        self.sessionId = sessId
        self.projs = self.getProjs()
        self.mapObj = mapObj()
        self.mapObj.setExtent(-180, -90, 180, 90)  # irrelevant for WCS/WFS
        #self.mapObj.setProjection("+init=%s" % self.projs[0])
        self.mapObj.name = "%s-%s" % (self.process.identifier, self.sessionId)
        self.mapObj.setMetaData(
            "ows_title", config.getConfigValue("wps", "title"))
        self.mapObj.setMetaData(
            "wms_abstract", config.getConfigValue("wps", "abstract"))
        self.mapObj.setMetaData(
            "wcs_abstract", config.getConfigValue("wps", "abstract"))
        self.mapObj.setMetaData("wcs_label", process.title)
        self.mapObj.setMetaData(
            "wfs_abstract", config.getConfigValue("wps", "abstract"))
        self.mapObj.setMetaData(
            "ows_keywordlist", config.getConfigValue("wps", "keywords"))
        self.mapObj.setMetaData(
            "ows_fees", config.getConfigValue("wps", "fees"))
        self.mapObj.setMetaData("ows_accessconstraints",
                                config.getConfigValue("wps", "constraints"))
        self.mapObj.setMetaData("ows_contactorganization",
                                config.getConfigValue("provider", "providerName"))
        self.mapObj.setMetaData(
            "ows_contactperson", config.getConfigValue("provider", "individualName"))
        self.mapObj.setMetaData(
            "ows_contactposition", config.getConfigValue("provider", "positionName"))
        self.mapObj.setMetaData("ows_enable_request", "*")
        self.mapObj.debug = 5
        self.mapObj.setConfigOption("MS_ERRORFILE", "stderr")

        phone = config.getConfigValue("provider", "phoneVoice")
        if phone:
            self.mapObj.setMetaData(
                "ows_contactvoicetelephone", config.getConfigValue("provider", "phoneVoice"))
        phone = config.getConfigValue("provider", "phoneFacsimile")
        if phone:
            self.mapObj.setMetaData("ows_contactfacsimiletelephone", config.getConfigValue(
                "provider", "phoneFacsimile"))
        self.mapObj.setMetaData(
            "ows_address", config.getConfigValue("provider", "deliveryPoint"))
        self.mapObj.setMetaData(
            "ows_city", config.getConfigValue("provider", "city"))
        self.mapObj.setMetaData(
            "ows_country", config.getConfigValue("provider", "country"))
        self.mapObj.setMetaData(
            "ows_postcode", config.getConfigValue("provider", "postalCode"))
        self.mapObj.setMetaData("ows_contactelectronicmailaddress", config.getConfigValue(
            "provider", "electronicMailAddress"))
        self.mapObj.setMetaData(
            "ows_role", config.getConfigValue("provider", "role"))

        self.mapFileName = os.path.join(config.getConfigValue(
            "server", "outputPath"), self.sessionId + ".map")
        self.mapObj.setMetaData("wms_onlineresource", config.getConfigValue(
            "mapserver", "mapserveraddress") + "?map=" + self.mapFileName)

    def getReference(self, output):
        """Get reference URL for given output

        :param output: :class:`pywps.Process.InAndOutputs.ComplexOutput`

        :rtype: String
        :returns: URL pointing to OGC OWS (WCS/WFS)
        """
        # try to determine the data type
        # need to silence error like: ERROR 4: `/var/www/html/wpsoutputs/vectorout-24363CH3zxi' not recognised as a supported file format.
        # caused by wrong opendataset from gdal or ogr
        datatype = self.getDataset(output)

        if not datatype:
            return None

        # get projection and bounding box
        if not output.bbox:
            output.bbox = self.getBBox(output, datatype)

        myLayerObj = layerObj(self.mapObj)
        myLayerObj.group = self.process.identifier
        myLayerObj.dump = MS_TRUE

        # raster is no problem
        if datatype == "raster":
            myLayerObj.type = MS_LAYER_RASTER
            myLayerObj.data = output.value
            myLayerObj.setMetaData("wcs_label", output.title)
            myLayerObj.setMetaData("wcs_rangeset_name", "Range 1")
            myLayerObj.setMetaData("wcs_rangeset_label", "My Label")
        # vector has to be point,line,polygon
        # determine this from the first found feature from the first layer
        else:
            myLayerObj.setConnectionType(MS_OGR, output.value)
            myLayerObj.connection = output.value
            myLayerObj.setMetaData("wfs_title", output.title)
            myClassObj = classObj(myLayerObj)
            myStyleObj = styleObj(myClassObj)
            myLayerObj.setMetaData("wfs_title", output.title)
            myLayerObj.setMetaData("gml_featureid", "ID")
            myLayerObj.setMetaData("gml_include_items", "all")

            layer = self.dataset.GetLayer()
            feature = layer.GetNextFeature()
            geometry = feature.GetGeometryRef()

            myLayerObj.data = layer.GetName()

            if geometry.GetGeometryName().lower() in ["point", "multipoint"]:
                myLayerObj.type = MS_LAYER_POINT
                myStyleObj.color.setRGB(0, 0, 0)
            else:
                myStyleObj.outlinecolor = colorObj(134, 81, 0)
                myStyleObj.color = colorObj(238, 153, 0)
                myStyleObj.size = 5
                myStyleObj.width = 5
                if geometry.GetGeometryName().lower() in ["linestring", "multilinestring"]:
                    myLayerObj.type = MS_LAYER_LINE
                elif geometry.GetGeometryName().lower() in ["polygon", "multipolygon"]:
                    myLayerObj.type = MS_LAYER_POLYGON
                else:
                    myLayerObj.type = MS_LAYER_POINT

            # elif geometry.GetGeometryName().lower() in ["line","linestring"]:
            #    myLayerObj.type = MS_LAYER_LINE
            #    myStyleObj.color.setRGB(0,0,0)
            # elif geometry.GetGeometryName().lower() == "polygon":
            #    myLayerObj.type = MS_LAYER_POLYGON
            #    myStyleObj.outlinecolor.setRGB(0,0,0)

        if self.process.abstract:
            myLayerObj.setMetaData("group_abstract", self.process.abstract)
        if output.abstract:
            myLayerObj.setMetaData("wcs_abstract", output.abstract)
            myLayerObj.setMetaData("wfs_abstract", output.abstract)
        myLayerObj.name = output.identifier

        # either the output has projection already, use it

        if output.projection:
            # output.projection=epsg:4326,epsg:102067,epsg:3059,epsg:900913
            # like cfg file
            firstEPSG = output.projection.lower().split(",")[0]
            myLayerObj.setProjection(firstEPSG)
        else:
            # do we have info from self.projs
            if len(self.projs) > 0:
                myLayerObj.setProjection(self.projs[0])
            else:
                # try to determine dataset projection using gdal/ogr
                spatialReference = self.getSpatialReference(output, datatype)
                if spatialReference:

                    if spatialReference.IsProjected():
                        authority = spatialReference.GetAuthorityName("PROJCS")
                        code = spatialReference.GetAuthorityCode("PROJCS")
                    else:
                        authority = spatialReference.GetAuthorityName("GEOGCS")
                        code = spatialReference.GetAuthorityCode("GEOGCS")
                        #authority = spatialReference.GetAuthorityName("PROJCS")
                        #code = spatialReference.GetAuthorityCode("PROJCS")

                # we are able to construct something like "epsg:4326"
                    if authority and code:
                        myLayerObj.setProjection(
                            "init=%s:%s" % (authority.lower(), code))

                # we will have at least PROJ4 parameters, but no
                # AUTHORITY:CODE, the dataset will obtain projection from
                # the map object

        # setting wfs_srs, wms_srs and wcs_srs
        if output.projection:
            epsgList = output.projection.split(",")
            ows_srs = " ".join(epsgList).upper()
            output.projection = epsgList[0]
        else:
            # check for output.projection
            if len(self.projs) > 0:
                ows_srs = " ".join(self.projs).upper()
                output.projection = self.projs[0]
            else:
                # try to get from data otherwise give up on it
                if ("authority" and "code" in locals()) and (authority and code):
                    ows_srs = authority.upper() + ":" + code
                    output.projection = authority.upper() + ":" + code
       # if we have ows_srs then set it
        if "ows_srs" in locals():
            myLayerObj.setMetaData("ows_srs", ows_srs)

        if datatype == "raster":
            return self.getMapServerWCS(output)
            myLayerObj.type = MS_LAYER_RASTER
        # make it WFS
        else:
            return self.getMapServerWFS(output)

    def getDataset(self, output):
        """
        :param output: :class:`pywps.Process.InAndOutputs.ComplexOutput`
        :returns: "raster" or "vector"
        """

        logging.debug("Importing given output [%s] using gdal" % output.value)
        # If dataset is XML it will make an error like ERROR 4:
        # `/var/www/html/wpsoutputs/vectorout-26317EUFxeb' not recognised as a
        # supported file format.
        self.dataset = gdal.Open(output.value)

        if self.dataset:
            return "raster"

        if not self.dataset:
            logging.debug(
                "Importing given output [%s] using ogr" % output.value)
            self.dataset = ogr.Open(output.value)

        if self.dataset:
            return "vector"
        else:
            return None

    def getSpatialReference(self, output, datatype):
        """
        :param output: :class:`pywps.Process.InAndOutputs.ComplexOutput`
        :param datatype: String
        :return: projection of the output
        """

        sr = osr.SpatialReference()
        if datatype == "raster":
            wkt = self.dataset.GetProjection()
            res = sr.ImportFromWkt(wkt)
            if res == 0:
                return sr
        elif datatype == "vector":
            layer = self.dataset.GetLayer()
            ref = layer.GetSpatialRef()
            if ref:
                return ref
        return None

    def getBBox(self, output, datatype):
        """
        :param output: :class:`pywps.Process.InAndOutputs.ComplexOutput`
        :param datatype: String raster or vector
        :return: bounding box of the dataset
        """

        if datatype == "raster":
            geotransform = self.dataset.GetGeoTransform()
            if not output.height:
                output.height = self.dataset.RasterYSize
                output.width = self.dataset.RasterXSize
            return (geotransform[0],
                    geotransform[3] + geotransform[5] *
                    self.dataset.RasterYSize,
                    geotransform[0] + geotransform[1] *
                    self.dataset.RasterXSize,
                    geotransform[3])
        else:
            layer = self.dataset.GetLayer()
            return layer.GetExtent()

    def save(self):
        """Save the mapfile to disc"""
        if self.mapObj:
            self.mapObj.save(self.mapFileName)

    def getProjs(self):
        """Gets projections from config file and it returns a len=0 list if no projs present"""
        projs = config.getConfigValue(
            "mapserver", "projs").lower().split(",")  # List with projs
        # cleaning a bit (specially from '')
        projs = [item for item in projs if 'epsg' in item]
        return projs

    def getMapServerWCS(self, output):
        """Get the URL for mapserver WCS request of the output"""
        return urllib2.quote(config.getConfigValue("mapserver", "mapserveraddress") +
                             "?map=" + self.mapFileName +
                             "&SERVICE=WCS" + "&REQUEST=GetCoverage" + "&VERSION=1.0.0" +
                             "&COVERAGE=" + output.identifier + "&CRS=" + output.projection.replace("+init=", "") +
                             "&BBOX=%s,%s,%s,%s" % (output.bbox[0], output.bbox[1], output.bbox[2], output.bbox[3]) +
                             "&HEIGHT=%s" % (output.height) + "&WIDTH=%s" % (output.width) + "&FORMAT=%s" % output.format["mimetype"])

    def getMapServerWFS(self, output):
        """Get the URL for mapserver WFS request of the output"""
        return urllib2.quote(config.getConfigValue("mapserver", "mapserveraddress") +
                             "?map=" + self.mapFileName +
                             "&SERVICE=WFS" + "&REQUEST=GetFeature" + "&VERSION=1.0.0" +
                             "&TYPENAME=" + output.identifier)
