# Copyright (c) 2003, 2004, 2007, 2008 by Intevation GmbH
# Authors:
# Didrik Pinte <dpinte@dipole-consulting.com>
# Jan-Oliver Wagner <jan@intevation.de>
# Martin Schulze <joey@infodrom.org>
#
# 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

"""
Graphic Layer via OGC WMS.

class WMSLayer:
    __init__()

    LatLongBoundingBox()
    BoundingBox()

    getFormat(format)
    calcFormat(formats)

    getFormats()
    getLayers()
    getLayerTitle()

    getWMSFormat()
    setWMSFormat(format)

    GetMapImg(width, height, bbox)

Requirements:
    - OWSLib <http://trac.gispython.org/projects/PCL/wiki/OwsLib>

Requires the owslib installed regularily on the system or checked out
next to the Thuban checkout.  Or set the PYTHONPATH to the OWSLib
directory before starting Thuban.

"""

__version__ = "$Revision: 2868 $"
# $Source$
# $Id: layer.py 2868 2008-12-08 08:50:53Z dpinte $


from Thuban import internal_from_unicode, _
from Thuban.Model.layer import BaseLayer
from Thuban.Model.resource import get_system_proj_file, EPSG_PROJ_FILE, \
     EPSG_DEPRECATED_PROJ_FILE
from Thuban.UI.common import ThubanBeginBusyCursor, ThubanEndBusyCursor

from owslib.wms import WebMapService

def epsg_code_to_projection(epsg):
    """Find the projection for the given epsg code.

    epsg -- EPSG code as string
    """
    proj_file, warnings = get_system_proj_file(EPSG_PROJ_FILE)

    for proj in proj_file.GetProjections():
        if proj.EPSGCode() == epsg:
            return proj
    proj_file, warnings = get_system_proj_file(EPSG_DEPRECATED_PROJ_FILE)
    for proj in proj_file.GetProjections():
        if proj.EPSGCode() == epsg:
            return proj
    return None


