# Copyright (c) 2004,2005 by Intevation GmbH
# Authors:
# Markus Rechtien <markus@intevation.de> (2004)
# Bernhard Reiter <bernhard@intevation.de> (2004,2005)
#
# This program is free software under the GPL (>=v2)
# Read the file COPYING coming with Thuban for details.

"""Test the svgexport."""

__version__ = "$Revision: 2671 $"
# $Source$
# $Id: test_svgmapwriter.py 2671 2005-10-17 20:10:18Z bernhard $


import os
import sys
import string
import StringIO
import unittest

# If run directly as a script, add Thuban's test directory to the path.
# Otherwise we assume that the importing code has already done it.
if __name__ == "__main__":
    sys.path.append(os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])),
                                 "..", "..", "..", "test"))
from mockgeo import SimpleShapeStore
import support
support.initthuban()

# Now import needed SVG stuff
from Extensions.svgexport.svgmapwriter import VirtualDC, \
     Pen, Brush, SOLID,  Point, Font, TRANSPARENT_PEN, TRANSPARENT_BRUSH, \
     SVGRenderer, SVGMapWriterError
# Color related classes from the model of thuban
from Thuban.Model.color import Color, Black, Transparent

from Thuban.Model.data import SHAPETYPE_ARC, SHAPETYPE_POLYGON
from Thuban.Model.map import Map
from Thuban.Model.layer import BaseLayer, Layer
from Thuban.Model.table import MemoryTable, \
     FIELDTYPE_DOUBLE, FIELDTYPE_INT, FIELDTYPE_STRING



# We use xmlsupport to verify the SVG output
import xmlsupport

