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

"""
Test the Session class
"""

__version__ = "$Revision: 1676 $"
# $Source$
# $Id: test_session.py 1676 2003-08-28 13:14:44Z bh $

import os
import unittest

import support
support.initthuban()

from Thuban.Model.messages import CHANGED, MAPS_CHANGED, FILENAME_CHANGED, \
     MAP_PROJECTION_CHANGED, MAP_LAYERS_CHANGED, \
     LAYER_VISIBILITY_CHANGED, LAYER_CHANGED, TABLE_REMOVED
from Thuban.Model.session import Session
from Thuban.Model.map import Map
from Thuban.Model.layer import Layer
from Thuban.Model.proj import Projection
from Thuban.Model.color import Color
from Thuban.Model.table import MemoryTable, FIELDTYPE_STRING, \
                               FIELDTYPE_INT, FIELDTYPE_DOUBLE
from Thuban.Model.data import DerivedShapeStore
from Thuban.Model.transientdb import TransientJoinedTable

class TestSessionSimple(unittest.TestCase):

    """Very simple test cases for Session"""

    def setUp(self):
        """Initialize self.session to None"""
        self.session = None

    def tearDown(self):
        """Call self.session.Destroy() and reset self.session to None"""
        self.session.Destroy()
        self.session = None

    def test_initial_state(self):
        """Test Session's initial state"""
        session = self.session = Session("Test Session")
        self.assertEquals(session.Title(), "Test Session")
        self.assertEquals(session.Maps(), [])
        self.assertEquals(session.Tables(), [])
        self.assertEquals(session.ShapeStores(), [])
        self.assertEquals(session.filename, None)
        self.failIf(session.HasMaps())
        self.failIf(session.WasModified())

    def test_add_table(self):
        """Test Session.AddTable()"""
        session = self.session = Session("Test Session")
        memtable = MemoryTable([("type", FIELDTYPE_STRING),
                                ("value", FIELDTYPE_DOUBLE),
                                ("code", FIELDTYPE_INT)],
                               [("OTHER/UNKNOWN", -1.5, 11),
                                ("RUINS", 0.0, 1),
                                ("FARM", 3.141, 2),
                                ("BUILDING", 2.5, 3),
                                ("HUT", 1e6, 4),
                                ("LIGHTHOUSE", -0.01, 5)])

        # The session should be unmodified before the AddTable call and
        # modified afterwards and of course the table should show up in
        # Tables
        self.failIf(session.WasModified())
        table = session.AddTable(memtable)
        self.assertEquals(session.Tables(), [table])
        self.failUnless(session.WasModified())

    def test_open_table_file(self):
        """Test Session.OpenTableFile()"""
        session = self.session = Session("Test Session")
        filename = os.path.join("..", "Data", "iceland",
                                "roads-line.dbf")
        table = session.OpenTableFile(filename)
        self.assertEquals(session.Tables(), [table])

    def test_open_shapefile(self):
        """Test Session.OpenShapefile()"""
        session = self.session = Session("Test Session")
        filename = os.path.join("..", "Data", "iceland",
                                "roads-line.shp")
        store = session.OpenShapefile(filename)
        self.assertEquals(store.FileName(), os.path.abspath(filename))
        # The filetype of a shapefile is "shapefile"
        self.assertEquals(store.FileType(), "shapefile")
        # The shapestore itself depends on nothing else
        self.assertEquals(store.Dependencies(), ())
        # The shapestore's table depends on the shapestore
        self.assertEquals(store.Table().Dependencies(), (store,))

        self.assertEquals(session.Tables(), [store.Table()])

    def test_add_shapestore(self):
        """Test Session.AddShapeStore()"""
        session = self.session = Session("Test Session")
        filename = os.path.join("..", "Data", "iceland",
                                "roads-line.shp")
        try:
            store = session.OpenShapefile(filename)
            derived = DerivedShapeStore(store, store.Table())
            session.AddShapeStore(derived)
            self.assertEquals(session.ShapeStores(), [store, derived])
        finally:
            store = derived = None


    def test_unreferenced_tables(self):
        """Test Session.UnreferencedTables()"""
        session = self.session = Session("Test Session")
        filename = os.path.join("..", "Data", "iceland",
                                "roads-line.shp")
        try:
            store = session.OpenShapefile(filename)
            filename = os.path.join("..", "Data", "iceland",
                                    "roads-line.dbf")
            table = session.OpenTableFile(filename)
            self.assertEquals(session.Tables(), [table, store.Table()])
            # The store's table is reference by the store, so the only
            # unreferenced table is the roads-line table
            self.assertEquals(session.UnreferencedTables(), [table])
        finally:
            store = table = None


