# -*- coding: ISO-8859-15 -*-
# =============================================================================
# Copyright (c) 2004, 2006 Sean C. Gillies
# Copyright (c) 2007 STFC <http://www.stfc.ac.uk>
#
# Authors:
#          Dominic Lowe <d.lowe@rl.ac.uk>
#
# Contact email: d.lowe@rl.ac.uk
# =============================================================================

# NOTE: Does not conform to new interfaces yet #################

import errno
import logging
import os
from urllib.parse import urlencode

from .wcsBase import WCSBase, WCSCapabilitiesReader, ServiceException
from owslib.etree import etree
from owslib.coverage import wcsdecoder
from owslib.crs import Crs
from owslib.util import openURL, testXMLValue

LOGGER = logging.getLogger(__name__)


class Namespaces_1_1_0():

    def WCS(self, tag):
        return '{http://www.opengis.net/wcs/1.1}' + tag

    def WCS_OWS(self, tag):
        return '{http://www.opengis.net/wcs/1.1/ows}' + tag

    def OWS(self, tag):
        return '{http://www.opengis.net/ows}' + tag


class WebCoverageService_1_1_0(WCSBase):
    """Abstraction for OGC Web Coverage Service (WCS), version 1.1.0
    Implements IWebCoverageService.
    """

    version = '1.1.0'
    ns = Namespaces_1_1_0()

    def __getitem__(self, name):
        ''' check contents dictionary to allow dict like access to service layers'''
        if name in list(self.__getattribute__('contents').keys()):
            return self.__getattribute__('contents')[name]
        else:
            raise KeyError("No content named %s" % name)

    def __init__(self, url, xml, cookies, auth=None, timeout=30, headers=None):
        super(WebCoverageService_1_1_0, self).__init__(auth=auth, timeout=timeout, headers=headers)

        self.url = url
        self.cookies = cookies
        self.timeout = timeout
        # initialize from saved capability document or access the server
        reader = WCSCapabilitiesReader(self.version, self.cookies, self.auth, headers=self.headers)
        if xml:
            self._capabilities = reader.readString(xml)
        else:
            self._capabilities = reader.read(self.url, self.timeout)

        # check for exceptions
        se = self._capabilities.find(self.ns.OWS('Exception'))

        if se is not None:
            err_message = str(se.text).strip()
            raise ServiceException(err_message, xml)

        # build metadata objects:

        self.updateSequence = self._capabilities.attrib.get('updateSequence')

        # serviceIdentification metadata
        elem = self._capabilities.find(self.ns.WCS_OWS('ServiceIdentification'))
        if elem is None:
            elem = self._capabilities.find(self.ns.OWS('ServiceIdentification'))
        self.identification = ServiceIdentification(elem, self.ns)

        # serviceProvider
        elem = self._capabilities.find(self.ns.OWS('ServiceProvider')) or self._capabilities.find(self.ns.OWS('ServiceProvider'))  # noqa
        self.provider = ServiceProvider(elem, self.ns)

        # serviceOperations
        self.operations = []
        for elem in self._capabilities.findall(
                self.ns.OWS('OperationsMetadata') + '/' + self.ns.OWS('Operation')):
            self.operations.append(Operation(elem, self.ns))

        # exceptions - ***********TO DO *************
            self.exceptions = [f.text for f in self._capabilities.findall('Capability/Exception/Format')]

        # serviceContents: our assumption is that services use a top-level layer
        # as a metadata organizer, nothing more.
        self.contents = {}
        top = self._capabilities.find(self.ns.WCS('Contents') + '/' + self.ns.WCS('CoverageSummary'))
        for elem in self._capabilities.findall(
                self.ns.WCS('Contents') + '/' + self.ns.WCS('CoverageSummary') + '/' + self.ns.WCS('CoverageSummary')):
            cm = ContentMetadata(elem, top, self, self.ns)
            self.contents[cm.id] = cm

        if self.contents == {}:
            # non-hierarchical.
            top = None
            for elem in self._capabilities.findall(self.ns.WCS('Contents') + '/' + self.ns.WCS('CoverageSummary')):
                cm = ContentMetadata(elem, top, self, self.ns)
                # make the describeCoverage requests to populate the supported formats/crs attributes
                self.contents[cm.id] = cm

    def items(self):
        '''supports dict-like items() access'''
        items = []
        for item in self.contents:
            items.append((item, self.contents[item]))
        return items

    # TO DECIDE: Offer repackaging of coverageXML/Multipart MIME output?
    # def getData(self, directory='outputdir', outputfile='coverage.nc',  **kwargs):
    #     u=self.getCoverageRequest(**kwargs)
    #     #create the directory if it doesn't exist:
    #     try:
    #         os.mkdir(directory)
    #     except OSError, e:
    #         # Ignore directory exists error
    #         if e.errno <> errno.EEXIST:
    #             raise
    #     #elif wcs.version=='1.1.0':
    #     #Could be multipart mime or XML Coverages document, need to use the decoder...
    #     decoder=wcsdecoder.WCSDecoder(u)
    #     x=decoder.getCoverages()
    #     if type(x) is wcsdecoder.MpartMime:
    #         filenames=x.unpackToDir(directory)
    #         #print 'Files from 1.1.0 service written to %s directory'%(directory)
    #     else:
    #         filenames=x
    #     return filenames

    # TO DO: Handle rest of the  WCS 1.1.0 keyword parameters e.g. GridCRS etc.
    def getCoverage(self, identifier=None, bbox=None, time=None, format=None, store=False, rangesubset=None,
                    gridbaseCRS=None, gridtype=None, gridCS=None, gridorigin=None, gridoffsets=None,
                    method='Get', timeout=30, **kwargs):
        """Request and return a coverage from the WCS as a file-like object
        note: additional **kwargs helps with multi-version implementation
        core keyword arguments should be supported cross version
        example:
        cvg=wcs.getCoverageRequest(identifier='TuMYrRQ4', time=['2792-06-01T00:00:00.0'], bbox=(-112,36,-106,41),
                                   format='application/netcdf', store='true')

        is equivalent to:
        http://myhost/mywcs?SERVICE=WCS&REQUEST=GetCoverage&IDENTIFIER=TuMYrRQ4&VERSION=1.1.0&BOUNDINGBOX=-180,-90,180,90&TIMESEQUENCE=2792-06-01T00:00:00.0&FORMAT=application/netcdf

        if store = true, returns a coverages XML file
        if store = false, returns a multipart mime
        """
        msg = 'WCS 1.1.0 DEBUG: Parameters passed to GetCoverage: identifier={}, bbox={}, time={}, format={}, rangesubset={}, gridbaseCRS={}, gridtype={}, gridCS={}, gridorigin={}, gridoffsets={}, method={}, other_arguments={}'  # noqa
        LOGGER.debug(msg.format(
            identifier, bbox, time, format, rangesubset, gridbaseCRS, gridtype, gridCS,
            gridorigin, gridoffsets, method, str(kwargs)))

        if method == 'Get':
            method = self.ns.WCS_OWS('Get')
        try:
            base_url = next((m.get('url') for m in self.getOperationByName('GetCoverage').methods
                            if m.get('type').lower() == method.lower()))
        except StopIteration:
            base_url = self.url

        # process kwargs
        request = {'version': self.version, 'request': 'GetCoverage', 'service': 'WCS'}
        request['identifier'] = identifier
        # request['identifier'] = ','.join(identifier)
        if bbox:
            request['boundingbox'] = ','.join([str(x) for x in bbox])
        if time:
            request['timesequence'] = ','.join(time)
        request['format'] = format
        request['store'] = store

        # rangesubset: untested - require a server implementation
        if rangesubset:
            request['RangeSubset'] = rangesubset

        # GridCRS structure: untested - require a server implementation
        if gridbaseCRS:
            request['gridbaseCRS'] = gridbaseCRS
        if gridtype:
            request['gridtype'] = gridtype
        if gridCS:
            request['gridCS'] = gridCS
        if gridorigin:
            request['gridorigin'] = gridorigin
        if gridoffsets:
            request['gridoffsets'] = gridoffsets

        # anything else e.g. vendor specific parameters must go through kwargs
        if kwargs:
            for kw in kwargs:
                request[kw] = kwargs[kw]

        # encode and request
        data = urlencode(request)

        u = openURL(base_url, data, method, self.cookies, auth=self.auth, timeout=timeout, headers=self.headers)
        return u

    def getOperationByName(self, name):
        """Return a named operation item."""
        for item in self.operations:
            if item.name == name:
                return item
        raise KeyError("No operation named %s" % name)


