"""
This module parses OGC Web Processing Service (WPS) Execute request.
"""
# Author:   Jachym Cepicky
#           http://les-ejk.cz
#               jachym at les-ejk dot cz
# Lince:
#
# 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

import xml.dom.minidom
import pywps
from pywps.Parser.Post import Post as PostParser
from pywps.Parser.Get import Get as GetParser

import string
import re
import urllib


class Post(PostParser):
    """ HTTP POST XML request encoding parser.  """

    nameSpace = None  # WPS namespace
    owsNameSpace = None  # OWS namespace
    xlinkNameSpace = None  # OWS namespace

    def __init__(self, wps):
        PostParser.__init__(self, wps)

    def parse(self, document, initInputs=None):
        """ Parse given XML document """

        if initInputs:
            self.inputs = initInputs

        self.document = document  # input DOM
        firstChild = self.isSoapFirstChild(self.document)  # no comments or
        # white spaces

        self.nameSpace = firstChild.namespaceURI    # document namespace
        self.nameSpace = pywps.WPS_NAMESPACE
        self.owsNameSpace = pywps.OWS_NAMESPACE
        self.xlinkNameSpace = pywps.XLINK_NAMESPACE
        language = None
        identifiers = []
        identifierNode = None
        dataInputs = []

        #
        # Mandatory options
        #

        # service & Request are already controlled

        # version
        self.checkVersion(firstChild)

        # identifier
        try:
            self.inputs["identifier"] = [
                firstChild.getElementsByTagNameNS(self.owsNameSpace, "Identifier")[
                    0].firstChild.nodeValue
            ]
        except IndexError:
            raise pywps.MissingParameterValue("Identifier")

        #
        # Optional options
        #

        # language
        self.checkLanguage(firstChild)

        # dataInputs
        try:
            inputsNode = firstChild.getElementsByTagNameNS(
                self.nameSpace, "DataInputs")[0]
            self.inputs["datainputs"] = self.parseDataInputs(inputsNode)
        except IndexError:
            self.inputs["datainputs"] = None

        # responseForm
        try:
            responseFormNode = \
                firstChild.getElementsByTagNameNS(self.nameSpace,
                                                  "ResponseForm")[0]
            self.inputs["responseform"] = self.parseResponseForm(
                responseFormNode)
        except IndexError:
            self.inputs["responseform"] = {}

        # OGC 05-007r7 page 36, Table 49
        # Either responseDocument or rawDataOutput should be specified, not
        # both
        if self.inputs.has_key('responseform') and \
           (self.inputs["responseform"].has_key("rawdataoutput") and
                self.inputs["responseform"].has_key("responsedocument")) and \
                self.inputs["responseform"]["responsedocument"] and \
                self.inputs["responseform"]["rawdataoutput"]:
            raise pywps.InvalidParameterValue(
                "Either responseDocument or rawDataOutput should be specified, but not both")
        if not self.inputs["responseform"].has_key("rawdataoutput"):
            self.inputs["responseform"]["rawdataoutput"] = {}
        if not self.inputs["responseform"].has_key("responsedocument"):
            self.inputs["responseform"]["responsedocument"] = {}
        return self.inputs

    def parseResponseForm(self, responseFormNode):
        """ Parse requested response form node """

        form = {}

        # ResponseDocument
        try:
            form["responsedocument"] = {}
            responseDocumentNode = responseFormNode.getElementsByTagNameNS(
                self.nameSpace, "ResponseDocument")[0]

            # store
            store = False
            if responseDocumentNode.hasAttribute("storeExecuteResponse"):
                if responseDocumentNode.getAttribute("storeExecuteResponse").lower() == "true":
                    form["responsedocument"]["storeexecuteresponse"] = True
                else:
                    form["responsedocument"]["storeexecuteresponse"] = False

            # lineage
            lineage = False
            if responseDocumentNode.hasAttribute("lineage"):
                if responseDocumentNode.getAttribute("lineage").lower() == "true":
                    form["responsedocument"]["lineage"] = True
                else:
                    form["responsedocument"]["lineage"] = False

            # status
            status = False
            if responseDocumentNode.hasAttribute("status"):
                if responseDocumentNode.getAttribute(
                        "status").lower() == "true":
                    form["responsedocument"]["status"] = True
                else:
                    form["responsedocument"]["status"] = False

            form["responsedocument"]["outputs"] = {}
            outputs = []
            for outputNode in responseDocumentNode.getElementsByTagNameNS(
                    self.nameSpace, "Output"):

                try:
                    # identifier
                    identifier = outputNode.getElementsByTagNameNS(
                        self.owsNameSpace,
                        "Identifier")[0].firstChild.nodeValue
                    outputs.append({"identifier": identifier})
                except IndexError:
                    continue
                    # raise pywps.MissingParameterValue("Identifier")
                # Abstract, Title are not supported yet
                # is it necessary ?

                outputs[-1]["mimetype"] = \
                    outputNode.getAttribute("mimeType")
                outputs[-1]["encoding"] = \
                    outputNode.getAttribute("encoding")
                outputs[-1]["schema"] = \
                    outputNode.getAttribute("schema")
                outputs[-1]["uom"] = \
                    outputNode.getAttributeNS(self.nameSpace, "uom")

                outputs[-1]["asreference"] = False
                if outputNode.getAttribute("asReference").lower() == "true":
                    outputs[-1]["asreference"] = True

            form["responsedocument"]["outputs"] = outputs

        # RawDataOutput
        except IndexError:
            responseFormNode.getElementsByTagNameNS(self.nameSpace,
                                                    "RawDataOutput")
            form["rawdataoutput"] = {}
            identifier = None
            try:
                # identifier
                identifier = responseFormNode.getElementsByTagNameNS(
                    self.owsNameSpace,
                    "Identifier")[0].firstChild.nodeValue
                form["rawdataoutput"][identifier] = {}

                form["rawdataoutput"][identifier]["mimetype"] = \
                    responseFormNode.getAttribute("mimeType")
                form["rawdataoutput"][identifier]["encoding"] = \
                    responseFormNode.getAttribute("encoding")
                form["rawdataoutput"][identifier]["schema"] = \
                    responseFormNode.getAttribute("schema")
                form["rawdataoutput"][identifier]["uom"] = \
                    responseFormNode.getAttributeNS(self.nameSpace, "uom")
            except IndexError:
                #raise pywps.MissingParameterValue("Identifier")
                pass
        return form

    def parseDataInputs(self, inputsNode):
        """ Parse input data from given node """

        parsedDataInputs = []

        for inputNode in inputsNode.getElementsByTagNameNS(self.nameSpace,
                                                           "Input"):
            # input Identifier
            try:
                identifier = inputNode.getElementsByTagNameNS(
                    self.owsNameSpace, "Identifier")[0].firstChild.nodeValue
            except (IndexError, AttributeError):
                raise pywps.NoApplicableCode(
                    "Identifier for input not set")
            parsedDataInputs.append({"identifier": identifier, "value": None,
                                     "attributes": {}})

            # Title and Abstract are only mandatory and not necessary:
            # skipping, not supported yet
            # formchoice
            try:
                dataTypeNode = inputNode.getElementsByTagNameNS(
                    self.nameSpace, "Reference")[0]
                attributes = self.parseReferenceDataInput(dataTypeNode)
                attributes["identifier"] = identifier
                parsedDataInputs[-1] = attributes
            except IndexError, e:

                dataTypeNode = inputNode.getElementsByTagNameNS(
                    self.nameSpace, "Data")[0]
                attributes = self.parseDataInput(dataTypeNode)
                attributes["identifier"] = identifier
                parsedDataInputs[-1] = attributes
            try:
                parsedDataInputs[-1]
            except KeyError:
                raise pywps.InvalidParameterValue(identifier)

        return parsedDataInputs

    def parseReferenceDataInput(self, dataTypeNode):
        """ Parse given complex value reference node """

        attributes = {}

        #
        # mandatory attributes
        #
        attributes["value"] =\
            dataTypeNode.getAttributeNS(self.xlinkNameSpace, "href")
        if attributes["value"] == "":
            raise pywps.MissingParameterValue("'href'")

        #
        # optional attributes
        #

        # mimeType, encoding, schema - are now supportd supported ^_^ #jmdj

        attributes["mimetype"] = dataTypeNode.getAttribute("mimeType")
        attributes["encoding"] = dataTypeNode.getAttribute("encoding")
        attributes["schema"] = dataTypeNode.getAttribute("schema")

       # jmdj GET method doesn't have a namespace
        attributes["method"] = dataTypeNode.getAttribute("method")
        if attributes["method"] == "":
            attributes["method"] = "GET"

        # header
        try:
            attributes["header"] = self.parseHeaderDataInput(
                dataTypeNode.getElementsByTagNameNS(
                    self.nameSpace, "Header")[0])
        except IndexError:
            attributes["header"] = None

        # body
        try:
            attributes["body"] = \
                dataTypeNode.getElementsByTagNameNS(self.nameSpace,
                                                    "Body")[0].firstChild

            # get node value, if node type is Text or CDATA
            if attributes["body"].nodeType == \
                    xml.dom.minidom.Text.nodeType or\
                    attributes["body"].nodeType ==\
                    xml.dom.minidom.CDATASection.nodeType:
                attributes["body"] = attributes["body"].nodeValue
        except IndexError:
            attributes["body"] = None

        # bodyreference
        try:
            bodyReferenceNode = dataTypeNode.getElementsByTagNameNS(
                self.nameSpace, "BodyReference")[0]
            attributes["bodyreference"] = bodyReferenceNode.getAttributeNS(
                self.xlinkNameSpace, "href")
        except IndexError:
            attributes["bodyreference"] = None

        attributes["type"] = "ComplexValue"
        attributes["asReference"] = True
        return attributes

    def parseHeaderDataInput(self, headerNode):
        """ Parse header node """

        header = {}

        if headerNode:
            header[headerNode.getAttributeNS(self.nameSpace, "key")] =\
                headerNode.getAttributeNS(self.nameSpace, "value")

            if len(header.keys()) == 0:
                raise pywps.MissingParameterValue("Header")

        return header

    def parseDataInput(self, dataTypeNode):
        """Parse attributes of given data type node """

        attributes = {}

        # complexData
        if len(dataTypeNode.getElementsByTagNameNS(
                self.nameSpace, "ComplexData")) > 0:
            attributes = self.parseComplexData(
                dataTypeNode.getElementsByTagNameNS(
                    self.nameSpace, "ComplexData")[0])

        # literalData
        elif len(dataTypeNode.getElementsByTagNameNS(
                self.nameSpace, "LiteralData")) > 0:
            attributes = self.parseLiteralData(
                dataTypeNode.getElementsByTagNameNS(
                    self.nameSpace, "LiteralData")[0])
        # bboxData
        elif len(dataTypeNode.getElementsByTagNameNS(
                self.nameSpace, "BoundingBoxData")) > 0:
            attributes = self.parseBBoxData(
                dataTypeNode.getElementsByTagNameNS(
                    self.nameSpace, "BoundingBoxData")[0])

        # if attributes are still None, exception will
        # be called in parent method

        return attributes

    def parseComplexData(self, complexDataNode):
        """Parse complex data node"""

        attributes = {}
        attributes["mimetype"] = complexDataNode.getAttribute("mimeType")
        attributes["encoding"] = complexDataNode.getAttribute("encoding")
        attributes["schema"] = complexDataNode.getAttribute("schema")
        attributes["value"] = None
        for complexDataChildNode in complexDataNode.childNodes:
            # CDATA or text and the input value is empty and the Text or
            # CDATA is not empty
            if (complexDataChildNode.nodeType == xml.dom.minidom.Text.nodeType or
                    complexDataChildNode.nodeType == xml.dom.minidom.CDATASection.nodeType) and\
                    complexDataChildNode.nodeValue and not attributes["value"]:
                attributes["value"] = complexDataChildNode.nodeValue
            # xml input
            elif complexDataChildNode.nodeType == \
                    xml.dom.minidom.Element.nodeType:
                attributes["value"] = complexDataChildNode.toxml()

        attributes["type"] = "ComplexValue"

        return attributes

    def parseLiteralData(self, literalDataNode):
        """Parse literal data node"""

        attributes = {}
        attributes["dataType"] = literalDataNode.getAttributeNS(
            "*", "dataType")
        attributes["uom"] = literalDataNode.getAttributeNS(
            "*", "uom")
        try:
            attributes["value"] = self._trueOrFalse(
                (literalDataNode.firstChild.nodeValue).encode("utf-8"))
        except:
            attributes["value"] = None

        return attributes

    def parseBBoxData(self, bboxDataNode):
        """Parse bbox data node"""

        attributes = {}
        attributes["value"] = []

        attributes["crs"] = ''
        if bboxDataNode.hasAttribute("crs"):
            attributes["crs"] = bboxDataNode.getAttribute("crs")
        elif bboxDataNode.hasAttributeNS(self.nameSpace, "crs"):
            attributes["crs"] = bboxDataNode.getAttributeNS(
                self.nameSpace, "crs")
        elif bboxDataNode.hasAttributeNS(self.owsNameSpace, "crs"):
            attributes["crs"] = bboxDataNode.getAttributeNS(
                self.owsNameSpace, "crs")
        attributes["dimensions"] = 2
        if bboxDataNode.hasAttribute("dimensions"):
            attributes["dimensions"] = int(
                bboxDataNode.getAttribute("dimensions"))
        elif bboxDataNode.hasAttributeNS(self.nameSpace, "dimensions"):
            attributes["dimensions"] = int(
                bboxDataNode.getAttributeNS(self.nameSpace, "dimensions"))
        elif bboxDataNode.hasAttributeNS(self.owsNameSpace, "dimensions"):
            attributes["dimensions"] = int(
                bboxDataNode.getAttributeNS(self.owsNameSpace, "dimensions"))

        for coord in bboxDataNode.getElementsByTagNameNS(
                self.owsNameSpace, "LowerCorner")[0].firstChild.nodeValue.split():
            attributes["value"].append(float(coord))
        for coord in bboxDataNode.getElementsByTagNameNS(
                self.owsNameSpace, "UpperCorner")[0].firstChild.nodeValue.split():
            attributes["value"].append(float(coord))

        # reset everything, if there are not 4 coordinates
        if len(attributes["value"]) != 4:
            attributes = None
        return attributes