class UnreferencedTablesTests(unittest.TestCase):

    """Test cases for the session.UnreferencedTables() method"""

    def setUp(self):
        """Create a session with a few test tables"""
        self.session = Session("Test Session")
        memtable = MemoryTable([("type", FIELDTYPE_STRING),
                                ("value", FIELDTYPE_DOUBLE),
                                ("code", FIELDTYPE_INT)],
                               [("OTHER/UNKNOWN", -1.5, 11),
                                ("RUINS", 0.0, 1),
                                ("FARM", 3.141, 2),
                                ("BUILDING", 2.5, 3),
                                ("HUT", 1e6, 4),
                                ("LIGHTHOUSE", -0.01, 5)])
        self.memtable = self.session.AddTable(memtable)
        filename = os.path.join("..", "Data", "iceland",
                                "roads-line.dbf")
        self.roads_line = self.session.OpenTableFile(filename)
        filename = os.path.join("..", "Data", "iceland",
                                "cultural_landmark-point.dbf")
        self.landmarks = self.session.OpenTableFile(filename)

    def tearDown(self):
        """Clear the session and layers"""
        self.memtable = self.roads_line = self.landmarks = None
        self.session.Destroy()
        self.session = None

    def test_unreferenced_tables(self):
        """Test Session.UnreferencedTables()"""
        self.assertEquals(self.session.UnreferencedTables(),
                          [self.memtable, self.roads_line, self.landmarks])

    def test_unreferenced_tables_with_joins(self):
        """Test Session.UnreferencedTables() with joins"""
        joined = TransientJoinedTable(self.session.TransientDB(),
                                      self.landmarks, "CLPTLABEL",
                                      self.memtable, "type", outer_join = True)
        try:
            joined = self.session.AddTable(joined)
            # After the join, landmarks and memtable are referenced by
            # joined which in turn is referenced by nothing
            self.assertEquals(self.session.UnreferencedTables(),
                              [self.roads_line, joined])
            # Creating a DerivedShapeStore that references the joined table
            # will remove it from the list of unreferenced tables,
            store = self.session.OpenShapefile(
                os.path.join("..", "Data", "iceland",
                             "cultural_landmark-point.dbf"))
            derived = DerivedShapeStore(store, joined)
            self.session.AddShapeStore(derived)
            self.assertEquals(self.session.UnreferencedTables(),
                              [self.roads_line])
        finally:
            joined = derived = store = None

class TestSessionBase(unittest.TestCase, support.SubscriberMixin):

    """Base class for Session test cases that test the messages"""

    def setUp(self):
        """
        Clear the message list, create a session and subscribe to its messages

        Bind the session to self.session.
        """
        self.clear_messages()

        # Create a Session and subscribe to all interesting channels.
        self.session = Session("Test Session")
        for channel in (CHANGED,
                        MAPS_CHANGED,
                        FILENAME_CHANGED,
                        MAP_PROJECTION_CHANGED,
                        MAP_LAYERS_CHANGED,
                        LAYER_VISIBILITY_CHANGED,
                        LAYER_CHANGED,
                        TABLE_REMOVED):
            self.session.Subscribe(channel,
                                   self.subscribe_with_params, channel)

    def tearDown(self):
        """Destroy self.session and clear the message list"""
        self.session.Destroy()
        self.session = None
        self.clear_messages()


