# Copyright (C) 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 the software for details.

"""Test Thuban.Model.baserenderer"""

__version__ = "$Revision: 1.7 $"
# $Source: /thubanrepository/thuban/test/test_baserenderer.py,v $
# $Id: test_baserenderer.py,v 1.7 2003/11/10 16:57:43 bh Exp $

import os
import binascii
import unittest

from mockgeo import SimpleShapeStore
import support
support.initthuban()

from Thuban.Model.color import Transparent, Color
from Thuban.Model.data import SHAPETYPE_ARC, SHAPETYPE_POLYGON, SHAPETYPE_POINT
from Thuban.Model.map import Map
from Thuban.Model.layer import BaseLayer, Layer, RasterLayer
from Thuban.Model.table import MemoryTable, \
     FIELDTYPE_DOUBLE, FIELDTYPE_INT, FIELDTYPE_STRING
from Thuban.Model.classification import ClassGroupSingleton
import Thuban.Model.resource


from Thuban.UI.baserenderer import BaseRenderer, \
     add_renderer_extension, init_renderer_extensions


class MockDC:

    def __init__(self, size = None):
        self.calls = []
        self.size = size

    def GetSizeTuple(self):
        return self.size

    def __getattr__(self, attr):
        def method(*args):
            self.calls.append((attr,) + args)
        return method

class P:

    """A simple point"""

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

    def __ne__(self, other):
        return self.x != other.x and self.y != other.y

    def __repr__(self):
        return "P(%r, %r)" % (self.x, self.y)


class SimpleRenderer(BaseRenderer):

    TRANSPARENT_PEN = ("pen", Transparent)
    TRANSPARENT_BRUSH = ("brush", Transparent)

    def make_point(self, x, y):
        return P(x, y)

    def tools_for_property(self, prop):
        fill = prop.GetFill()
        brush = ("brush", fill)

        stroke = prop.GetLineColor()
        stroke_width = prop.GetLineWidth()
        if stroke is Transparent:
            pen = ("pen", Transparent)
        else:
            pen = ("pen", stroke, stroke_width)

        return pen, brush

    def label_font(self):
        return "label font"

    def draw_raster_data(self, data, format='BMP'):
        self.raster_data = data
        self.raster_format = format


class MockProjection:

    """Objects that look like projections but simply apply non-uniform scalings
    """

    def __init__(self, xscale, yscale):
        self.xscale = float(xscale)
        self.yscale = float(yscale)

    def Forward(self, x, y):
        return (x * self.xscale, y * self.yscale)

    def Inverse(self, x, y):
        return (x / self.xscale, y / self.yscale)


