# Copyright 2007 World Wide Workshop Foundation
#
# 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#
# If you find this activity useful or end up using parts of it in one of your
# own creations we would love to hear from you at info@WorldWideWorkshop.org !
#

import pygtk
pygtk.require('2.0')
import gtk, gobject, pango, cairo
import random
import os
import logging
import md5
from cStringIO import StringIO
from mmm_modules import BorderFrame, utils

MAGNET_POWER_PERCENT = 20
CUTTERS = {}

def create_pixmap (w, h):
    cm = gtk.gdk.colormap_get_system()
    pm = gtk.gdk.Pixmap(None, w, h, cm.get_visual().depth)
    gc = pm.new_gc()
    gc.set_colormap(gtk.gdk.colormap_get_system())
    color = cm.alloc_color('white')
    gc.set_foreground(color)
    pm.draw_rectangle(gc, True, 0, 0, w, h)
    return pm

class JigsawPiece (gtk.EventBox):
    __gsignals__ = {'picked' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
                    'moved' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (int, int)),
                    'dropped' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),}
    
    def __init__ (self):
        super(JigsawPiece, self).__init__()
        self.index = None
        self.press_coords = (0,0)
        self.root_coords = (0,0)
        self.last_coords = (0,0)
        self.shape = None
        self.image = gtk.Image()
        self.pb_wf = gtk.Image()
        self.placed = False
        self._prepare_ui()
        self._prepare_event_callbacks()

    def _prepare_ui (self):
        self._c = gtk.Fixed()
        self._c.show()
        self.add(self._c)
        self._c.put(self.image, 0, 0)
        self.image.show()
        self._c.put(self.pb_wf, 0, 0)

    def _prepare_event_callbacks (self):
        self.l_evids = []
        self.l_evids.append(self.connect('button-press-event', self._press_cb))
        self.l_evids.append(self.connect('button-release-event', self._release_cb))
        self.l_evids.append(self.connect('motion-notify-event', self._motion_cb))
        self.connect('expose-event', self._expose_cb)

    def set_index (self, index):
        self.index = index

    def get_index (self):
        return self.index
    
    def set_from_pixbuf(self, pb, pb_wf=None, mask=None):
        self.image.set_from_pixbuf(pb)
        self.width = pb.get_width()
        self.height = pb.get_height()
        self.shape = mask
        if pb_wf is not None:
            self.pb_wf.set_from_pixbuf(pb_wf)
            self.pb_wf.show()
            self.image.hide()
        self.set_size_request(self.width, self.height)

    def get_width (self):
        return self.width

    def get_height (self):
        return self.height

    def hide_wireframe (self):
        self.pb_wf.hide()
        self.image.show()

    def get_position (self):
        # The position relative to the puzzle playing area
        if self.parent and self.parent.window:
            bx,by = self.parent.window.get_origin()
            px,py = self.window.get_origin()
            self.last_coords = (px-bx,py-by)
        return self.last_coords

    def set_position (self, x, y):
        # The new position, relative to the piece parent
        self.parent.move(self, x, y)

    def bring_to_top (self):
        p = self.get_parent()
        self.unparent()
        self.set_parent(p)

    def _press_cb (self, w, e, *attrs):
        self.press_coords = e.get_coords()
        self.root_coords = w.window.get_origin()
        self.emit('picked')
        
    def _motion_cb (self, w, e, *args):
        nx, ny = w.root_coords
        rx, ry = e.get_root_coords()
        px, py = w.press_coords
        delta = (rx-nx-px, ry-ny-py)
        w.root_coords = (nx+delta[0], ny+delta[1])
        self.emit('moved', *delta)

    def _release_cb (self, w, e, *args):
        self.emit('dropped')
        # The actual position in the whole window is w.window.get_origin()

    def _expose_cb (self, *args):
        if self.shape is not None:
            self.window.shape_combine_mask(self.shape, 0, 0)


class CutterBasic (object):
    """ Cutters are used to create the connectors between pieces.
    This one uses no connector at all, pieces will be simple rectangles. """
    SIDE_LEFT = 1
    SIDE_RIGHT = 2
    SIDE_TOP = 3
    SIDE_BOTTOM = 4

    connector_percent = 10

    def draw_connector (self, cairo_ctx, size, side, point_out=True, type=0):
        """
        cairo_ctx is the Cairo context used to draw, already at the start position for the connector.
        size is the length of the connector along the piece side. It's assumed to be 'connector_percent' of the side length.
        side describes the side being drawn.
        point_out is true if the connector is to be drawn onto the outside of the piece.
        type is a placeholder for supporting multiple connector subtypes.
        returns the extrusion size.
        """
        if side in (self.SIDE_RIGHT, self.SIDE_TOP):
            op = lambda x: -x
        else:
            op = lambda x: x

        if side in (self.SIDE_LEFT, self.SIDE_RIGHT):
            cairo_ctx.rel_line_to(0,op(size))
        else:
            cairo_ctx.rel_line_to(op(size),0)
        return 0
CUTTERS['basic'] = CutterBasic

class CutterSimple (CutterBasic):
    connector_percent = 20

    def draw_connector (self, cairo_ctx, size, side, point_out=True, type=0):
        if (si
de in (self.SIDE_RIG
HT, self.SIDE_BOTTOM)) ^ point_out:
            op1 = lambda x: -x
        else:
            op1 = lambda x: x
        if side in (self.SIDE_RIGHT, self.SIDE_TOP):
            op2 = lambda x: -x
        else:
            op2 = lambda x: x
            
        if side in (self.SIDE_LEFT, self.SIDE_RIGHT):
            cairo_ctx.rel_line_to(op1(size),0)
            cairo_ctx.rel_line_to(0,op2(size))
            cairo_ctx.rel_line_to(-op1(size),0)
        else:
            cairo_ctx.rel_line_to(0,op1(size))
            cairo_ctx.rel_line_to(op2(size),0)
            cairo_ctx.rel_line_to(0,-op1(size))
        return size
CUTTERS['simple'] = CutterSimple

class CutterClassic (CutterBasic):
    connector_percent = 20

    def draw_connector (self, cairo_ctx, size, side, point_out=True, type=0):
        if (side in (self.SIDE_RIGHT, self.SIDE_BOTTOM)) ^ point_out:
            op1 = lambda x: -x
        else:
            op1 = lambda x: x
        if side in (self.SIDE_RIGHT, self.SIDE_TOP):
            op2 = lambda x: -x
        else:
            op2 = lambda x: x
            
        if side in (self.SIDE_LEFT, self.SIDE_RIGHT):
            cairo_ctx.rel_curve_to(op1(size*2), -op2(size*1.5), op1(size*2), op2(size*2.5), 0, op2(size))
        else:
            cairo_ctx.rel_curve_to(-op2(size*1.5), op1(size*2), op2(size*2.5), op1(size*2), op2(size), 0)
        return size*2
CUTTERS['classic'] = CutterClassic

class CutBoard (object):
    def __init__ (self, *args, **kwargs):
        if len(args) or len(kwargs):
            self._prepare(*args, **kwargs)
        self.cutter = CutterClassic()
        self.pb = None

    def _prepare (self, cols, rows, cutter=None, hch=None, vch=None):
        if self.pb is None:
            logging.error("You must fist set CutBoard.pb with a pixbuf to be used!")
            return
        if cutter is not None:
            self.cutter = CUTTERS.get(cutter, CutterClassic)()
        self.rows, self.cols = rows, cols
        if hch is not None:
            self.h_connector_hints = hch
        else:
            self.h_connector_hints = [random.random()*2-1 for x in range((self.rows+1)*(sel