# Copyright (C) 2003 by Intevation GmbH
# Authors:
# Jan-Oliver Wagner <jan@intevation.de>
#
# This program is free software under the GPL (>=v2)
# Read the file COPYING coming with Thuban for details.

"""
Import a ArcView project file (.apr) and convert it
to Thuban.
"""

__version__ = "$Revision: 1.3 $"

import os, sys

from types import StringType

from wxPython.wx import *

from Thuban.Model.extension import Extension
from Thuban.Model.base import TitledObject, Modifiable
from Thuban.UI.command import registry, Command
import Thuban.UI.mainwindow
from Thuban import _
from Thuban.Model.layer import Layer
from Thuban.Model.classification import ClassGroupRange, ClassGroupSingleton

from odb import ODBBaseObject
from apr import APR_LClass, APR_TClr, APR_BLnSym, APR_BMkSym, APR_BShSym

class ODBExtension(Extension):
    def TreeInfo(self):
        return (_("Extension: %s") % self.title,
                [ object.TreeInfo() for object in self.objects ])

class APR_FTheme(ODBBaseObject):
    _obj_refs = [ 'Source', 'Legend' ]

class APR_Legend(ODBBaseObject):
    """Legend object.
    There could one or more Class objects. Each class corresponds to a
    Child in the Symbols.
    """
    _obj_refs = [ 'FieldNames', 'Symbols', 'Class', 'NullSym',
                  'NullValues', 'StatValues' ]

class APR_Project(ODBBaseObject):
    _obj_refs = [ 'Doc' ]

class APR_ShpSrc(ODBBaseObject):
    _obj_refs = [ 'Name' ]

class APR_SrcName(ODBBaseObject):
    _obj_refs = [ 'FileName' ]

class APR_SymList(ODBBaseObject):
    _obj_refs = [ 'Child' ]

class APR_View(ODBBaseObject):
    _obj_refs = [ 'Theme', 'ITheme' ]

class ODB(TitledObject, Modifiable):

    def __init__(self):
        TitledObject.__init__(self, 'ODB Object')
        self._objects = {}
        self._version = None
        self._filename = None
        self.name = None # required for Thuban.model.extension.Extension

    def SetFileName(self, fname):
        self._filename = fname
        self.name = fname

    def AddObject(self, object):
        self._objects[object.number] = object

    def GetObjects(self):
        return self._objects

    def GetProject(self):
        """Return the main Root if it is a Project, else None."""
        # it is assumed that the first object is the ODB object
        if self._objects[1].type != 'ODB':
            return None
        if self._objects[1].FirstRootClassName != 'Project':
            return None

        # it is assumed that the second object is the first root
        o = self._objects[2]
        if o.type != 'Project':
            return None
        return o

    def TreeInfo(self):
        items = []
        items.append(_('Format version: %s') % self._version)
        p = self.GetProject()
        items.append(_('Project Name: %s') % p.Name)
        for doc in p.Get('Doc'):
            items.append(doc.TreeInfo())
        return [_("ODB File '%s'" % self._filename), items]

def parse_apr(fname):
    """Load a ArcView project file.

    fname  -- Filename of the .apr file

    Return: the ODB class object.
    """
    odb = ODB()
    odb.SetFileName(fname)

    apr = open(fname, 'r').readlines()

    # get the version from the first line (eg. from "/3.1")
    odb._version = apr.pop(0)[1:]

    i = 0
    in_object = False
    for line in apr:
        i += 1
        if line[0] == '(':
            line = line[1:]
            type, number = line.split('.')
            number = int(number)
            class_name = 'APR_' + type
            try:
                clazz = eval(class_name)
                object = clazz(odb, type, number)
            except:
                object = ODBBaseObject(odb, type, number)
            in_object = True
            continue
        if line[0] == ')':
            in_object = False
            odb.AddObject(object)
        if in_object:
            line = line.strip()
            if len(line) == 0: continue
            try:
                property, value = line.split(':', 1)
                property = property.strip()
                value = value.strip()
            except:
                print "Error in line %d:" % i, line
                continue
            if value[0] == '"':
                value = value[1:-1]
            setattr(object, property, value)
    return odb

