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

from __future__ import generators

__version__ = "$Revision: 1.49 $"
# $Source: /thubanrepository/thuban/Thuban/UI/renderer.py,v $
# $Id: renderer.py,v 1.49 2003/11/11 18:16:42 bh Exp $

import cStringIO

from Thuban import _

from wxPython.wx import wxPoint, wxRect, wxPen, wxBrush, wxFont, \
    wxTRANSPARENT_PEN, wxTRANSPARENT_BRUSH, \
    wxBLACK_PEN, wxBLACK, wxSOLID, wxCROSS_HATCH, wxSWISS, wxNORMAL, \
    wxBitmapFromImage, wxImageFromStream, wxBITMAP_TYPE_BMP, wxBITMAP_TYPE_JPEG

from wxproj import draw_polygon_shape, draw_polygon_init

from Thuban.UI.common import Color2wxColour
from Thuban.UI.classifier import ClassDataPreviewer
from Thuban.UI.scalebar import ScaleBar

from Thuban.Model.data import SHAPETYPE_POLYGON, SHAPETYPE_ARC, \
     SHAPETYPE_POINT, RAW_SHAPEFILE

from Thuban.Model.color import Transparent
import Thuban.Model.resource

from baserenderer import BaseRenderer


# Map the strings used for the format parameter of the draw_raster_data
# method to the appropriate wxWindows constants
raster_format_map = {
    "BMP": wxBITMAP_TYPE_BMP,
    "JPEG": wxBITMAP_TYPE_JPEG,
    }

class MapRenderer(BaseRenderer):

    """Class to render a map onto a wxDC"""

    TRANSPARENT_PEN = wxTRANSPARENT_PEN
    TRANSPARENT_BRUSH = wxTRANSPARENT_BRUSH

    def make_point(self, x, y):
        return wxPoint(int(round(x)), int(round(y)))

    def tools_for_property(self, prop):
        fill = prop.GetFill()
        if fill is Transparent:
            brush = self.TRANSPARENT_BRUSH
        else:
            brush = wxBrush(Color2wxColour(fill), wxSOLID)

        stroke = prop.GetLineColor()
        if stroke is Transparent:
            pen = self.TRANSPARENT_PEN
        else:
            pen = wxPen(Color2wxColour(stroke), prop.GetLineWidth(), wxSOLID)
        return pen, brush

    def low_level_renderer(self, layer):
        """Override inherited method to provide more efficient renderers

        If the underlying data format is not a shapefile or the layer
        contains points shapes, simply use what the inherited method
        returns.

        Otherwise, i.e. for arc and polygon use the more efficient
        wxproj.draw_polygon_shape and its corresponding parameter
        created with wxproj.draw_polygon_init.
        """
        if (layer.ShapeStore().RawShapeFormat() == RAW_SHAPEFILE
            and layer.ShapeType() in (SHAPETYPE_ARC, SHAPETYPE_POLYGON)):
            offx, offy = self.offset
            return (True, draw_polygon_shape,
                    draw_polygon_init(layer.ShapeStore().Shapefile(),
                                      self.dc, self.map.projection,
                                      layer.projection,
                                      self.scale, -self.scale, offx, offy))
        else:
            return BaseRenderer.low_level_renderer(self, layer)

    def label_font(self):
        return wxFont(int(round(self.resolution * 10)), wxSWISS, wxNORMAL,
                      wxNORMAL)

    def draw_raster_data(self, data, format = 'BMP'):
        stream = cStringIO.StringIO(data)
        image = wxImageFromStream(stream, raster_format_map[format])
        bitmap = wxBitmapFromImage(image)
        self.dc.DrawBitmap(bitmap, 0, 0)