class WMSLayer(BaseLayer):
    """
    WMS Layer

    This layer incorporates all methods from the Thuban BaseLayer and
    adds specific methods for operating with a WMS server.
    """

    def __init__(self, title, url):
        """Initializes the WMSLayer.

        title -- Title of this layer.
        url -- URL of the WMS-Server wich must contain '?'

        If an error occured, self.error_msg is a string describing
        the problem(s). Else, self.error_msg is None.
        """
        BaseLayer.__init__(self, title, visible = True, projection = None)
        self.url = url
        self.bbox = None
        self.latlonbbox = None
        self.error_msg = None
        self.wms_layers = []
        self.capabilities = None
        self.cached_bbox = None

        # Change the cursor to demonstrate that we're busy but working
        ThubanBeginBusyCursor()
        self.wmsserver = WebMapService(url, version="1.1.1")
        self.capabilities = [op.name for op in self.wmsserver.operations]
        ThubanEndBusyCursor()

        # name of the top layer of the remote map
        layers = list(self.wmsserver.contents)                   
        if len(layers) == 0:
            self.error_msg = _('No layers found in remote resource:\n'\
                               '%s') % url
            return
        top_layer = layers[1]
        self.wms_layers = [top_layer]

        # first projection of the top layer
        crs = self.wmsserver[top_layer].crsOptions
        if len(crs) == 0:
            self.error_msg = _('No LatLonBoundingBox found for top layer %s')\
                             % top_layer
            return
        # extract only the EPSG code from the EPSG string 
        # received 'EPSG:4326' but keep only '4326'
        top_srs = crs[0].split(':')[1]

        # LatLonBox of the top layer
        bbox = self.wmsserver.contents[top_layer].boundingBoxWGS84
        self.latlonbbox = (float(bbox[0]),
                     float(bbox[1]),
                     float(bbox[2]),
                     float(bbox[3])) 
        
        # BoundingBox of the top layer
        # Do we really need to know the bbox not in WGS84 ? 
        bbox = self.wmsserver.contents[top_layer].boundingBox
        if bbox is None or len(bbox) == 0:
        #    self.error_msg = _('No BoundingBox found for layer %s and EPSG %s')\
        #                     % (top_layer, top_srs)
            self.bbox = None
        else :
            self.bbox = (float(bbox[0]),
                     float(bbox[1]),
                     float(bbox[2]),
                     float(bbox[3]))
        if self.bbox is None:
            self.bbox =self.latlonbbox

        # get projection
        p = epsg_code_to_projection(top_srs)
        self.SetProjection(p)

        if p is None:
            self.error_msg = _('EPSG projection code %s not found!\n'\
                               'Setting projection to "None".\n'\
                               'Please set an appropriate projection yourself.'\
                               % top_srs)

        # pre-determine the used format
        self.wmsformat, self.format = \
        self.calcFormat(self.wmsserver.getOperationByName('GetMap').formatOptions)
        if self.wmsformat is None:
            self.error_msg = \
                _('No supported image format found in remote resource')
            return

        # get and set the title
        self.SetTitle(internal_from_unicode(self.wmsserver.identification.title))


    def LatLongBoundingBox(self):
        """
        Return the layer's bounding box in lat-lon
        """
        return self.latlonbbox


    def BoundingBox(self):
        """
        Return the layer's bounding box in the intrinsic coordinate system
        """
        return self.bbox


    def getFormat(self, format):
        """
        Return the image format for the render engine

        format -- format as returned by the WMS server

        If no mapping was found, None is returned.

        This routine uses a simple heuristic in order to find the
        broken down image format to be used with the internal render
        engine.

        An exception rule is implemented in order to not accept
        image/wbmp or WBMP which refers to WAP bitmap format and is
        not supported by the included render engine.
        """
        fmap = {'png' : "PNG",
                'jpeg': "JPEG",
                'jpg' : "JPEG",
                'tif' : "TIFF",
                'gif' : "GIF",
                'wbmp': None,
                'bmp' : "BMP"}

        for f in fmap.keys():
            if format.lower().find(f) > -1:
                    return fmap[f]
        return None

        
    def calcFormat(self, formats):
        """
        Calculate the preferred image format

        formats -- list of formates as returned by the WMS server

        The following priority is used:
        - PNG
        - JPEG
        - TIFF
        - GIF
        - BMP

        If no matching format was found, None, None will be returned.

        An exception rule is implemented in order to not accept
        image/wbmp or WBMP which refers to WAP bitmap format and is
        not supported by the included render engine.
        """
        prio = ['png', 'gif', 'jpeg', 'bmp']
        for p in prio:
            for f in formats:
                if f.lower().find(p) > -1:
                    if f.lower().find('wbmp') == -1:
                        return f, self.getFormat(f)
        return None, None
        

    def getFormats(self):
        """
        Return the list of supported image formats by the WMS server

        These formats may be used in the WMS GetMap request.  Data is
        retrieved from the included WMSCapabilities object.

        The called method from WMSCapabilities will default to
        'image/jpeg' if no format is recognised in XML Capabilities,
        assuming that JPEG will always be supported on the server side
        with this encoding.
        """
        return self.wmsserver.getOperationByName('GetMap').formatOptions


    def getLayers(self):
        """
        Return the list of layer names supported by the WMS server

        Data is retrieved from the included WMSCapabilities object.

        Only named layers will be returned, since a layer may have a
        title but doesn't have to have a name associated to it as
        well.  If no layers were found, an empty list is returned.
        """
        return list(self.wmsserver.contents)


    def getLayerTitle(self, layer):
        """
        Return the title of the named layer

        Data is retrieved from the included WMSCapabilities object.

        If no such title or no such layer exists, an empty string is
        returned.
        """
        return self.wmsserver[layer].title


    def getWMSFormat(self):
        """
        Return the image format that is used for WMS GetMap requests
        """
        return self.wmsformat


    def setWMSFormat(self, format):
        """
        Set the image format that is used for WMS GetMap requests

        format -- format, one of getFormats()
        """
        self.wmsformat = format
        self.format = self.getFormat(format)


    def getVisibleLayers(self):
        """
        Return the list of names for all visible layers

        """
        return self.wms_layers


    def setVisibleLayers(self, layers):
        """
        Set the list of names for all visible layers

        """
        self.wms_layers = layers


    def GetMapImg(self, width, height, bbox):
        """
        Retrieve a new map from the WMS server and return it

        width -- width in pixel of the desired image
        height -- height in pixel of the desired image
        bbox -- array of min(x,y) max(x,y) in the given SRS

        SRS and used image format will be retrieved from within the
        layer itself.
        """
        bbox_dict = { 'minx': bbox[0], 'miny': bbox[1],
                      'maxx': bbox[2], 'maxy': bbox[3] }

        if self.cached_bbox is not None:
            for i in xrange(4):
                if self.cached_bbox[i] != bbox[i]:
                    break
            else:
                if self.cached_response is not None:
                    return self.cached_response, self.format
        self.cached_bbox = bbox
        # Change the cursor to demonstrate that we're busy but working
        ThubanBeginBusyCursor()

        epsg_id = int(self.GetProjection().EPSGCode())

        wms_response = self.wmsserver.getmap(layers=self.wms_layers, 
                                             styles=None, 
                                             srs="EPSG:%s" % epsg_id, 
                                             bbox=bbox, 
                                             size=(width, height), 
                                             format=self.wmsformat, 
                                             transparent=False)
        self.cached_response = wms_response.read()
        
        ThubanEndBusyCursor()
        
        return self.cached_response , self.format