def import_apr_dialog(context):
    """Request filename from user and run importing of apr file.

    context -- The Thuban context.
    """
    dlg = wxFileDialog(context.mainwindow,
                       _("Select APR file"), ".", "",
                       _("ArcView Project Files (*.apr)|*.apr|") +
                       _("All Files (*.*)|*.*"),
                       wxOPEN|wxOVERWRITE_PROMPT)
    if dlg.ShowModal() == wxID_OK:
        filename = dlg.GetPath()
        dlg.Destroy()
    else:
        return

    odb = parse_apr(filename)
    if odb is None:
        context.mainwindow.RunMessageBox(_("Import APR"), _("Loading failed"))
        return
    else:
        context.mainwindow.RunMessageBox(_("Import APR"),
                                         _("%d objects loaded" % 
                                            len(odb.GetObjects().keys())))

    # find the views of the APR file
    views = {}
    p = odb.GetProject()
    for doc in p.Get('Doc'):
        if doc.type != 'View':
            continue
        views[doc.Name] = doc

    # it is possible that a APR file has no view at all
    if len(views) == 0:
        context.mainwindow.RunMessageBox(_("Import APR"),
                _("No view found in APR file"))
        return

    # let the user select one of the views
    if len(views) > 1:
        titles = views.keys()
        dlg = wxSingleChoiceDialog(context.mainwindow,
                                   _('Pick a View to import:'),
                                   _('Import APR'), titles,
                                   style = wxDEFAULT_DIALOG_STYLE | 
                                           wxRESIZE_BORDER)
        if dlg.ShowModal() == wxID_OK:
            view = views[views.keys()[dlg.GetSelection()]]
        else:
            return
    else:
        view = views[views.keys()[0]]

    # load the themes of the View as layers into Thuban
    count_theme = 0
    count_theme_fail = 0
    for theme in view.Get('Theme'):
        if theme.type !=  'FTheme':
            continue
        count_theme += 1
        filename = theme.Get('Source').Get('Name').Get('FileName').Path
        try:
            store = context.application.Session().OpenShapefile(filename)
        except IOError:
            # the layer couldn't be opened
            context.mainwindow.RunMessageBox(_('Add Layer'),
                           _("Can't open the file '%s'.") % filename)
            count_theme_fail += 1
        else:
            title = theme.Name
            apr_legend = theme.Get('Legend')

            map = context.mainwindow.canvas.Map()

            # create layer
            layer = Layer(title, store)

            # set the field for classification (if there is one in the apr)
            if hasattr(apr_legend, 'FieldNames'):
                apr_fieldname = apr_legend.Get('FieldNames').S
                # unfortunately, the APR file does not store the actual
                # Name of the column, but always makes the first character
                # a capital letter, the rest lower case.
                # Therefore, we have to search through the table of the
                # layer to find out the correct spelling.
                table_columns = layer.ShapeStore().Table().Columns()
                for column in table_columns:
                    if apr_fieldname.lower() == column.name.lower():
                        layer.SetClassificationColumn(column.name)
                        break

            clazz = layer.GetClassification()
            apr_classes = apr_legend.Get('Class')
            if not isinstance(apr_classes, list):
                apr_classes = [ apr_classes ]
            apr_symbols = apr_legend.Get('Symbols').Get('Child')
            if not isinstance(apr_symbols, list):
                apr_symbols = [ apr_symbols ]
            i = -1
            for symbol in apr_symbols:
                i += 1

                if hasattr(apr_classes[i], 'IsNoData'):
                    group = clazz.GetDefaultGroup()
                    group.SetLabel(apr_classes[i].Label)
                    continue

                # create a new group property from the symbol
                prop = symbol.GetThubanProp()

                # build range
                range = apr_classes[i].GetThubanRange()

                if isinstance(range, StringType):
                    new_group = ClassGroupSingleton(value = range,
                                    props = prop,
                                    label = apr_classes[i].GetLabel())
                else:
                    new_group = ClassGroupRange(_range = range,
                                    props = prop,
                                    label = apr_classes[i].GetLabel())
                clazz.AppendGroup(new_group)

            map.AddLayer(layer)

    map.SetTitle(view.Name)

    # fit the new map to the window
    context.mainwindow.canvas.FitMapToWindow()

    context.mainwindow.RunMessageBox(_('Import APR'),
        _('Imported %d out of %d themes of view "%s" ...') % \
            (count_theme - count_theme_fail, count_theme, view.Name))

    # import_apr as an extension to Thuban
    if context.session.HasExtensions():
        for ext in context.session.Extensions():
            if ext.Title() == apr_import_extension.Title():
                ext.AddObject(odb)
                return

    # no extension found, so lets make a new
    context.session.AddExtension(apr_import_extension)
    apr_import_extension.AddObject(odb)

if __name__ == "__main__": # import_apr executed as a command line tool
    if len(sys.argv) == 2:
        odb = parse_apr(sys.argv[1])
        print "%d objects loaded" % len(odb.GetObjects().keys())

        def print_structured_list(lst, indent = ''):
            for item in lst:
                if isinstance(item, StringType):
                    print indent + item
                elif isinstance(item, list):
                    print_structured_list(item, indent + ' ')

        print_structured_list(odb.TreeInfo())
        sys.exit(0)
    else:
        print 'usage: %s apr-file' % sys.argv[0]
        sys.exit(1)

apr_import_extension = ODBExtension('APR Import')

# register the new command
registry.Add(Command('import-apr', _('Import apr-file...'), import_apr_dialog,
                         helptext = _('Import a ArcView project file')))

# find the experimental menu (create it anew if not found)
main_menu = Thuban.UI.mainwindow.main_menu
experimental_menu = main_menu.find_menu('experimental')
if experimental_menu is None:
    experimental_menu = main_menu.InsertMenu('experimental', _('Experimenta&l'))

# finally add the new entry to the experimental menu
experimental_menu.InsertItem('import-apr')
