# Copyright (C) 2000-2001 The OpenRPG Project
# Copyright (C) 2009 David Vrabel
#
#    openrpg-dev@lists.sourceforge.net
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
# --
#
# File: mapper/whiteboard.py
# Author: Chris Davis
# Maintainer:
# Version:
#   $Id: whiteboard.py,v 1.47 2007/03/09 14:11:55 digitalxero Exp $
#
# Description: This file contains some of the basic definitions for the chat
# utilities in the orpg project.
#

import random

from base import *
from orpg.mapper.map_utils import *
from random import randint


class WhiteboardText:
    def __init__(self, id, text_string, pos, style, pointsize, weight, color="#000000"):
        self.text_string = text_string
        self.id = id
        self.weight = int(weight)
        self.pointsize = int(pointsize)
        self.style = int(style)
        self.textcolor = wx.Color()
        self.textcolor.SetFromName(color)
        self.posx = pos.x
        self.posy = pos.y

        self.font = wx.Font(self.pointsize, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL,
                            wx.FONTWEIGHT_NORMAL, False,
                            open_rpg.get_component('settings').get_setting('defaultfont'))

        self.highlighted = False
        self.is_updated = False

    def highlight(self, highlight=True):
        self.highlighted = highlight

    def move(self, delta):
        self.posx += delta.x
        self.posy += delta.y
        self.is_updated = True

    def set_text_props(self, text_string, style, point, weight, color):
        self.text_string = text_string
        self.textcolor = color

        self.style = int(style)
        self.font.SetStyle(self.style)

        self.pointsize = int(point)
        self.font.SetPointSize(self.pointsize)

        self.weight = int(weight)
        self.font.SetWeight(self.weight)

        self.is_updated = True

    def hit_test(self, pt, dc):
        rect = self.get_rect(dc)
        return rect.InsideXY(pt.x, pt.y)

    def get_rect(self, dc):
        dc.SetFont(self.font)
        (w,h,d,v) = dc.GetFullTextExtent(self.text_string)
        return wx.Rect(self.posx, self.posy, w, h)

    def draw(self, parent, dc, op=wx.COPY):
        # Draw text
        dc.SetTextForeground(self.textcolor)
        dc.SetFont(self.font)
        dc.DrawText(self.text_string, self.posx, self.posy)
        dc.SetTextForeground(wx.Colour(0,0,0))

        # Selection handles
        if self.highlighted:
            dc.SetPen(wx.BLACK_PEN)
            dc.SetBrush(wx.LIGHT_GREY_BRUSH)

            (x,y,w,h) = self.get_rect(dc)
            dc.DrawRectangle(x-7, y-7, 7, 7)
            dc.DrawRectangle(x+w, y-7, 7, 7)
            dc.DrawRectangle(x-7, y+h, 7, 7)
            dc.DrawRectangle(x+w, y+h, 7, 7)

            
    def toxml(self, action="update"):
        if action == "del":
            xml_str = "<text action='del' id='" + self.id + "'/>"
            return xml_str

        xml_str = "<text"

        xml_str += " action='" + action + "'"
        xml_str += " id='" + self.id + "'"

        if self.pointsize != None:
            xml_str += " pointsize='" + str(self.pointsize) + "'"

        if self.style != None:
            xml_str += " style='" + str(self.style) + "'"

        if self.weight != None:
            xml_str += " weight='" + str(self.weight) + "'"

        if self.posx != None:
            xml_str+= " posx='" + str(self.posx) + "'"

        if not (self.posy is None):
            xml_str += " posy='" + str(self.posy) + "'"

        if self.text_string != None:
            xml_str+= " text_string='" + self.text_string + "'"

        if self.textcolor != None:
            xml_str += " color='" + self.textcolor.GetAsString(wx.C2S_HTML_SYNTAX) + "'"

        xml_str += "/>"

        if (action == "update" and self.is_updated) or action == "new":
            self.isUpdated = False
            return xml_str
        else:
            return ''

    def takedom(self, xml_dom):
        self.text_string = xml_dom.getAttribute("text_string")

        if xml_dom.hasAttribute("posy"):
            self.posy = int(xml_dom.getAttribute("posy"))

        if xml_dom.hasAttribute("posx"):
            self.posx = int(xml_dom.getAttribute("posx"))

        if xml_dom.hasAttribute("weight"):
            self.weight = int(xml_dom.getAttribute("weight"))
            self.font.SetWeight(self.weight)

        if xml_dom.hasAttribute("style"):
            self.style = int(xml_dom.getAttribute("style"))
            self.font.SetStyle(self.style)

        if xml_dom.hasAttribute("pointsize"):
            self.pointsize = int(xml_dom.getAttribute("pointsize"))
            self.font.SetPointSize(self.pointsize)

        if xml_dom.hasAttribute("color") and xml_dom.getAttribute("color") != '':
            self.textcolor.SetFromName(xml_dom.getAttribute("color"))