class TestBaseRenderer(unittest.TestCase):

    def setUp(self):
        """Set self.to_destroy to an empty list

        Test should put all objects whose Destroy should be called atunittest.main
        the end into this list so that they're destroyed in tearDown
        """
        self.to_destroy = []

    def tearDown(self):
        for obj in self.to_destroy:
            obj.Destroy()

    def test_arc_no_projection(self):
        """Test BaseRenderer with arc layer and no projections"""
        table = MemoryTable([("type", FIELDTYPE_STRING),
                             ("value", FIELDTYPE_DOUBLE),
                             ("code", FIELDTYPE_INT)],
                            [("UNKNOWN", 0.0, 0)])
        shapes = [[[(0, 0), (10, 10)]]]
        store = SimpleShapeStore(SHAPETYPE_ARC, shapes, table)

        map = Map("TestBaseRenderer")
        self.to_destroy.append(map)
        layer = Layer("arc layer", store)
        map.AddLayer(layer)

        dc = MockDC()
        renderer = SimpleRenderer(dc, map, 2, (10, 10))

        renderer.render_map()

        self.assertEquals(dc.calls,
                          [('BeginDrawing',),
                           ('SetBrush', ('brush', Transparent)),
                           ('SetPen', ('pen', Color(0, 0, 0), 1)),
                           ('DrawLines', [P(10, 10), P(30, -10)]),
                           ('SetFont', "label font"),
                           ('EndDrawing',)])

    def test_polygon_no_projection(self):
        """Test BaseRenderer with polygon layer and no projections"""
        table = MemoryTable([("type", FIELDTYPE_STRING),
                             ("value", FIELDTYPE_DOUBLE),
                             ("code", FIELDTYPE_INT)],
                            [("UNKNOWN", 0.0, 0)])
        shapes = [[[(0, 0), (10, 10), (10, 0), (0, 0)]]]
        store = SimpleShapeStore(SHAPETYPE_POLYGON, shapes, table)

        map = Map("TestBaseRenderer")
        layer = Layer("polygon layer", store)
        prop = layer.GetClassification().GetDefaultGroup().GetProperties()
        prop.SetFill(Color(1, 1, 1))

        map.AddLayer(layer)
        self.to_destroy.append(map)

        dc = MockDC()
        renderer = SimpleRenderer(dc, map, 2, (10, 10))

        renderer.render_map()

        self.assertEquals(dc.calls,
                          [('BeginDrawing',),
                           ('SetBrush', ('brush', Color(1, 1, 1))),
                           ('SetPen', ('pen', Transparent)),
                           ('DrawPolygon', [P(10, 10), P(30, -10), P(30, 10),
                                            P(10, 10)]),
                           ('SetBrush', ('brush', Transparent)),
                           ('SetPen', ('pen', Color(0, 0, 0), 1)),
                           ('DrawLines', [P(10, 10), P(30, -10), P(30, 10),
                                          P(10, 10)]),
                           ('SetFont', "label font"),
                           ('EndDrawing',)])

    def test_complex_polygon(self):
        """Test BaseRenderer with complex polygon and no projections"""
        # A square inside a sqare. This has to be drawn with at least a
        # draw polygon call and two draw lines calls.
        shapes = [[[(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)],
                   [(2, 2), (8, 2), (8, 8), (2, 8), (2, 2)]]]

        table = MemoryTable([("type", FIELDTYPE_STRING),
                             ("value", FIELDTYPE_DOUBLE),
                             ("code", FIELDTYPE_INT)],
                            [("UNKNOWN", 0.0, 0)])
        store = SimpleShapeStore(SHAPETYPE_POLYGON, shapes, table)

        map = Map("TestBaseRenderer")
        layer = Layer("polygon layer", store)
        prop = layer.GetClassification().GetDefaultGroup().GetProperties()
        prop.SetFill(Color(1, 1, 1))

        map.AddLayer(layer)
        self.to_destroy.append(map)

        dc = MockDC()
        renderer = SimpleRenderer(dc, map, 2, (10, 10))

        renderer.render_map()

        self.assertEquals(dc.calls,
                          [('BeginDrawing',),
                           ('SetBrush', ('brush', Color(1, 1, 1))),
                           ('SetPen', ('pen', Transparent)),
                           ('DrawPolygon',
                            [P(10, 10), P(10, -10), P(30, -10), P(30, 10),
                             P(10, 10),
                             P(14, 6), P(26, 6), P(26, -6), P(14, -6),
                             P(14, 6),
                             P(10, 10)]),
                           ('SetBrush', ('brush', Transparent)),
                           ('SetPen', ('pen', Color(0, 0, 0), 1)),
                           ('DrawLines', [P(10, 10), P(10, -10), P(30, -10),
                                          P(30, 10), P(10, 10)]),
                           ('DrawLines', [P(14, 6), P(26, 6), P(26, -6),
                                          P(14, -6), P(14, 6)]),
                           ('SetFont', "label font"),
                           ('EndDrawing',)])

    def test_point_no_projection(self):
        """Test BaseRenderer with point layer and no projections"""
        table = MemoryTable([("type", FIELDTYPE_STRING),
                             ("value", FIELDTYPE_DOUBLE),
                             ("code", FIELDTYPE_INT)],
                            [("UNKNOWN", 0.0, 0),
                             ("UNKNOWN", 0.0, 1)])
        shapes = [[[(0, 0)]], [[(10, 10)]]]
        store = SimpleShapeStore(SHAPETYPE_POINT, shapes, table)

        map = Map("TestBaseRenderer")
        layer = Layer("point layer", store)
        map.AddLayer(layer)
        self.to_destroy.append(map)

        dc = MockDC()
        renderer = SimpleRenderer(dc, map, 2, (10, 10))

        renderer.render_map()

        self.assertEquals(dc.calls,
                          [('BeginDrawing',),
                           ('SetBrush', ('brush', Transparent)),
                           ('SetPen', ('pen', Color(0, 0, 0), 1)),
                           ('DrawEllipse', 5, 5, 10, 10),
                           ('SetBrush', ('brush', Transparent)),
                           ('SetPen', ('pen', Color(0, 0, 0), 1)),
                           ('DrawEllipse', 25, -15, 10, 10),
                           ('SetFont', "label font"),
                           ('EndDrawing',)])

    def test_raster_no_projection(self):
        """Test BaseRenderer with raster layer and no projections

        This test is very simple minded and perhaps can easily fail due
        to round-off errors. It simply compares the complete BMP file
        returned by gdalwarp.ProjectRasterFile to a BMP file data.
        """
        if not Thuban.Model.resource.has_gdal_support():
            raise support.SkipTest("No gdal support")

        map = Map("TestBaseRenderer")

        layer = RasterLayer("raster layer",
                            os.path.join("..", "Data", "iceland",
                                         "island.tif"))
        map.AddLayer(layer)
        self.to_destroy.append(map)

        dc = MockDC(size = (20, 20))
        renderer = SimpleRenderer(dc, map, 34, (800, 2250))

        renderer.render_map()

        # The following commented out code block can be used to generate
        # the base64 coded reference image data
        #hexed = binascii.b2a_base64(renderer.raster_data)
        #while hexed:
        #    print repr(hexed[:65])
        #    hexed = hexed[65:]

        # The reference data as a base64 coded BMP image
        raw_data = binascii.a2b_base64(
            'Qk3GBQAAAAAAADYEAAAoAAAAFAAAABQAAAABAAgAAAAAAJABAAAAAAAAAAAAAAABA'
            'AAAAAAAApYOAALGAgAGfjoAHmZyACZ2egAujo4AArICAE66GgACngIA5mZSAJqONg'
            'ACzgIAAoIyABZqZgAO4uYAAtICAAKqAgAScloAAtYCADKepgAS2t4AAooiAALaAgA'
            'CtgIAHsbOAAp2TgACogIAFtbaACqOigAidnoAAuICADKaogACfjoAAr4CAAKSFgAm'
            'fnoAAo4eABrS1gAibnoAHsbKAAp6SgACmg4AGs7SACLCxgAqioIAAoYqAAZ6RgACm'
            'goAKrK6AALmAgAC3gIAApIaABZqagACngYAAo4iAAKmAgAivsYAJoJ6AALCAgACyg'
            'IAAq4CAAKWEgAOclYALpqeAAK6AgACgjYAEm5eAAKKKgAGekIAHmp6ABpmcgAChi4'
            'ALpaaACJyegAClhYAEnJeAAZ+QgAqhoIADnZSAB5mdgACiiYAJnp6ACqGegAqrrYA'
            'GmpuAB5megACkh4ALqqyAA52VgAulpYAAoI6AAZ+PgASbmIALpKWAA7m5gAWbmYAG'
            'mpyAC6SjgAqioYADnZOAA7q6gAianoALqauABpqagAqgnoAEnJWAAp6RgAWbmIACu'
            '7uACqGfgAqiooAMqauAAby8gAmusIAMp6qAC6WngAyoqoABvb2AC6SkgAS3uIAJra'
            '+AB7KzgAynqIALq62ADKirgAC+voAGsrSABLi4gAG8vYADubqAC6qtgAuprIABvb6'
            'ACLCygAW2t4AKra+AAru8gAS4uYACuruAAry8gAG+voAAEBAAFhkZABAQEAABp6fA'
            'EBACAAAgPwAAPu/AJE9CgBACAAALj1BAEAICAAGPAAAQAgAAAY8+gBACL8AJTxXAA'
            'gIQAAAPEAAAAgIAAY8QABACAgAAGQAAABAAAAALpoAAEBAAAAALgAAAEAABkAGAEA'
            'IQAD2AEAAvwAIAJAu2ABAQEAALmQ5AEBAQAAAnp8AAEAIAAD4+gAAv78An5rDAEBA'
            'QAAuhAcAQEBAAAb8AABAvwAAAGQKAABAAABpLksAQEAIAC4ACwBAAAAAAPkGAAC/Q'
            'AD2APoAvwC/AJ0umgBAQEAAAGRkAABAQAAGnp8AQEAIAPcA/AC/AL8A7D0GAEAIQA'
            'D4hAAAv0AAAAD8QAAAvwgA92T6AL9AvwDsLlcAQEBAAC4AQABAAAgA+EAAAL8IAAA'
            '8AAAACAAAAJ8uVgBAQEAAAGQ5AABAQAAAnpcAAEBAAPYAQAC/AAgAnQsGAEAAQAAA'
            'hG0AAEBAAB38PQAIv0AA9mT6AL9AvwAJLmwAZUBAAB0AQAAIAAgAHUAAAAgIAAAAA'
            'AAAAAAAAACzbAAAQEAA//k5AH+/QAAGb5cAQEBAAACy5AAAQAgAAIpAAABACAAAAA'
            'AAAAAAAAkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQk'
            'JCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJ'
            'CQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJC'
            'QkJCQkJCQkJCQkJCQkJCQkJCQkyEi8aQCEJCQkJCQkJCQkJCQkJCTI7OwgILzsyCg'
            'kJCQkJCQkJCQkJCzcJFggvADwGEDc3EhYAMgkJCQkJEjcVJDohGj0LGgYAPT0hCT0'
            'LDyI3CQoBLwAaFgkyEC9AJAE8OgsIMjoABi8kCx4JCQkJCQkeGko8KTcLCxIyNwkJ'
            'CQkWEjwWNUAQPCEAMwgJCQkJCQkSQBcvEkAPAQkyN0AMCQkJCQhBFyEvNy89JCIkM'
            'yItGQwJCQo9RxozIgkyCQoPFxAtDBkgIgkJCQkJCQkJMRoQECJQNi9EIAAgCQkJCQ'
            'kSUA88UAYeBjELICA8HiI=\n')
        self.assertEquals(renderer.raster_data, raw_data)
        self.assertEquals(renderer.raster_format, "BMP")

        self.assertEquals(dc.calls,
                          [('BeginDrawing',),
                           ('SetFont', "label font"),
                           ('EndDrawing',)])

    def test_point_map_projection(self):
        """Test BaseRenderer with point layer and map projection"""
        table = MemoryTable([("type", FIELDTYPE_STRING),
                             ("value", FIELDTYPE_DOUBLE),
                             ("code", FIELDTYPE_INT)],
                            [("UNKNOWN", 0.0, 0)])
        shapes = [[[(10, 10)]]]
        store = SimpleShapeStore(SHAPETYPE_POINT, shapes, table)

        map = Map("TestBaseRenderer")
        map.SetProjection(MockProjection(-3, 3))
        layer = Layer("point layer", store)
        map.AddLayer(layer)
        self.to_destroy.append(map)

        dc = MockDC()
        renderer = SimpleRenderer(dc, map, 2, (10, 10))

        renderer.render_map()

        self.assertEquals(dc.calls,
                          [('BeginDrawing',),
                           ('SetBrush', ('brush', Transparent)),
                           ('SetPen', ('pen', Color(0, 0, 0), 1)),
                           ('DrawEllipse', -55, -55, 10, 10),
                           ('SetFont', "label font"),
                           ('EndDrawing',)])

    def test_point_layer_projection(self):
        """Test BaseRenderer with point layer and layer projection"""
        table = MemoryTable([("type", FIELDTYPE_STRING),
                             ("value", FIELDTYPE_DOUBLE),
                             ("code", FIELDTYPE_INT)],
                            [("UNKNOWN", 0.0, 0)])
        shapes = [[[(9, 9)]]]
        store = SimpleShapeStore(SHAPETYPE_POINT, shapes, table)

        map = Map("TestBaseRenderer")
        layer = Layer("point layer", store)
        layer.SetProjection(MockProjection(3, -3))
        map.AddLayer(layer)
        self.to_destroy.append(map)

        dc = MockDC()
        renderer = SimpleRenderer(dc, map, 2, (10, 10))

        renderer.render_map()

        self.assertEquals(dc.calls,
                          [('BeginDrawing',),
                           ('SetBrush', ('brush', Transparent)),
                           ('SetPen', ('pen', Color(0, 0, 0), 1)),
                           ('DrawEllipse', 11, 11, 10, 10),
                           ('SetFont', "label font"),
                           ('EndDrawing',)])

    def test_point_layer_and_map_projection(self):
        """Test BaseRenderer with point layer and layer and map projection"""
        table = MemoryTable([("type", FIELDTYPE_STRING),
                             ("value", FIELDTYPE_DOUBLE),
                             ("code", FIELDTYPE_INT)],
                            [("UNKNOWN", 0.0, 0)])
        shapes = [[[(9, 9)]]]
        store = SimpleShapeStore(SHAPETYPE_POINT, shapes, table)

        map = Map("TestBaseRenderer")
        map.SetProjection(MockProjection(-3, 3))
        layer = Layer("point layer", store)
        layer.SetProjection(MockProjection(3, -3))
        map.AddLayer(layer)
        self.to_destroy.append(map)

        dc = MockDC()
        renderer = SimpleRenderer(dc, map, 2, (10, 10))

        renderer.render_map()

        self.assertEquals(dc.calls,
                          [('BeginDrawing',),
                           ('SetBrush', ('brush', Transparent)),
                           ('SetPen', ('pen', Color(0, 0, 0), 1)),
                           ('DrawEllipse', -13, 23, 10, 10),
                           ('SetFont', "label font"),
                           ('EndDrawing',)])


    def test_point_with_classification(self):
        """Test BaseRenderer with point layer and classification"""
        table = MemoryTable([("type", FIELDTYPE_STRING),
                             ("value", FIELDTYPE_DOUBLE),
                             ("code", FIELDTYPE_INT)],
                            [("UNKNOWN", 0.0, 0),
                             ("UNKNOWN", 0.0, 1)])
        shapes = [[[(0, 0)]], [[(10, 10)]]]
        store = SimpleShapeStore(SHAPETYPE_POINT, shapes, table)

        map = Map("TestBaseRenderer")
        layer = Layer("point layer", store)
        group = ClassGroupSingleton(1)
        group.GetProperties().SetFill(Color(0, 0, 1))
        layer.GetClassification().AppendGroup(group)
        layer.SetClassificationColumn("code")

        map.AddLayer(layer)
        self.to_destroy.append(map)

        dc = MockDC()
        renderer = SimpleRenderer(dc, map, 2, (10, 10))

        renderer.render_map()

        self.assertEquals(dc.calls,
                          [('BeginDrawing',),
                           ('SetBrush', ('brush', Transparent)),
                           ('SetPen', ('pen', Color(0, 0, 0), 1)),
                           ('DrawEllipse', 5, 5, 10, 10),
                           ('SetBrush', ('brush', Color(0, 0, 1))),
                           ('SetPen', ('pen', Color(0, 0, 0), 1)),
                           ('DrawEllipse', 25, -15, 10, 10),
                           ('SetFont', "label font"),
                           ('EndDrawing',)])


    def test_renderer_extension(self):
        """Test renderer with a renderer extension"""
        class MyLayer(BaseLayer):
            pass

        calls = []
        def my_renderer(renderer, layer):
            calls.append((renderer, layer))
            return ()

        add_renderer_extension(MyLayer, my_renderer)

        try:
            map = Map("test_renderer_extension")
            layer = MyLayer("my layer")
            map.AddLayer(layer)
            self.to_destroy.append(map)

            dc = MockDC()
            renderer = SimpleRenderer(dc, map, 2, (10, 10))
            renderer.render_map()
        finally:
            init_renderer_extensions()

        self.assertEquals(calls, [(renderer, layer)])


if __name__ == "__main__":
    support.run_tests()