class TestSessionMessages(TestSessionBase):

    """Simple Session test cases that test messges"""

    def test_add_map(self):
        """Test Session.AddMap"""
        self.failIf(self.session.WasModified())
        map = Map("Some Map")
        self.session.AddMap(map)
        self.assert_(self.session.HasMaps())
        self.assert_(self.session.WasModified())
        self.assertEquals(self.session.Maps(), [map])
        self.check_messages([(MAPS_CHANGED,),
                             (self.session, CHANGED)])

    def test_set_filename(self):
        """Test Session.SetFilename"""
        self.session.SetFilename("session_set_filename_test")
        self.session.filename = "session_set_filename_test"
        self.check_messages([(FILENAME_CHANGED,),
                             (self.session, CHANGED)])

    def test_remove_table(self):
        """Test Session.RemoveTable()"""
        memtable = MemoryTable([("type", FIELDTYPE_STRING),
                                ("value", FIELDTYPE_DOUBLE),
                                ("code", FIELDTYPE_INT)],
                               [("OTHER/UNKNOWN", -1.5, 11),
                                ("RUINS", 0.0, 1),
                                ("FARM", 3.141, 2),
                                ("BUILDING", 2.5, 3),
                                ("HUT", 1e6, 4),
                                ("LIGHTHOUSE", -0.01, 5)])
        table = self.session.AddTable(memtable)
        self.assertEquals(self.session.Tables(), [table])
        self.clear_messages()
        self.session.RemoveTable(table)
        self.check_messages([(table, TABLE_REMOVED),
                             (self.session, CHANGED)])
        self.assertEquals(self.session.Tables(), [])
        self.assertRaises(ValueError, self.session.RemoveTable, table)