class WhiteboardLine:
    def __init__(self, id, color="#000000", width=1):
        self.linecolor = wx.Color()
        self.linecolor.SetFromName(color)
        self.linewidth = width
        self.points = []
        self.id = id
        self.highlighted = False
        self.is_updated = False

    def add_point(self, x, y):
        p = wx.Point(x, y)
        self.points.append(p)

    def _approximate(self):
        """
        Approximate the line with fewer points.

        For every group of three adjacent points (A, B and C), point B
        is discarded if it's sufficiently close to AC.
        """
        new_points = []
        i = 0
        while i < len(self.points):
            a = self.points[i]
            new_points.append(a)
            for j in range(i+1, len(self.points)-1):
                b = self.points[j]
                c = self.points[j+1]
                if orpg.mapper.map_utils.proximity_test(a, c, b, 0.5):
                    i += 1
                else:
                    break;
            i += 1
        self.points = new_points

    def _choose_handles(self):
        self.handles = []
        self.handles.append(self.points[0])
        dist = 0
        a = self.points[0]
        for b in self.points[1:]:
            dist += orpg.mapper.map_utils.distance_between(a[0], a[1], b[0], b[1])
            if (dist > 50):
                self.handles.append(b)
                dist = 0
            a = b
        self.handles[-1] = self.points[-1]

    def complete(self):
        self._approximate()
        self._choose_handles()

    def points_from_string(self, line_string):
        self.points = []
        for p in line_string.split(";"):
            p = p.split(",")
            if len(p) == 2:
                self.add_point(int(p[0]), int(p[1]))
        self._choose_handles()

    def highlight(self, highlight=True):
        self.highlighted = highlight

    def move(self, delta):
        for p in self.points:
            p.x += delta.x
            p.y += delta.y
        self.is_updated = True

    def hit_test(self, pt):
        if self.points != []:
            a = self.points[0]
            for b in self.points[1:]:
                if orpg.mapper.map_utils.proximity_test(a, b, pt, 12):
                    return True
                a = b
        return False

    def draw(self, parent, dc, op=wx.COPY):
        # Line segments
        pen = wx.Pen(self.linecolor, self.linewidth)
        dc.SetPen(pen)
        dc.SetBrush(wx.BLACK_BRUSH)

        if self.points != []:
            a = self.points[0]
            for b in self.points[1:]:
                (xa, ya) = a
                (xb, yb) = b
                dc.DrawLine(xa, ya, xb, yb)
                a = b

        # Selection handles
        if self.highlighted:
            dc.SetPen(wx.BLACK_PEN)
            dc.SetBrush(wx.LIGHT_GREY_BRUSH)

            for p in self.handles:
                (x, y) = p;
                dc.DrawRectangle(x-3, y-3, 7, 7)

    def toxml(self, action="update"):
        if action == "del":
            xml_str = "<line action='del' id='" + self.id + "'/>"
            return xml_str

        #  if there are any changes, make sure id is one of them
        xml_str = "<line"

        xml_str += " action='" + action + "'"
        xml_str += " id='" + self.id + "'"

        xml_str+= " line_string='"
        for p in self.points:
            (x,y) = p
            xml_str += str(x) + "," + str(y) + ";"
        xml_str += "'"

        if self.linecolor != None:
            xml_str += " color='" + self.linecolor.GetAsString(wx.C2S_HTML_SYNTAX) + "'"

        if self.linewidth != None:
            xml_str += " width='" + str(self.linewidth) + "'"

        xml_str += "/>"

        if (action == "update" and self.is_updated) or action == "new":
            self.is_updated = False
            return xml_str
        return ''

    def takedom(self, xml_dom):
        line_string = xml_dom.getAttribute("line_string")
        self.points_from_string(line_string)

        if xml_dom.hasAttribute("color") and xml_dom.getAttribute("color") != '':
            self.linecolor.SetFromName(xml_dom.getAttribute("color"))
        if xml_dom.hasAttribute("width"):
            self.linewidth = int(xml_dom.getAttribute("width"))