class Operation(object):
    """Abstraction for operation metadata
    Implements IOperationMetadata.
    """
    ns = Namespaces_1_1_0()

    def __init__(self, elem, nmSpc):

        self.name = elem.get('name')
        self.formatOptions = [f.text for f in elem.findall(nmSpc.WCS_OWS('Parameter') + '/' + nmSpc.WCS_OWS('AllowedValues') + '/' + nmSpc.WCS_OWS('Value'))]  # noqa
        methods = []
        for verb in elem.findall(nmSpc.WCS_OWS('DCP') + '/' + nmSpc.WCS_OWS('HTTP/*')):
            url = verb.attrib['{http://www.w3.org/1999/xlink}href']
            methods.append((verb.tag, {'url': url}))
        self.methods = dict(methods)


class ServiceIdentification(object):
    """ Abstraction for ServiceIdentification Metadata
    implements IServiceIdentificationMetadata"""

    def __init__(self, elem, nmSpc):
        self.service = "WCS"

        self.title = testXMLValue(elem.find(nmSpc.OWS('Title')))
        if self.title is None:  # may have used the wcs ows namespace:
            self.title = testXMLValue(elem.find(nmSpc.WCS_OWS('Title')))
        if self.title is None:  # may have used the other wcs ows namespace:
            self.title = testXMLValue(elem.find(nmSpc.OWS('Title')))

        self.abstract = testXMLValue(elem.find(nmSpc.OWS('Abstract')))
        if self.abstract is None:  # may have used the wcs ows namespace:
            self.abstract = testXMLValue(elem.find(nmSpc.WCS_OWS('Abstract')))
        if self.title is None:  # may have used the other wcs ows namespace:
            self.title = testXMLValue(elem.find(nmSpc.OWS('Abstract')))
        if elem.find(nmSpc.OWS('Abstract')) is not None:
            self.abstract = elem.find(nmSpc.OWS('Abstract')).text
        else:
            self.abstract = None
        self.keywords = [f.text for f in elem.findall(nmSpc.OWS('Keywords') + '/' + nmSpc.OWS('Keyword'))]
        # self.link = elem.find(nmSpc.WCS('Service') + '/' + nmSpc.WCS('OnlineResource')).attrib.get('{http://www.w3.org/1999/xlink}href', '')  # noqa

        if elem.find(nmSpc.OWS('ServiceType')) is not None:
            self.type = elem.find(nmSpc.OWS('ServiceType')).text
        if elem.find(nmSpc.OWS('ServiceTypeVersion')) is not None:
            self.version = elem.find(nmSpc.OWS('ServiceTypeVersion')).text

        if elem.find(nmSpc.WCS_OWS('Fees')) is not None:
            self.fees = elem.find(nmSpc.WCS_OWS('Fees')).text
        else:
            self.fees = elem.find(nmSpc.OWS('Fees')).text

        if elem.find(nmSpc.WCS_OWS('AccessConstraints')) is not None:
            self.accessConstraints = elem.find(nmSpc.WCS_OWS('AccessConstraints')).text
        else:
            self.accessConstraints = elem.find(nmSpc.OWS('AccessConstraints')).text


