# Copyright (c) 2003 by Intevation GmbH
# Authors:
# Jonathan Coles <jonathan@intevation.de>
#
# This program is free software under the GPL (>=v2)
# Read the file COPYING coming with Thuban for details.

"""Handle resources loaded from files such as projections"""

__version__ = "$Revision: 1.21 $"
# $Source: /thubanrepository/thuban/Thuban/Model/resource.py,v $
# $Id: resource.py,v 1.21 2003/11/19 19:48:47 bh Exp $


import os
import os.path
import weakref
import traceback

import Thuban
from Thuban import _

from Thuban.Lib.fileutil import get_application_dir

from Thuban.Model.xmlreader import XMLReader
from Thuban.Model.xmlwriter import XMLWriter
from Thuban.Model.proj import Projection, ProjFile
from xml.sax import SAXParseException

projdir = \
        os.path.join(Thuban.__path__[0], os.pardir, "Resources", "Projections")


PROJ_EXT = ".proj"

# Determine the status of GDAL support. If GDAL is supported
# gdal_support_status will be set to the empty string, otherwise to a
# string with information why it isn't supported
#
# GDAL is supported if we can import both the thuban specific gdalwarp
# module and the GDAL python bindings.
for _module in ("gdalwarp", "gdal"):
    try:
        __import__(_module)
    except ImportError, val:
        gdal_support_status = (_("No GDAL support because module '%s'"
                                 " cannot be imported. Python exception: '%s'")
                               % (_module, str(val)))
        break
else:
    gdal_support_status = ""

def has_gdal_support():
    return gdal_support_status == ""


projfile_cache = weakref.WeakValueDictionary()

def clear_proj_file_cache():
    """Clear the cache of ProjFile objects maintained by read_proj_file.

    This function is probably only useful for the test suite.
    """
    projfile_cache.clear()

def read_proj_file(filename):
    """Read a .proj file and return a ProjFile object and warnings

    The return value is a tuple with the ProjFile object and a list of
    strings with warnings messages that might have been generated by the
    proj file parser.

    The objects returned cached so that reading the same file
    (identified by its absolute name) several times yields the same
    ProjFile object. The cache uses weak references so the objects will
    be removed from the cache once the last reference an object in the
    cache is removed.

    Raises IOError if the file cannot be opened, OSError if the file
    cannot be read and SAXParseException if the file is not valid XML.
    """
    filename = os.path.abspath(filename)
    if filename in projfile_cache:
        return projfile_cache[filename], []
    else:
        handler = ProjFileReader()
        handler.read(filename)
        proj_file = handler.GetProjFile()
        projfile_cache[filename] = proj_file
        return proj_file, handler.GetWarnings()

def write_proj_file(pf):
    """Write a single .proj file

    Raises IOError if the file cannot be written.
    """

    saver = ProjFileSaver(pf)
    saver.write(pf.GetFilename())

#
# Constants for the get_system_proj_file function
#

# The default projection file with a few predefined projections
DEFAULT_PROJ_FILE = "defaults.proj"

# The epsg projections.
EPSG_PROJ_FILE = "epsg.proj"

# Deprecated EPSG projections.
EPSG_DEPRECATED_PROJ_FILE = "epsg-deprecated.proj"

def get_system_proj_file(filename):
    """Return the projections from the indicated file and a list with warnings

    The filename argument should be the name of a file in the directory
    with Thuban's default projection files (Resources/Projections/). If
    possible callers should not use hardwired string literal for the
    name to avoid unnecessary duplication. Instead they should use one
    of the predefined constants, currently DEFAULT_PROJ_FILE,
    EPSG_PROJ_FILE or EPSG_DEPRECATED_PROJ_FILE.

    The return value is a tuple with the projections in a ProjFile
    object and a list of strings with warning messages. The warnings
    list is usually empty but may contain messages about ignored errors.

    If the file could could not be opened return an empty projection
    file object set to store data in the indicated default file.
    """
    fullname = os.path.join(projdir, filename)
    try:
        return read_proj_file(fullname)
    except (OSError, IOError, SAXParseException), val:
        msg = _('Could not read "%s": %s') % (fullname, str(val))
        return ProjFile(fullname), [msg]

def get_user_proj_file():
    """Return the user's projections and a list with warnings

    The projections read from the user's thuban projection file (usually
    in ~/.thuban/user.proj). The return value is a tuple with the
    projections in a ProjFile object and a list of strings with warning
    messages. The warnings list is usually empty but may contain
    messages about ignored errors.

    If the file could could not be opened return an empty projection
    file object set to store data in the default file.
    """
    usrdir  = get_application_dir()
    filename = os.path.join(usrdir, "user.proj")
    try:
        return read_proj_file(filename)
    except (OSError, IOError, SAXParseException), val:
        msg = _('Could not read "%s": %s') % (filename, str(val))
        return ProjFile(filename), [msg]


class ProjFileReader(XMLReader):

    def __init__(self):
        XMLReader.__init__(self)
        self.projfile = ProjFile("")
        self.warnings = []

        XMLReader.AddDispatchers(self,
            {'projection': ("start_projection", "end_projection"),
             'parameter':  ("start_parameter", None)})

    def read(self, file_or_filename):
        XMLReader.read(self, file_or_filename)

        self.projfile.SetFilename(XMLReader.GetFilename(self))

    def start_projection(self, name, qname, attrs):
        self.params = []
        name = self.encode(attrs.get((None, 'name')))
        if name is None:
            name = _("Unknown")
        self.name = name
        self.epsg = self.encode(attrs.get((None, 'epsg')))

    def end_projection(self, name, qname):
        try:
            proj = Projection(self.params, self.name, epsg = self.epsg)
        except IOError, val:
            self.warnings.append(_('Error in projection "%s": %s')
                                 % (self.name, str(val)))
        else:
            self.projfile.Add(proj)

    def start_parameter(self, name, qname, attrs):
        s = attrs.get((None, 'value'))
        s = str(s) # we can't handle unicode in proj
        self.params.append(s)

    def GetProjFile(self):
        return self.projfile

    def GetWarnings(self):
        """Return the list of warning messages that may have been produced"""
        return self.warnings


class ProjFileSaver(XMLWriter):

    def __init__(self, pf):
        XMLWriter.__init__(self)
        self.__pf = pf

    def write(self, file_or_filename):
        XMLWriter.write(self, file_or_filename)

        self.write_header("projectionlist", "projfile.dtd")
        self.write_projfile(self.__pf)
        self.close()

    def write_projfile(self, pf):

        self.open_element("projectionlist")

        for p in pf.GetProjections():
            attrs = {"name": p.GetName()}
            if p.EPSGCode():
                attrs["epsg"] = p.EPSGCode()
            self.open_element("projection", attrs)

            for param in p.GetAllParameters():
                self.write_element("parameter", {"value": param})

            self.close_element("projection")

        self.close_element("projectionlist")