class Get(GetParser):
    """
    Parses input request obtained via HTTP GET encoding.
    """

    def __init__(self, wps):
        GetParser.__init__(self, wps)

    def parse(self, unparsedInputs, initInputs=None):
        """ Parse given inputs """

        if initInputs:
            self.inputs = initInputs

        self.unparsedInputs = unparsedInputs

        #
        # Mandatory options
        #

        # service & Request are already controlled

        # version
        self.checkVersion()

        # identifier
        if "identifier" in self.unparsedInputs:
            self.inputs["identifier"] = [self.unparsedInputs["identifier"]]
        else:
            raise pywps.MissingParameterValue("identifier")

        #
        # Optional options
        #

        # Language
        self.checkLanguage()

        # dataInputs
        try:
            self.inputs["datainputs"] = self.parseDataInputs(
                self.unparsedInputs["datainputs"])

        except KeyError:
            self.inputs["datainputs"] = None
        # ResponseForm

        self.inputs["responseform"] = {}

        # ResponseDocument
        try:
            self.inputs["responseform"]["responsedocument"] = \
                {"outputs":  self.parseDataInputs(
                    self.unparsedInputs["responsedocument"])}
        except KeyError:
            self.inputs["responseform"]["responsedocument"] = {}

        # RawDataOutput
        try:
            preparsed = self.parseDataInputs(
                self.unparsedInputs["rawdataoutput"])
            self.inputs["responseform"][
                "rawdataoutput"] = self._parseRawDataOutput(preparsed[0])
        except KeyError:
            self.inputs["responseform"]["rawdataoutput"] = {}

        # storeExecuteResponse
        if "storeexecuteresponse" in self.unparsedInputs:
            if self.unparsedInputs["storeexecuteresponse"].lower() ==\
                    "true":
                self.inputs["responseform"]["responsedocument"][
                    "storeexecuteresponse"] = True
            else:
                self.inputs["responseform"]["responsedocument"][
                    "storeexecuteresponse"] = False

        # lineage
        if "lineage" in self.unparsedInputs:
            if self.unparsedInputs["lineage"].lower() == "true":
                self.inputs["responseform"]["responsedocument"]["lineage"] =\
                    True
            else:
                self.inputs["responseform"]["responsedocument"]["lineage"] =\
                    False

        # status
        if "status" in self.unparsedInputs:
            if self.unparsedInputs["status"].lower() == "true":
                self.inputs["responseform"]["responsedocument"]["status"] =\
                    True
            else:
                self.inputs["responseform"]["responsedocument"]["status"] =\
                    False

        # OGC 05-007r7 page 36, Table 49
        # Either responseDocument or rawDataOutput should be specified, not
        # both
        if len(self.inputs["responseform"]["rawdataoutput"]) > 0 and \
                len(self.inputs["responseform"]["responsedocument"]) > 0:
            raise pywps.InvalidParameterValue(
                "Either responseDocument or rawDataOutput should be specified, but not both")
        return self.inputs

    def _parseRawDataOutput(self, dataInput):
        """Parser RawDataOutput parameter according to Table 52"""
        dataOut = {dataInput["identifier"]: {
            "mimetype": '', "uom": '', "encoding": '', "schema": ''}}

        if dataInput.has_key("mimetype"):
            dataOut[dataInput["identifier"]][
                "mimetype"] = dataInput["mimetype"]
        if dataInput.has_key("schema"):
            dataOut[dataInput["identifier"]]["schema"] = dataInput["schema"]
        if dataInput.has_key("uom"):
            dataOut[dataInput["identifier"]]["uom"] = dataInput["uom"]
        if dataInput.has_key("encoding"):
            dataOut[dataInput["identifier"]][
                "encoding"] = dataInput["encoding"]
        return dataOut

    def _parseBBoxInput(self, dataInput):
        """Parser of Bounding Box data input"""
        print dataInput

    def parseDataInputs(self, dataInputs):
        """Parse DataInputs parameter

        This is described in OGC WPS 1.0.0,  05-007, page 38

        """

        parsedDataInputs = []

        # Parameters are separated by ";"
        for dataInput in dataInputs.split(";"):
            try:
                # key is separated by "=" from value
                key, valueAndAttrs = string.split(dataInput, "=", maxsplit=1)
            except ValueError, e:
                key = dataInput
                valueAndAttrs = ""

            if not key and not valueAndAttrs:
                continue

            # initial value
            parsed = {"identifier": key, "value": None}
            # additional input attributes are separated by "@"
            attributes = []
            if valueAndAttrs.find("@") > 0:
                encodedValue = valueAndAttrs.split("@")[0]
                parsed["value"] = urllib.unquote(encodedValue)
                attributes = valueAndAttrs.split("@")[1:]

            elif valueAndAttrs.find("@") == 0:
                # example:
                # @xlink:href=http://rsg.pml.ac.uk/wps/testdata/elev_srtm_30m.img
                if ("@xlink:href" in valueAndAttrs):
                    # This attribute is actually a value
                    valueAndAttrs = valueAndAttrs.replace("@xlink:href=", "")
                    encodedValue = valueAndAttrs.split("@")[0]
                    parsed["value"] = urllib.unquote(encodedValue)
                    attributes = valueAndAttrs.split("@")[1:]
                else:
                    attributes = valueAndAttrs.split("@")[1:]
                    # just passing the attributes
                    parsed["value"] = None
            else:
                # needs to be checked for trueOrFalse
                encodedValue = valueAndAttrs
                parsed["value"] = self._trueOrFalse(
                    urllib.unquote(valueAndAttrs))
                attributes = []

            # additional attribute key is separated by "=" from it's value
            for attribute in attributes:
                attributeKey, attributeValue = attribute.split("=")
                parsed[attributeKey.lower()] = self._trueOrFalse(
                    urllib.unquote(attributeValue))
            parsedDataInputs.append(parsed)
        return parsedDataInputs

    # Moved to Parser class
    # def _trueOrFalse(self,str):
    #    """Return True or False, if input is "true" or "false" """
    #    if str.lower() == "false":
    #        return False
    #    elif str.lower() == "true":
    #        return True
    #    else:
    #        return str