class ServiceProvider(object):
    """ Abstraction for ServiceProvider metadata
    implements IServiceProviderMetadata """

    def __init__(self, elem, nmSpc):
        name = elem.find(nmSpc.OWS('ProviderName'))

        if name is not None:
            self.name = name.text
        else:
            self.name = None

        # self.contact=ServiceContact(elem.find(nmSpc.OWS('ServiceContact')))
        self.contact = ContactMetadata(elem, nmSpc)
        self.url = self.name  # no obvious definitive place for url in wcs, repeat provider name?


class ContactMetadata(object):
    ''' implements IContactMetadata'''

    def __init__(self, elem, nmSpc):
        try:
            self.name = elem.find(nmSpc.OWS('ServiceContact') + '/' + nmSpc.OWS('IndividualName')).text
        except AttributeError:
            self.name = None

        try:
            self.organization = elem.find(nmSpc.OWS('ProviderName')).text
        except AttributeError:
            self.organization = None

        try:
            self.address = elem.find(
                nmSpc.OWS('ServiceContact') + '/' + nmSpc.OWS('ContactInfo') + '/' + nmSpc.OWS('Address') + '/' + nmSpc.OWS('DeliveryPoint')).text  # noqa
        except AttributeError:
            self.address = None
        try:
            self.city = elem.find(
                nmSpc.OWS('ServiceContact') + '/' + nmSpc.OWS('ContactInfo') + '/' + nmSpc.OWS('Address') + '/' + nmSpc.OWS('City')).text  # noqa
        except AttributeError:
            self.city = None

        try:
            self.region = elem.find(
                nmSpc.OWS('ServiceContact') + '/' + nmSpc.OWS('ContactInfo') + '/' + nmSpc.OWS('Address') + '/' + nmSpc.OWS('AdministrativeArea')).text  # noqa
        except AttributeError:
            self.region = None

        try:
            self.postcode = elem.find(
                nmSpc.OWS('ServiceContact') + '/' + nmSpc.OWS('ContactInfo') + '/' + nmSpc.OWS('Address') + '/' + nmSpc.OWS('PostalCode')).text  # noqa
        except AttributeError:
            self.postcode = None

        try:
            self.country = elem.find(
                nmSpc.OWS('ServiceContact') + '/' + nmSpc.OWS('ContactInfo') + '/' + nmSpc.OWS('Address') + '/' + nmSpc.OWS('Country')).text  # noqa
        except AttributeError:
            self.country = None

        try:
            self.email = elem.find(
                nmSpc.OWS('ServiceContact') + '/' + nmSpc.OWS('ContactInfo') + '/' + nmSpc.OWS('Address') + '/' + nmSpc.OWS('ElectronicMailAddress')).text  # noqa
        except AttributeError:
            self.email = None