class TestSessionWithContent(TestSessionBase):

    """Session test cases that start with a filled session."""

    def setUp(self):
        """Extend the inherited method to add a non-empty map to self.session
        """
        TestSessionBase.setUp(self)
        open_shp = self.session.OpenShapefile
        self.arc_layer = Layer("Roads",
                               open_shp(os.path.join("..", "Data", "iceland",
                                                     "roads-line.shp")))
        self.poly_layer = Layer("Political",
                                open_shp(os.path.join("..", "Data", "iceland",
                                                      "political.shp")))
        self.map = Map("A Map")
        self.map.AddLayer(self.arc_layer)
        self.map.AddLayer(self.poly_layer)
        self.session.AddMap(self.map)
        self.session.UnsetModified()
        self.clear_messages()

    def tearDown(self):
        TestSessionBase.tearDown(self)
        self.arc_layer = self.poly_layer = None

    def test_remove_map(self):
        """Test Session.RemoveMap"""
        self.session.RemoveMap(self.map)
        self.assert_(self.session.WasModified())
        self.failIf(self.session.HasMaps())
        self.check_messages([(MAPS_CHANGED,),
                             (self.session, CHANGED)])

    def test_tree_info(self):
        """Test Session.TreeInfo"""
        self.assertEquals(self.session.TreeInfo(),
                          ('Session: Test Session',
                           ['Filename:',
                            'Unmodified',
                            self.map]))

    def test_forward_map_projection(self):
        """Test Session forwarding of Map.SetProjection messages"""
        proj = Projection(["zone=26", "proj=utm", "ellps=clrk66"])
        self.map.SetProjection(proj)
        self.check_messages([(self.map, MAP_PROJECTION_CHANGED),
                             (self.session, CHANGED)])
        self.assert_(self.session.WasModified())

    def test_forward_map_projection(self):
        """Test Session forwarding of Map.SetProjection messages"""
        proj = Projection(["zone=26", "proj=utm", "ellps=clrk66"])
        self.map.SetProjection(proj)
        self.assert_(self.session.WasModified())
        self.check_messages([(self.map, None, MAP_PROJECTION_CHANGED),
                             (self.session, CHANGED)])

    def test_forwarding_fill(self):
        """Test Session's forwarding of Layer.SetFill messages"""
        self.poly_layer.GetClassification().SetDefaultFill(Color(0.0, 0.5, 1.0))
        self.assert_(self.session.WasModified())
        self.check_messages([(self.poly_layer, LAYER_CHANGED),
                             (self.session, CHANGED)])

    def test_forwarding_stroke(self):
        """Test Session's forwarding of Layer.SetStroke messages"""
        self.poly_layer.GetClassification().\
            SetDefaultLineColor(Color(0.0, 0.5, 1.0))
        self.assert_(self.session.WasModified())
        self.check_messages([(self.poly_layer, LAYER_CHANGED),
                             (self.session, CHANGED)])

    def test_forwarding_stroke_width(self):
        """Test Session's forwarding of Layer.SetStrokeWidth messages"""
        self.poly_layer.GetClassification().SetDefaultLineWidth(3)
        self.assert_(self.session.WasModified())
        self.check_messages([(self.poly_layer, LAYER_CHANGED),
                             (self.session, CHANGED)])

    def test_forwarding_visibility(self):
        """Test Session's forwarding of Layer.SetVisible messages"""
        self.poly_layer.SetVisible(0)
        # Currently changing the visibility of a layer doesn't change
        # the modification flag.
        self.failIf(self.session.WasModified())
        self.check_messages([(self.poly_layer, LAYER_VISIBILITY_CHANGED),
                             (self.session, CHANGED)])

    def test_unset_modified_map(self):
        """Test Session.UnsetModified with map level changes"""
        self.failIf(self.session.WasModified())
        proj = Projection(["zone=26", "proj=utm", "ellps=clrk66"])
        self.map.SetProjection(proj)
        self.assert_(self.session.WasModified())
        self.session.UnsetModified()
        self.failIf(self.session.WasModified())

    def test_unset_modified_layer(self):
        """Test Session.UnsetModified with layer level changes"""
        self.failIf(self.session.WasModified())
        self.poly_layer.GetClassification().SetDefaultLineWidth(3)
        self.assert_(self.session.WasModified())
        self.session.UnsetModified()
        self.failIf(self.session.WasModified())
        self.check_messages([(self.poly_layer, LAYER_CHANGED),
                             (self.session, CHANGED),
                             (CHANGED,)])

    def test_shape_stores(self):
        """Test Session.ShapeStores()"""
        # Strictly speaking the session doesn't make guarantees about
        # the order of the ShapeStores in the list, but currently it's
        # deterministic and they're listed in the order in which they
        # were created
        self.assertEquals(self.session.ShapeStores(),
                          [self.arc_layer.ShapeStore(),
                           self.poly_layer.ShapeStore()])
        # If we remove the map from the session and clear our instance
        # variables that hold the layers and the map the list should
        # become empty again.
        self.session.RemoveMap(self.map)
        self.arc_layer = self.poly_layer = self.map = None
        self.assertEquals(self.session.ShapeStores(), [])

    def test_tables(self):
        """Test Session.Tables()"""
        # Strictly speaking the session doesn't make guarantees about
        # the order of the tables in the list, but currently it's
        # deterministic and they're listed in the order in which they
        # were opened
        self.assertEquals(self.session.Tables(),
                          [self.arc_layer.ShapeStore().Table(),
                           self.poly_layer.ShapeStore().Table()])
        # If we remove the map from the session and clear our instance
        # variables that hold the layers and the map the list should
        # become empty again.
        self.session.RemoveMap(self.map)
        self.arc_layer = self.poly_layer = self.map = None
        self.assertEquals(self.session.Tables(), [])


if __name__ == "__main__":
    unittest.main()