##-----------------------------
## whiteboard layer
##-----------------------------
class whiteboard_layer(layer_base):

    def __init__(self, canvas):
        self.canvas = canvas
        self.log = self.canvas.log

        layer_base.__init__(self)

        self.r_h = RGBHex()
        self.lines = []
        self.texts = []
        self.color = "#000000"
        self.width = 1

    def get_next_highest_z(self):
        return len(self.lines)+1

    def cleanly_collapse_zorder(self):
        pass

    def collapse_zorder(self):
        pass

    def new_line(self):
        id = 'line-' + self.canvas.session.get_next_id()
        line = WhiteboardLine(id, color=self.color, width=self.width)
        self.lines.append(line)
        return line

    def complete_line(self, line):
        line.complete()
        xml_str = "<map><whiteboard>"
        xml_str += line.toxml("new")
        xml_str += "</whiteboard></map>"
        self.canvas.frame.session.send(xml_str)

    def get_line_by_id(self, id):
        for line in self.lines:
            if str(line.id) == str(id):
                return line
        return None

    def get_text_by_id(self, id):
        for text in self.texts:
            if str(text.id) == str(id):
                return text
        return None

    def del_line(self, line):
        xml_str = "<map><whiteboard>"
        xml_str += line.toxml("del")
        xml_str += "</whiteboard></map>"
        self.canvas.frame.session.send(xml_str)
        if line:
            self.lines.remove(line)
        self.canvas.Refresh()

    def del_all_lines(self):
        for i in xrange(len(self.lines)):
            self.del_line(self.lines[0])

    def del_text(self, text):
        xml_str = "<map><whiteboard>"
        xml_str += text.toxml("del")
        xml_str += "</whiteboard></map>"
        self.canvas.frame.session.send(xml_str)
        if text:
            self.texts.remove(text)
        self.canvas.Refresh(True)

    def layerDraw(self, dc):
        for m in self.lines:
            m.draw(self, dc)
        for m in self.texts:
            m.draw(self,dc)

    def hit_test_text(self, pos, dc):
        list_of_texts_matching = []
        if self.canvas.layers['fog'].use_fog == 1:
            if self.canvas.frame.session.role != "GM":
                return list_of_texts_matching
        for m in self.texts:
            if m.hit_test(pos,dc):
                list_of_texts_matching.append(m)
        return list_of_texts_matching

    def hit_test_lines(self, pos):
        list_of_lines_matching = []
        if self.canvas.layers['fog'].use_fog == 1:
            if self.canvas.frame.session.role != "GM":
                return list_of_lines_matching
        for m in self.lines:
            if m.hit_test(pos):
                list_of_lines_matching.append(m)
        return list_of_lines_matching

    def find_line(self, pt):
        line_list = self.hit_test_lines(pt)
        if line_list:
            return line_list[0]
        return None

    def find_text(self, pt, dc):
        text_list = self.hit_test_text(pt, dc)
        if text_list:
            return text_list[0]
        return None

    def setcolor(self, color):
        r,g,b = color.Get()
        self.color = self.r_h.hexstring(r,g,b)

    def sethexcolor(self, hexcolor):
        self.color = hexcolor

    def setwidth(self, width):
        self.width = int(width)

    def set_font(self, font):
        self.font = font

    def add_text(self, text_string, pos, style, pointsize, weight, color="#000000"):
        id = 'text-' + self.canvas.session.get_next_id()
        text = WhiteboardText(id, text_string, pos, style, pointsize, weight, color)
        self.texts.append(text)
        xml_str = "<map><whiteboard>"
        xml_str += text.toxml("new")
        xml_str += "</whiteboard></map>"

        self.canvas.frame.session.send(xml_str)
        self.canvas.Refresh(True)

    def layerToXML(self, action="update"):
        white_string = ""
        if self.lines:
            for l in self.lines:
                white_string += l.toxml(action)

        if self.texts:
            for l in self.texts:
                white_string += l.toxml(action)

        if len(white_string):
            s = "<whiteboard>"
            s += white_string
            s += "</whiteboard>"
            return s
        return ""

    def layerTakeDOM(self, xml_dom):
        children = xml_dom.childNodes
        for l in children:
            nodename = l.nodeName

            action = l.getAttribute("action")
            id = l.getAttribute('id')

            if action == "del":
                if nodename == 'line':
                    line = self.get_line_by_id(id)
                    if line != None:
                        self.lines.remove(line)
                    else:
                        self.log.log("Whiteboard error: Deletion of unknown line object attempted.", ORPG_GENERAL)
                elif nodename == 'text':
                    text = self.get_text_by_id(id)
                    if text != None:
                        self.texts.remove(text)
                    else:
                        self.log.log("Whiteboard error: Deletion of unknown text object attempted.", ORPG_GENERAL)

                else:
                    self.log.log("Whiteboard error: Deletion of unknown whiteboard object attempted.", ORPG_GENERAL)
            elif action == "new":
                if nodename == "line":
                    try:
                        line_string = l.getAttribute('line_string')
                        color = l.getAttribute('color')
                        if color == '#0000000':
                            color = '#000000'
                        id = l.getAttribute('id')
                        width = int(l.getAttribute('width'))

                    except:
                        self.log.log(traceback.format_exc(), ORPG_GENERAL)
                        self.log.log("invalid line", ORPG_GENERAL)
                        continue
                    line = WhiteboardLine(id, color, width)
                    line.points_from_string(line_string)
                    self.lines.append(line)
                elif nodename == "text":
                    try:
                        text_string = l.getAttribute('text_string')
                        style = l.getAttribute('style')
                        pointsize = l.getAttribute('pointsize')
                        weight = l.getAttribute('weight')
                        color = l.getAttribute('color')
                        if color == '#0000000':
                            color = '#000000'
                        id = l.getAttribute('id')
                        posx = l.getAttribute('posx')
                        posy = l.getAttribute('posy')
                        pos = wx.Point(0,0)
                        pos.x = int(posx)
                        pos.y = int(posy)
                    except:
                        self.log.log(traceback.format_exc(), ORPG_GENERAL)
                        self.log.log("invalid line", ORPG_GENERAL)
                        continue
                    text = WhiteboardText(id, text_string, pos, style, pointsize, weight, color)
                    self.texts.append(text)

            else:
                if nodename == "line":
                    line = self.get_line_by_id(id)
                    if line:
                        line.takedom(l)
                    else:
                        self.log.log("Whiteboard error: Update of unknown line attempted.", ORPG_GENERAL)
                if nodename == "text":
                    text = self.get_text_by_id(id)
                    if text:
                        text.takedom(l)
                    else:
                        self.log.log("Whiteboard error: Update of unknown text attempted.", ORPG_GENERAL)