class ScreenRenderer(MapRenderer):

    # On the screen we want to see only visible layers by default
    honor_visibility = 1

    def RenderMap(self, selected_layer, selected_shapes):
        """Render the map.

        Only the given region (a tuple in window coordinates as returned
        by a wxrect's asTuple method) needs to be redrawn. Highlight the
        shapes given by the ids in selected_shapes in the
        selected_layer.
        """
        self.selected_layer = selected_layer
        self.selected_shapes = selected_shapes
        self.render_map()

    def RenderMapIncrementally(self):
        """Render the map.

        Only the given region (a tuple in window coordinates as returned
        by a wxrect's asTuple method) needs to be redrawn. Highlight the
        shapes given by the ids in selected_shapes in the
        selected_layer.
        """
        return self.render_map_incrementally()

    def draw_selection_incrementally(self, layer, selected_shapes):
        pen = wxPen(wxBLACK, 3, wxSOLID)
        brush = wxBrush(wxBLACK, wxCROSS_HATCH)

        shapetype = layer.ShapeType()
        useraw, func, param = self.low_level_renderer(layer)
        args = (pen, brush)
        count = 0
        for index in selected_shapes:
            count += 1
            shape = layer.Shape(index)
            if useraw:
                data = shape.RawData()
            else:
                data = shape.Points()
            func(param, data, *args)
            if count % 500 == 0:
                yield True

    def layer_shapes(self, layer):
        """Return the shapeids covered by the region that has to be redrawn

        Call the layer's ShapesInRegion method to determine the ids so
        that it can use the quadtree.
        """
        # FIXME: the quad-tree should be built from the projected
        # coordinates not the lat-long ones because it's not trivial to
        # determine an appropriate rectangle in lat-long for a given
        # rectangle in projected coordinates which we have to start from
        # here.
        proj = self.map.projection
        if proj is not None:
            inverse = proj.Inverse
        else:
            inverse = None

        scale = self.scale
        offx, offy = self.offset
        xs = []
        ys = []
        x, y, width, height = self.region
        for winx, winy in ((x, y), (x + width, y),
                           (x + width, y + height), (x, y + height)):
            px = (winx - offx) / scale
            py = -(winy - offy) / scale
            if inverse:
                px, py = inverse(px, py)
            xs.append(px)
            ys.append(py)
        left = min(xs)
        right = max(xs)
        top = max(ys)
        bottom = min(ys)

        return layer.ShapesInRegion((left, bottom, right, top))


