# Copyright (c) 2001, 2003, 2006 by Intevation GmbH
# Authors:
# Bernhard Herzog <bh@intevation.de>
# Bernhard Reiter <bernhard@intevation.de>
#
# This program is free software under the GPL (>=v2)
# Read the file COPYING coming with Thuban for details.
__version__ = "$Revision: 2756 $"

from types import StringTypes
import locale

from Thuban import _
from Thuban.Lib.connector import Publisher

import Projection
BaseProjection = Projection.Projection
del Projection

from messages import PROJECTION_ADDED, PROJECTION_REPLACED, PROJECTION_REMOVED

PROJ_UNITS_METERS  = 1
PROJ_UNITS_DEGREES = 2

def _do_we_have_to_work_around_broken_proj():
    """If we have a problematic locale, check if proj results are good."""
    if locale.localeconv()['decimal_point'] != '.':
        params = ["proj=latlong", "to_meter=0.01745", "ellps=clrk66"]
        proj = BaseProjection(params)
        result1 = proj.Forward(1,1)

        # using setlocale because of python 2.[345] problem with getlocale here
        savedlocale = locale.setlocale(locale.LC_NUMERIC)
        locale.setlocale(locale.LC_NUMERIC, "C")

        proj = BaseProjection(params)
        result2 = proj.Forward(1,1)

        locale.setlocale(locale.LC_NUMERIC, savedlocale)

        if result1 != result2:
            return True

    return False

class Projection(BaseProjection):
    """A proj4 projection object that remembers the parameters.

    The proj library is not robust against decimal_point != '.' locales.
    Since python 2.4 calls C extensions with the set locale, it can create
    a problem.  It seems that calling
        self.assuregoodlocale()
        self.assureinitlocale()
    before BaseProjection.__init__() is enough to work around this.

    We assuming that the locale stays the same after a projection
    has been initialised 
    and thus we can return to it in self.assureinitlocale().
    """

    def __init__(self, params, name = None, epsg = None):
        """Initialize the Projection

        Parameters:

        params -- a list of 'parameter=value' strings

        name -- (optional) The name of the projection. If None or omitted
                it defaults to 'Unknown' in the local language.

        epsg -- (optional) The EPSG code as a string.
        """
        # using setlocale because of python 2.[345] problem with getlocale here
        self.initlocale = locale.setlocale(locale.LC_NUMERIC)
        self.work_around_broken_proj = _do_we_have_to_work_around_broken_proj()

        self.assuregoodlocale()
        BaseProjection.__init__(self, params)
        self.assureinitlocale()

        if name is None:
            self.name = _("Unknown")
        elif isinstance(name, StringTypes):
            self.name = name

        self.epsg = epsg
        self.params = params

    def assuregoodlocale(self):
        if self.work_around_broken_proj:
            locale.setlocale(locale.LC_NUMERIC, "C")

    def assureinitlocale(self):
        if self.work_around_broken_proj:
            locale.setlocale(locale.LC_NUMERIC, self.initlocale)

    def _transform_bbox(self, trafo, bbox):
        # This is not really the correct way to determine the bbox of a
        # projected bbox, but for now it works well enough
        llx, lly, urx, ury = bbox
        xs = []; ys = []
        for x, y in ((llx, lly), (llx, ury), (urx, lly), (urx, ury)):
            x, y = trafo(x, y)
            xs.append(x); ys.append(y)
        return min(xs), min(ys), max(xs), max(ys)

    def ForwardBBox(self, bbox):
        """Return the bounding box of the corners of the bounding box bbox
        """
        return self._transform_bbox(self.Forward, bbox)

    def InverseBBox(self, bbox):
        return self._transform_bbox(self.Inverse, bbox)

    def GetName(self):
        """Return the name of the projection."""
        return self.name

    def Label(self):
        if self.epsg:
            return "EPSG % 5s %s" % (self.epsg, self.name)
        return self.name

    def EPSGCode(self):
        """Return the EPSG code as a string or None if there is none"""
        return self.epsg

    def GetParameter(self, param):
        """Return the projection value for the given parameter.

        If 'param' exists as a valid parameter return the associated
        value as a string. If the parameter does not have a value (like
        e.g. the 'south' parameter for utm) then the value is the
        parameter name itself.

        If the parameter doesn't exist return an empty string.
        """

        for pair in self.params:
            if "=" in pair:
                p, v = pair.split("=")
            else:
                p = v = pair
            if p == param:
                return v

        return ""

    def GetAllParameters(self):
        """Return list of 'parameter=value' strings"""
        return self.params

    def GetProjectedUnits(self):
        if self.GetParameter("proj") in [ 'latlong', 'longlat' ]:
            return PROJ_UNITS_DEGREES
        else:
            return PROJ_UNITS_METERS

    def __repr__(self):
        return self.name + ": " + repr(self.params)


class ProjFile(Publisher):

    def __init__(self, filename):
        """Intialize the ProjFile.

        filename -- name of the file that this ProjFile represents.
        """

        self.__projs = []

        self.SetFilename(filename)

    def Add(self, proj):
        """Add the projection to the end of the file."""
        self.__projs.append(proj)
        self.issue(PROJECTION_ADDED, proj)

    def Remove(self, proj):
        """Remove the object proj from the projection file.

        Raises a ValueError is proj is not found.
        """
        self.__projs.remove(proj)
        self.issue(PROJECTION_REMOVED, proj)

    def Replace(self, oldproj, newproj):
        """Replace the object 'oldproj' with 'newproj'.

        Raises ValueError if oldproj is not in the file.
        """
        self.__projs[self.__projs.index(oldproj)] = newproj
        self.issue(PROJECTION_REPLACED, oldproj, newproj)

    def GetFilename(self):
        """Return the filename where the ProjFile was read or will be
        written to.
        """

        return self.__filename

    def SetFilename(self, filename):
        """Set the filename where the ProjFile will be written to."""
        self.__filename = filename

    def GetProjections(self):
        """Return a list of the projections in the order they were read
        from the file or will be written.

        This is not a deep copy list, so any modifications made to the
        Projection objects will be written to the file.
        """

        return self.__projs