class BaseTestWithDC(unittest.TestCase):
    """Add dc creation and self.to_destroy list to setUp() and tearDown().

    This is a baseclass for tests needing a dc.
    """
    def setUp(self):
        """Create dc for testing and set up self.to_destroy.

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

        self.file = StringIO.StringIO()
        self.dc = VirtualDC(self.file)

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


class BaseTestWithDCtools(BaseTestWithDC):
    """Add standard colors and polygon to setUp() for DC tests."""
    def setUp(self):
        """Initialize tools."""
        BaseTestWithDC.setUp(self)
        self.black = Black
        self.solid_pen = Pen(Color(1.0, 0.0, 1.0), 3, SOLID)
        self.trans_pen = TRANSPARENT_PEN
        self.solid_brush = Brush(Color(0.0, 1.0, 0.0), SOLID)
        self.trans_brush = TRANSPARENT_BRUSH
        self.polygon = [Point(5.6,5.5), Point(95.4,5.3), Point(95.2,95.1),
                Point(5.0,95.0), Point(5.0,5.0)]
        self.meta = {"Object":"test", "Label":"nothing"}
        self.font = Font("Helvetica", 12)


class TestVirtualDC(BaseTestWithDCtools):
    '''Test VirtualDC imitating a wxDC and writing SVGRenderer instead.'''

    def test_clippath(self):
        '''Define a clipping region and close it afterwards.'''
        data = '<clipPath id="testid">\n</clipPath>'
        self.dc.BeginClipPath('testid')
        self.dc.EndClipPath()
        self.assertEquals(xmlsupport.sax_eventlist(data=data),
                          xmlsupport.sax_eventlist(data=self.file.getvalue()))

    def test_polygon_closed(self):
        '''Set drawing properties and draw a polygon onto the dc.'''
        data = ('<path style="stroke-linejoin:round; stroke:#ff00ff; '
                'stroke-width:3; stroke-linecap:round; fill:#00ff00" ' 
                'meta="Object:test; Label:nothing" d="\nM 5.6 5.5 '
                'L 95.4 5.3 L 95.2 95.1 L 5.0 95.0 L 5.0 5.0 Z"/>')
        dc = self.dc
        dc.SetPen(self.solid_pen)
        dc.SetMeta(self.meta)
        dc.SetBrush(self.solid_brush)
        dc.DrawPolygonPath([self.polygon])
        self.assertEquals(xmlsupport.sax_eventlist(data=data), 
                          xmlsupport.sax_eventlist(data=self.file.getvalue()))
    
    def test_polyline(self):
        '''Set drawing properties and draw a polyline onto the dc.'''
        data = ('<path style="stroke-linejoin:round; stroke:#ff00ff; '
                'stroke-width:3; stroke-linecap:round; fill:#00ff00" ' 
                'meta="Object:test; Label:nothing" d="\nM 5.6 5.5'
                '\nL 95.4 5.3\nL 95.2 95.1\nL 5.0 95.0\nL 5.0 5.0"/>')
        dc = self.dc
        dc.SetPen(self.solid_pen)
        dc.SetMeta(self.meta)
        dc.SetBrush(self.solid_brush)
        dc.DrawPolygonPath([self.polygon], closed=False)
        self.assertEquals(xmlsupport.sax_eventlist(data=data), 
                          xmlsupport.sax_eventlist(data=self.file.getvalue()))

    def test_transparent_polyline(self):
        '''Test dc drawing a transparent polyline.'''
        data = ('<path style="stroke-linejoin:round; stroke:#ff00ff; '
                'stroke-width:3; stroke-linecap:round; fill:none" ' 
                'meta="Object:test; Label:nothing" d="\nM 5.6 5.5'
                ' L 95.4 5.3\nL 95.2 95.1\nL 5.0 95.0\nL 5.0 5.0"/>')
        dc = self.dc
        dc.SetPen(self.solid_pen)
        dc.SetMeta(self.meta)
        dc.SetBrush(self.trans_brush)
        dc.DrawPolygonPath([self.polygon], closed=False)
        self.assertEquals(xmlsupport.sax_eventlist(data=data), 
                          xmlsupport.sax_eventlist(data=self.file.getvalue()))

    def test_polygon_with_hole(self):
        '''Set drawing properties and draw a polygon onto the dc.'''

        holepolygon = [Point(11.1,11.1), Point(33.3,11.1), Point(22.2,22.2),
                Point(11.1,11.1)]
        data = ('<path style="stroke-linejoin:round; stroke:#ff00ff; '
                'stroke-width:3; stroke-linecap:round; fill:#00ff00" ' 
                'meta="Object:test; Label:nothing" d="\nM 5.6 5.5 '
                'L 95.4 5.3 L 95.2 95.1 L 5.0 95.0 L 5.0 5.0 Z'
                '\nM 11.1 11.1 L 33.3 11.1 L 22.2 22.2 L 11.1 11.1 Z"/>')

        dc = self.dc
        dc.SetPen(self.solid_pen)
        dc.SetMeta(self.meta)
        dc.SetBrush(self.solid_brush)
        dc.DrawPolygonPath([self.polygon, holepolygon])
        self.assertEquals(xmlsupport.sax_eventlist(data=data), 
                          xmlsupport.sax_eventlist(data=self.file.getvalue()))

    def test_rect(self):
        '''Set drawing properties and draw a rectangle'''
        data = ('<rect x="5.5" y="5.4" width="90.3" height="90.2" '
                'style="stroke-linejoin:round; stroke:#ff00ff; '
                'stroke-width:3; stroke-linecap:round; fill:none" '
                'meta="Object:test; Label:nothing"/>')
        dc = self.dc
        dc.SetPen(self.solid_pen)
        dc.SetMeta(self.meta)
        dc.SetBrush(self.trans_brush)
        dc.DrawRectangle(5.5, 5.4, 90.3, 90.2)
        self.assertEquals(xmlsupport.sax_eventlist(data=data), 
                          xmlsupport.sax_eventlist(data=self.file.getvalue()))

    def test_circle(self):
        '''Set drawing properties and draw a circle'''
        data = ('<circle cx="5.5" cy="5.3" r="90.1"  style="'
                'fill:#00ff00" meta="Object:test; Label:nothing"/>')
        dc = self.dc
        dc.SetPen(self.trans_pen)
        dc.SetMeta(self.meta)
        dc.SetBrush(self.solid_brush)
        dc.DrawCircle(5.5, 5.3, 90.1)
        self.assertEquals(xmlsupport.sax_eventlist(data=data),
                          xmlsupport.sax_eventlist(data=self.file.getvalue()))

    def test_ellipse(self):
        '''Set drawing properties and draw a circle'''
        data = ('<ellipse cx="5.5" cy="5.3" rx="90.1" ry="100.321"  style="'
                'fill:#00ff00" meta="Object:test; Label:nothing"/>')
        dc = self.dc
        dc.SetPen(self.trans_pen)
        dc.SetMeta(self.meta)
        dc.SetBrush(self.solid_brush)
        dc.DrawEllipse(5.5, 5.3, 90.1, 100.321)
        self.assertEquals(xmlsupport.sax_eventlist(data=data), 
                          xmlsupport.sax_eventlist(data=self.file.getvalue()))

    def test_text(self):
        '''Set drawing properties and draw a circle'''
        data = ('<text x="123.321" y="1515.5151" style="font-size:12; '
                'font-family:Helvetica; fill:#000000" >Some text.</text>')
        dc = self.dc
        dc.SetTextForeground(self.black)
        dc.SetFont(self.font)
        dc.DrawText('Some text.', 123.321, 1515.5151)
        self.assertEquals(xmlsupport.sax_eventlist(data=data),
                          xmlsupport.sax_eventlist(data=self.file.getvalue()))

    def test_document(self):
        '''Set up a document with a dimension and 
        latin encoding'''
        data = ('<?xml version="1.0" encoding="ISO-8859-1" standalone'
                '="yes"?>\n<svg>\n</svg>')
        self.dc.BeginExport()
        self.dc.EndExport()
        self.assertEquals(xmlsupport.sax_eventlist(data=data), 
                          xmlsupport.sax_eventlist(data=self.file.getvalue()))

class TestDrawSplines(BaseTestWithDCtools):
    """Testing DrawSpline variations.
    See comments in DrawSpline().
    """

    def setUp(self):
        BaseTestWithDCtools.setUp(self)
        self.dataframe = ('<path style="stroke-linejoin:round; stroke:#ff00ff; '
                'stroke-width:3; stroke-linecap:round; fill:none" ' 
                'meta="Object:test; Label:nothing" d="%s"/>')

        self.file = StringIO.StringIO()
        self.dc = VirtualDC(self.file)
        self.dc.SetPen(self.solid_pen)
        self.dc.SetMeta(self.meta)
        self.dc.SetBrush(self.trans_brush)

    def test_drawspline3(self):
        '''Test DrawSpline with three points in a row.'''

        d=('M 10 10 L 12.55 12.55 Q 15.1 15.1 17.65 17.65\nL 20.2 20.2')
        data=(self.dataframe % d)

        self.dc.DrawSpline([ Point(10, 10),
                        Point(15.1, 15.1),
                        Point(20.2, 20.2) ])
        #print file.getvalue()
        self.assertEquals(xmlsupport.sax_eventlist(data = data),
                          xmlsupport.sax_eventlist(data = self.file.getvalue()))

    def test_drawspline4(self):
        '''Test DrawSpline with four points in a row.'''

        d='M 0 0 L 0 1 Q 0 2 0 3\nQ 0 4 0 5\nL 0 6'
        data=(self.dataframe % d)

        self.dc.DrawSpline([ Point(0, 0),
                        Point(0, 2),
                        Point(0, 4),
                        Point(0, 6) ])
        #print file.getvalue()
        self.assertEquals(xmlsupport.sax_eventlist(data = data),
                          xmlsupport.sax_eventlist(data = self.file.getvalue()))



class TestSVGRendererIDHandling(BaseTestWithDC):

    def test_make_id_nonintegersetid(self):
        """Test that exception is raised when SetID was called with chars."""
        dc=self.dc
        dc.SetBaseID("a")
        dc.SetID("abc")
        self.assertRaises(SVGMapWriterError, dc.make_id)

    def test_make_ide_nosetbaseid(self):
        """Test as no setbaseid results in valid XML id starting with '_'."""
        dc=self.dc
        dc.SetBaseID("")
        dc.SetID(123)
        id=dc.make_id() # returns 'id="xxxxxx"'
        self.assert_(id[4]=='_' or (id[4] in string.ascii_letters))

    def test_xml_id_constraints(self):
        """Test the checks for the XML id contraints by trying bad id parts."""

        dc=self.dc
        dc.SetID(42)

        # an xml Name shall better not start with "xml" (case insensitive)
        dc.SetBaseID("xml") 
        id=dc.make_id() # returns 'id="xxxxxx"'
        self.assert_(id[4:7].lower() != "xml")

        dc.SetBaseID("XmL") 
        id=dc.make_id()
        self.assert_(id[4:7].lower() != "xml")

        # recommended to better not use ":"
        dc.SetBaseID("abc:def")
        id=dc.make_id()
        self.assert_(":" not in id )

        # an XML name shall only have:
        #    Letter | Digit | '.' | '-' | '_' | CombiningChar | Extender
        dc.SetBaseID("abc def")
        id=dc.make_id()
        self.assert_(" " not in id )

        dc.SetBaseID("ab!cd")
        id=dc.make_id()
        self.assert_("!" not in id )

        dc.SetBaseID("a.b-c_d")
        id=dc.make_id()
        self.assert_(id[4:11]=="a.b-c_d")


    def test_make_id(self):
        """Check "layer" and "layer1" do not clash; given integer ShapeIDs.
        """
        dc=self.dc
        dc.SetBaseID("layer")
        dc.SetID(10)
        id1=dc.make_id()
        dc.SetBaseID("layer1")
        dc.SetID(0)
        id2=dc.make_id()

        self.assertNotEqual(id1,id2)


    def test_check_for_layer_name_clash(self):
        '''Create 2 layers with same name, try to write and check exception.
        '''

        # BaseLayer is not enough, because BaseRenderer.render_map()
        # checks on isinstance of Layer not BaseLayer.
        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("TestLayerNameClash")
        self.to_destroy.append(map)
        layer=Layer("Same Name", store)
        map.AddLayer(layer)
        # reusing the same store with the same table and shapes should be okay
        layer=Layer("Same Name", store)
        map.AddLayer(layer)

        renderer = SVGRenderer(self.dc, map,
                    scale=1.0, offset=(0,0), region=(0,0,10,10))

        self.assertRaises(SVGMapWriterError,
                renderer.RenderMap, None, None)

class TestSVGRenderer(BaseTestWithDC):
    """Test methods of SVGRenderer."""

    def test_label_font(self):
        """Test that label_font used the self.factor."""
        mockmap = Map("mock title")
        self.to_destroy.append(mockmap)
        renderer = SVGRenderer(self.dc, mockmap,
                    scale=1.0, offset=(0,0), region=(0,0,30000,30000))
        #print renderer.factor
        self.assertEqual( renderer.label_font().GetPointSize(),
                          12*renderer.factor
                        )

class Testobjectexport(BaseTestWithDC):

    def test_transparent_polygon(self):
        """Create layer with non-filled polygon and test svg rendering."""

        data = ('<clipPath id="mapclip">\n'
                '    <rect x="0" y="0" width="10" height="10"   />\n'
                '</clipPath>\n'
                '<g meta="Object:map"  clip-path="url(#mapclip)" '
                'clip-rule="evenodd" transform=" scale(100.0)">\n'
                '    <g meta="Layer:P-Layer" >\n'
                '        <path style="stroke-linejoin:round; stroke:#000000; '
                'stroke-width:0.01; stroke-linecap:round; fill:none" '
                'id="P-Layer_0"'
                '  d="\nM 0.0 0.0\nL 10.0 -10.0\nL 0.0 -10.0\nL 0.0 0.0 Z"/>\n'
                '    </g>\n</g>\n')
        table = MemoryTable([("type", FIELDTYPE_STRING),
                             ("value", FIELDTYPE_DOUBLE),
                             ("code", FIELDTYPE_INT)],
                            [("UNKNOWN", 0.0, 0)])
        shapes = [[[(0, 0), (10, 10), (0, 10), (0, 0) ]]]
        store = SimpleShapeStore(SHAPETYPE_POLYGON, shapes, table)

        map = Map("testpolygonexport")
        self.to_destroy.append(map)
        layer=Layer("P-Layer", store, fill=Transparent)
        map.AddLayer(layer)

        renderer = SVGRenderer(self.dc, map,
                    scale=1.0, offset=(0,0), region=(0,0,10,10))
        renderer.RenderMap(None, None)
        #print (data)
        #print (self.dc.file.getvalue())
        self.assertEquals(data, self.dc.file.getvalue())

    def test_export_polygon_with_hole(self):
        """ Create layer with polygon ans hole and test svg rendering."""

        data = ('<clipPath id="mapclip">\n'
                '    <rect x="0" y="0" width="10" height="10"   />\n'
                '</clipPath>\n'
                '<g meta="Object:map"  clip-path="url(#mapclip)" '
                'clip-rule="evenodd" transform=" scale(100.0)">\n'
                '    <g meta="Layer:P-Layer" >\n'
                '        <path style="stroke-linejoin:round; stroke:#000000; '
                'stroke-width:0.01; stroke-linecap:round; fill:none" '
                'id="P-Layer_0"'
                '  d="\nM 0.0 0.0\nL 0.0 -10.0\nL 10.0 -10.0\nL 0.0 0.0 Z'
                '\nM 5.0 -6.0\nL 5.0 -7.0\nL 4.0 -7.0\nL 4.0 -6.0'
                '\nL 5.0 -6.0 Z"/>\n'
                '    </g>\n</g>\n')
        table = MemoryTable([("type", FIELDTYPE_STRING),
                             ("value", FIELDTYPE_DOUBLE),
                             ("code", FIELDTYPE_INT)],
                            [("UNKNOWN", 0.0, 0)])
        shapes = [[[(0, 0), (0, 10), (10, 10), (0, 0) ],
                   [(5, 6), (5,  7), ( 4,  7), (4, 6), (5, 6)]
                 ]]
        store = SimpleShapeStore(SHAPETYPE_POLYGON, shapes, table)

        map = Map("testpolygonexport")
        self.to_destroy.append(map)
        layer=Layer("P-Layer", store, fill=Transparent)
        map.AddLayer(layer)

        renderer = SVGRenderer(self.dc, map,
                    scale=1.0, offset=(0,0), region=(0,0,10,10))
        renderer.RenderMap(None, None)
        #print (data)
        #print (self.dc.file.getvalue())
        self.assertEquals(data, self.dc.file.getvalue())

    def test_export_arc_no_fill(self):
        """Create layer with a linestring and test svg rendering.

	Even when the layer or the classifications have a fill color,
	the resulting path shall not be filled for an ARC (linestring) layer.
	"""

        data = ('<clipPath id="mapclip">\n'
                '    <rect x="0" y="0" width="10" height="10"   />\n'
                '</clipPath>\n'
                '<g meta="Object:map"  clip-path="url(#mapclip)" '
                'clip-rule="evenodd" transform=" scale(100.0)">\n'
                '    <g meta="Layer:A-Layer" >\n'
                '        <path style="stroke-linejoin:round; stroke:#000000; '
                'stroke-width:0.01; stroke-linecap:round; fill:none" '
                'id="A-Layer_0"'
                '  d="\nM 0.0 0.0\nL 2.0 -8.0\nL 10.0 -10.0"/>\n'
                '    </g>\n</g>\n')
        table = MemoryTable([("type", FIELDTYPE_STRING),
                             ("value", FIELDTYPE_DOUBLE),
                             ("code", FIELDTYPE_INT)],
                            [("UNKNOWN", 0.0, 0)])
        shapes = [[[(0, 0), (2, 8), (10, 10) ]]]
        store = SimpleShapeStore(SHAPETYPE_ARC, shapes, table)

        map = Map("testarcexport")
        self.to_destroy.append(map)
        layer=Layer("A-Layer", store, fill=Black)
        map.AddLayer(layer)

        renderer = SVGRenderer(self.dc, map,
                    scale=1.0, offset=(0,0), region=(0,0,10,10))
        renderer.RenderMap(None, None)
        #print (data)
        #print (self.dc.file.getvalue())
        self.assertEquals(data, self.dc.file.getvalue())


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