class ExportRenderer(ScreenRenderer):

    honor_visibility = 1

    def __init__(self, *args, **kw):
        """Initialize the ExportRenderer.

        In addition to all parameters of the the ScreenRender
        constructor, this class requires and additional keyword argument
        destination_region with a tuple (minx, miny, maxx, maxy) giving
        the region in dc coordinates which is to contain the map.
        """
        self.destination_region = kw["destination_region"]
        del kw["destination_region"]
        ScreenRenderer.__init__(self, *args, **kw)

    def RenderMap(self, selected_layer, selected_shapes):
        """Render the map.

        The rendering device has been specified during initialisation.
        The device border distance was set in
        Thuban.UI.viewport.output_transform().

        RenderMap renders a frame set (one page frame, one around
        legend/scalebar and one around the map), the map, the legend and
        the scalebar on the given DC. The map is rendered with the
        region displayed in the canvas view, centered on the area
        available for map display.
        """

        self.selected_layer = selected_layer
        self.selected_shapes = selected_shapes

        # Get some dimensions
        llx, lly, urx, ury = self.region
        mminx, mminy, mmaxx, mmaxy = self.destination_region

        # Manipulate the offset to position the map
        offx, offy = self.offset
        # 1. Shift to corner of map drawing area
        offx = offx + mminx
        offy = offy + mminy

        # 2. Center the map on the map drawing area:
        # region identifies the region on the canvas view:
        # center of map drawing area - half the size of region: rendering origin
        self.shiftx = (mmaxx - mminx)*0.5 - (urx - llx)*0.5
        self.shifty = (mmaxy - mminy)*0.5 - (ury - lly)*0.5

        self.offset = (offx+self.shiftx, offy+self.shifty)

        # Draw the map
        self.dc.BeginDrawing()
        self.dc.DestroyClippingRegion()
        self.dc.SetClippingRegion(mminx+self.shiftx, mminy+self.shifty,
                                  urx, ury)
        self.render_map()
        self.dc.EndDrawing()

        # Draw the rest (frames, legend, scalebar)
        self.dc.BeginDrawing()
        self.dc.DestroyClippingRegion()

        # Force the font for Legend drawing
        font = wxFont(self.resolution * 10, wxSWISS, wxNORMAL, wxNORMAL)
        self.dc.SetFont(font)

        self.render_frame()
        self.render_legend()
        self.render_scalebar()
        self.dc.EndDrawing()

    def render_frame(self):
        """Render the frames for map and legend/scalebar."""

        dc = self.dc
        dc.SetPen(wxBLACK_PEN)
        dc.SetBrush(wxTRANSPARENT_BRUSH)

        # Dimension stuff
        width, height = dc.GetSizeTuple()
        mminx, mminy, mmaxx, mmaxy = self.destination_region

        # Page Frame
        dc.DrawRectangle(15,15,width-30, (mmaxy-mminy)+10)

        # Map Frame
        llx, lly, urx, ury = self.region
        dc.DrawRectangle(mminx + self.shiftx, mminy + self.shifty, urx, ury)

        # Legend Frame
        dc.DrawRectangle(mmaxx+10,mminy,(width-20) - (mmaxx+10), mmaxy-mminy)

        dc.DestroyClippingRegion()
        dc.SetClippingRegion(mmaxx+10,mminy,
                             (width-20) - (mmaxx+10), mmaxy-mminy)

    def render_legend(self):
        """Render the legend on the Map."""

        previewer = ClassDataPreviewer()
        dc = self.dc
        dc.SetPen(wxBLACK_PEN)
        dc.SetBrush(wxTRANSPARENT_BRUSH)

        # Dimension stuff
        width, height = dc.GetSizeTuple()
        mminx, mminy, mmaxx, mmaxy = self.destination_region
        textwidth, textheight = dc.GetTextExtent("0")
        iconwidth  = textheight
        iconheight = textheight
        stepy = textheight+3
        dx = 10
        posx = mmaxx + 10 + 5   # 10 pix distance mapframe/legend frame,
                                # 5 pix inside legend frame
        posy = mminy + 5        # 5 pix inside legend frame

        # Render the legend
        dc.SetTextForeground(wxBLACK)
        if self.map.HasLayers():
            layers = self.map.Layers()[:]
            layers.reverse()
            for l in layers:
                if l.Visible():
                    # Render title
                    dc.DrawText(l.Title(), posx, posy)
                    posy+=stepy
                    if l.HasClassification():
                        # Render classification
                        clazz = l.GetClassification()
                        shapeType = l.ShapeType()
                        for g in clazz:
                            if g.IsVisible():
                                previewer.Draw(dc,
                                    wxRect(posx+dx, posy,
                                           iconwidth, iconheight),
                                    g.GetProperties(), shapeType)
                                dc.DrawText(g.GetDisplayText(),
                                            posx+2*dx+iconwidth, posy)
                                posy+=stepy

    def render_scalebar(self):
        """Render the scalebar."""

        scalebar = ScaleBar(self.map)

        # Dimension stuff
        width, height = self.dc.GetSizeTuple()
        mminx, mminy, mmaxx, mmaxy = self.destination_region

        # Render the scalebar
        scalebar.DrawScaleBar(self.scale, self.dc,
                             (mmaxx+10+5, mmaxy-25),
                             ((width-15-5) - (mmaxx+10+5),20)
                            )
        # 10 pix between map and legend frame, 5 pix inside legend frame
        # 25 pix from the legend frame bottom line
        # Width: 15 pix from DC border, 5 pix inside frame, 10, 5 as above
        # Height: 20

class PrinterRenderer(ExportRenderer):

    # Printing as well as Export / Screen display only the visible layer.
    honor_visibility = 1