class ContentMetadata(object):
    """Abstraction for WCS ContentMetadata
    Implements IContentMetadata
    """

    def __init__(self, elem, parent, service, nmSpc):
        """Initialize."""
        # TODO - examine the parent for bounding box info.

        self._service = service
        self._elem = elem
        self._parent = parent
        self.id = self._checkChildAndParent(nmSpc.WCS('Identifier'))
        self.description = self._checkChildAndParent(nmSpc.WCS('Description'))
        self.title = self._checkChildAndParent(nmSpc.OWS('Title'))
        self.abstract = self._checkChildAndParent(nmSpc.OWS('Abstract'))

        # keywords.
        self.keywords = []
        for kw in elem.findall(nmSpc.OWS('Keywords') + '/' + nmSpc.OWS('Keyword')):
            if kw is not None:
                self.keywords.append(kw.text)

        # also inherit any keywords from parent coverage summary (if there is one)
        if parent is not None:
            for kw in parent.findall(nmSpc.OWS('Keywords') + '/' + nmSpc.OWS('Keyword')):
                if kw is not None:
                    self.keywords.append(kw.text)

        self.boundingBox = None  # needed for iContentMetadata harmonisation
        self.boundingBoxWGS84 = None
        b = elem.find(nmSpc.OWS('WGS84BoundingBox'))
        if b is not None:
            lc = b.find(nmSpc.OWS('LowerCorner')).text
            uc = b.find(nmSpc.OWS('UpperCorner')).text
            self.boundingBoxWGS84 = (
                float(lc.split()[0]), float(lc.split()[1]),
                float(uc.split()[0]), float(uc.split()[1]),
            )

        # bboxes - other CRS
        self.boundingboxes = []
        for bbox in elem.findall(nmSpc.OWS('BoundingBox')):
            if bbox is not None:
                try:
                    lc = b.find(nmSpc.OWS('LowerCorner')).text
                    uc = b.find(nmSpc.OWS('UpperCorner')).text
                    boundingBox = (
                        float(lc.split()[0]), float(lc.split()[1]),
                        float(uc.split()[0]), float(uc.split()[1]),
                        b.attrib['crs'])
                    self.boundingboxes.append(boundingBox)
                except Exception:
                    pass

        # others not used but needed for iContentMetadata harmonisation
        self.styles = None
        self.crsOptions = None

        # SupportedCRS
        self.supportedCRS = []
        for crs in elem.findall(nmSpc.WCS('SupportedCRS')):
            self.supportedCRS.append(Crs(crs.text))

        # SupportedFormats
        self.supportedFormats = []
        for format in elem.findall(nmSpc.WCS('SupportedFormat')):
            self.supportedFormats.append(format.text)

    # grid is either a gml:Grid or a gml:RectifiedGrid if supplied as part of the DescribeCoverage response.
    def _getGrid(self):
        grid = None
        # TODO- convert this to 1.1 from 1.0
        # if not hasattr(self, 'descCov'):
        #         self.descCov=self._service.getDescribeCoverage(self.id)
        # gridelem= self.descCov.find(ns('CoverageOffering/')+ns('domainSet/')+ns('spatialDomain/')+'{http://www.opengis.net/gml}RectifiedGrid')  # noqa
        # if gridelem is not None:
        #     grid=RectifiedGrid(gridelem)
        # else:
        #     gridelem=self.descCov.find(ns('CoverageOffering/')+ns('domainSet/')+ns('spatialDomain/')+'{http://www.opengis.net/gml}Grid')
        #     grid=Grid(gridelem)
        return grid
    grid = property(_getGrid, None)

    # time limits/postions require a describeCoverage request therefore only resolve when requested
    def _getTimeLimits(self):
        timelimits = []
        # TODO- convert this to 1.1 from 1.0
        # for elem in self._service.getDescribeCoverage(self.id).findall(
        #         ns('CoverageDescription/') + ns('Domain/') + ns('TemporalDomain/') + ns('TimePeriod/')):
        #     subelems = elem.getchildren()
        #     timelimits = [subelems[0].text, subelems[1].text]
        return timelimits
    timelimits = property(_getTimeLimits, None)

    # TODO timepositions property
    def _getTimePositions(self):
        return []
    timepositions = property(_getTimePositions, None)

    def _checkChildAndParent(self, path):
        ''' checks child coverage  summary, and if item not found checks higher level coverage summary'''
        try:
            value = self._elem.find(path).text
        except Exception:
            try:
                value = self._parent.find(path).text
            except Exception:
                value = None
        return value
