#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright (C) 2014 Rod Engerstad



import math
from math import sqrt
from random import randrange
from random import randint
import tkinter as tk
import tkinter.colorchooser
from PIL import Image, ImageTk, ImageDraw, ImageEnhance

from pzl_lib import PzlG
from pzl_lib import SettingsB
from pzl_lib import Preferences
from pzl_lib import MenuItem
from pzl_lib import ViewB
from pzl_lib import Flasher
from pzl_lib import ScreenShot
from pzl_lib import Toolbar
from pzl_lib import Menu
from pzl_lib import OpenFileI
from pzl_lib import ClockBox

class Vec2D(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, v):
        return Vec2D(self.x + v.x, self.y + v.y)

    def __sub__(self, v):
        return Vec2D(self.x - v.x, self.y - v.y)

    # nb Scalar multiply and divide!

    def __mul__(self, s):
        return Vec2D(self.x * s, self.y * s)

    def __truediv__(self, s):
        return Vec2D(self.x / s, self.y / s)

    def __abs__(self):
        return sqrt(self.x ** 2 + self.y ** 2)

    def __str__(self):
        return '(%g, %g)' % (self.x, self.y)


class Sgg(object):
    """
    Game globals
    """

    logger = None

    min_canvas_width = 500
    min_canvas_height = 500

    canvas_width = 500
    canvas_height = 500

    tiles_across = None
    tiles_down = None
    tile_size = None
    ghost_origin = Vec2D(None, None)
    ghost_height = None
    ghost_width = None

    pieces = None
    max_ghost_width = None
    max_ghost_height = None

    GIVE_HINT = 0
    APPLY_HINT = 1


class Settings(SettingsB):

    def __init__(self):
        SettingsB.__init__(self)
        self.max_ani_time = None
        self.dic = {}
        self.dic['Pattern'] = 'Rhombs'
        self.dic['set_colour'] = 'Colour_1'
        self.dic['Colour_1'] = 'white'
        self.dic['Colour_2'] = 'red'
        self.dic['Colour_3'] = 'green'
        self.dic['Colour_4'] = 'blue'
        self.dic['Colour_5'] = 'yellow'
        self.dic['Colour_6'] = 'purple'
        self.dic['Colour_7'] = 'orange'
        self.dic['Colour_8'] = 'cyan'
        self.dic['Colour_9'] = 'black'
        self.dic['Table_Colour'] = self.green
        self.dic['Table_Colour'] = '#2c2c2c'
        self.dic['animation'] = 'Off'
        self.dic['draw_style'] = 'random'
        self.dic['set_divisions'] = '4'
        self.dic['l-systems'] = 'Koch a'
        self.dic['set_piece_count'] = '100'
        self.dic['Rotation'] = 'Off'
        self.dic['set_max_ani_time'] = '5'
        self.dic['show_solution'] = 'Quick'
        self.dic['resizeable'] = 'True'
        self.dic['font_size'] = '2'
        self.plot_width = None
        self.plot_height = None
        self.menu_items = []

        self.preferences = Preferences(self.dic)
        self.preferences.load()
        self.divisions = int(self.dic['set_divisions'])
        self.max_ani_time = int(self.dic['set_max_ani_time'])
        Sgg.pieces = int(self.dic['set_piece_count'])
        self.load_menus()
        self.count = len(self.menu_items)
        self.set_fonts()
        self.make_help()
        self.make_tooltip()

    def get_tooltip(self, message_index, alg_index):
        text = ''
        title = self.title
        if message_index != 1 or alg_index is None:
            text = self.tooltip_text[message_index]
        return text, title

    def make_tooltip(self):
        self.tooltip_text = [
            _('New game'),
            _('Give hint'),
            _('Show solution')
            ]

    def make_help(self):
        self.help_text = _("At the top of the display is a Toolbar. The jigsaw piece button at the left is a menu which has options for selecting a picture file, choosing a pattern, the number of puzzle pieces, and whether or not the puzzle pieces should be randomly rotated through multiples of 90 degrees when scattered on the table. There are also 4 options for configuring the patterns and one for saving the current settings. To the right of the puzzle icon is the picture icon used to scatter the tiles. To the right of the picture icon is a wand. If you get stuck, clicking on the wand will cause the program to find a piece which fits the largest current chunk of puzzle, and move it to the top left corner of the table. From here it can be dragged and joined to its neighbours. If you are unable to recognise where the piece fits, a second click on the wand will cause the piece to move to the correct location (though you must click on it to make the join). If you give up on solving the puzzle, a click on the sad smiley icon will start an animation which will finish the puzzle. If puzzles are generated with the tiles randomly rotated, right clicking on a tile will rotate it 90 degrees. The brightness of the background image can be altered by clicking it with the middle mouse button. The creation of the patterns and L-Systems figures can be shown as animations but the resulting images cannot be used as puzzles: animation must be Off for puzzle generation. Before loading a new photo or pattern resize the window to the desired image size. Then load the picture or pattern. If larger than the window it will be scaled to fit. To make space for the puzzle pieces resize the window, then click on the picture icon. The puzzle will be created and the pieces scattered. Grab a piece with the left mouse button. Drop it by releasing the button. If the tile is sufficiently close to a piece it fits the two will automagically be joined and subsequently move together as a single piece. Continue until the whole image is recreated.")

    def load_menus(self):
        self.menu_items.append(MenuItem(
            _('Get Picture'),
            '',
            '',
            '',
            'get_picture'
        ))
        self.menu_items.append(MenuItem(
            _('Pieces'),
            [
                '20',
                '40',
                '60',
                '80',
                '100',
                '120',
                '140',
                '160',
                '180',
                '200',
            ],
            '',
            [
                '20',
                '40',
                '60',
                '80',
                '100',
                '120',
                '140',
                '160',
                '180',
                '200',
            ],
            'set_piece_count'
        ))

        self.menu_items.append(MenuItem(
            _('Animation Time'),
            [
                '4',
                '6',
                '8',
                '10',
                '12',
                '14',
                '16',
                '18',
                '20',
            ],
            '',
            [
                '4',
                '6',
                '8',
                '10',
                '12',
                '14',
                '16',
                '18',
                '20',
            ],
            'set_max_ani_time'
        ))

        self.menu_items.append(MenuItem(
            _('Rotation'),
            [_('On'), _('Off')],
            '',
            ['On', 'Off'],
            'Rotation'
        ))

        self.menu_items.append(MenuItem(
            _('Pattern'),
            ['Pinwheel', 'Danzer', 'Robinson', 'Rhombs', 'Chair'],
            'N',
            ['Pinwheel', 'Danzer', 'Robinson', 'Rhombs', 'Chair'],
            'Pattern'
        ))
        self.menu_items.append(MenuItem(
            'L-Systems',
            ['Koch a', 'Koch b', 'Koch c', 'Koch d', 'Koch e', 'Koch f',
             'Islands and Lakes', 'Quadratic Koch island',
             'Quadratic snowflake', 'Dragon', 'Sierpinski gasket',
             'Plant 1', 'Plant 2', 'Plant 3',
             'Plant 4', 'Plant 5', 'Plant 6', 'Sierpinski carpet',
             'Penrose'],
            'N',
            ['Koch a', 'Koch b', 'Koch c', 'Koch d', 'Koch e', 'Koch f',
             'Islands and Lakes', 'Quadratic Koch island',
             'Quadratic snowflake', 'Dragon', 'Sierpinski gasket',
             'Plant 1', 'Plant 2', 'Plant 3',
             'Plant 4', 'Plant 5', 'Plant 6', 'Sierpinski carpet',
             'Penrose'],
            'l-systems'
        ))

        self.menu_items.append(MenuItem(
            _('Draw Style'),
            [_('outline'), _('random'), _('orientation'), _('type')],
            '',
            ['outline', 'random', 'orientation', 'type'],
            'draw_style'
        ))

        self.menu_items.append(MenuItem(
            _('Divisions'),
            [
                '1',
                '2',
                '3',
                '4',
                '5',
                '6',
                '7',
                '8',
            ],
            '',
            [
                '1',
                '2',
                '3',
                '4',
                '5',
                '6',
                '7',
                '8',
            ],
            'set_divisions'
        ))

        self.menu_items.append(MenuItem(
            _('Animation'),
            [_('On'), _('Off')],
            '',
            ['On', 'Off'],
            'animation'
        ))

        self.menu_items.append(MenuItem(
            _('Set Colour'),
            [_('Colour_1'),
             _('Colour_2'),
             _('Colour_3'),
             _('Colour_4'),
             _('Colour_5'),
             _('Colour_6'),
             _('Colour_7'),
             _('Colour_8'),
             _('Colour_9'),
             _('Table_Colour')],
            'N',
            ['Colour_1',
             'Colour_2',
             'Colour_3',
             'Colour_4',
             'Colour_5',
             'Colour_6',
             'Colour_7',
             'Colour_8',
             'Colour_9',
             'Table_Colour'],
            'set_colour'
        ))

        self.menu_items.append(MenuItem(
            _('Show solution'),
            [_('Quick'), _('Bouncing'), _('Flocking')],
            '',
            ['Quick', 'Bouncing', 'Flocking'],
            'show_solution'
        ))
        self.menu_items.append(MenuItem(
            _('Demo'),
            '',
            '',
            '',
            'demo'
        ))
        self.menu_items.append(MenuItem(
            _('Save Settings'),
            '',
            '',
            '',
            'save_settings'
        ))
        self.menu_items.append(MenuItem(
            _('Help'),
            '',
            '',
            '',
            'help'
        ))
        self.menu_items.append(MenuItem(
            _('About'),
            '',
            '',
            '',
            'about'
        ))


class Triangle(object):
    """
    A triangle is 3 vertices stored as complex numbers and a type.
    For most applications the vertex order is important.
    """

    def __init__(self, vertex_a, vertex_b, vertex_c, triangle_type):
        self.vertex_a = vertex_a
        self.vertex_b = vertex_b
        self.vertex_c = vertex_c
        self.type = triangle_type
        self.middle = None

    def cog(self):
        self.middle = (self.vertex_b + self.vertex_c) / 2


class Rhomb(object):
    """
    A rhomb is 4 vertices stored as complex numbers and a type.
    For most applications the vertex order is important.
    """

    def __init__(self, vertex_a, vertex_b, vertex_c, vertex_d, rhomb_type):
        self.vertex_a = vertex_a
        self.vertex_b = vertex_b
        self.vertex_c = vertex_c
        self.vertex_d = vertex_d
        self.type = rhomb_type

    def cog(self):
        return (self.vertex_a + self.vertex_c) * 0.5


class Rhombs(object):
    """
    Rhombs are formed from adjacent Robinson triangles
    """
    def __init__(self, unit, max_division, cumulative):
        self.triangles = []
        blocks = [0, 20, 70, 200, 540, 1430, 3760, 10860, 26830,
                  68640, 178100]

        triangles = Robinson(unit, max_division, cumulative).triangles
        if cumulative:
            # Can only convert triangles to rhombs when all triangles
            # are of equivalent size, so cumulative have to be done in
            # batches.
            rhombs = []
            for block in range(1, max_division + 1):
                block_1 = blocks[block-1]
                block_2 = blocks[block]
                rhombs.extend(self.to_rhombs(
                    triangles[block_1:block_2], cumulative))
        else:
            rhombs = self.to_rhombs(triangles, cumulative)
        self.triangles = rhombs

    def to_rhombs(self, triangles, cumulative):
        triangle_count = len(triangles)
        # sort on edge centre
        for triangle in triangles:
            triangle.cog()
        triangles.sort(key=lambda triangle:
                       (triangle.middle.real - triangle.middle.imag))
        # merge adjacent triangles into rhombs
        pairs = []
        for item in range(1, triangle_count):
            m_1 = triangles[item-1].middle
            m_2 = triangles[item].middle
            if abs(m_1.real - m_2.real) < 0.0001 and \
                    abs(m_1.imag - m_2.imag) < 0.0001:
                pairs.append(triangles[item-1])
                pairs.append(triangles[item])
        triangles = pairs
        triangle_count = len(triangles)
        rhombs = []
        for item in range(0, triangle_count, 2):
            m_1 = triangles[item]
            m_2 = triangles[item+1]
            rhombs.append(Rhomb(m_1.vertex_a, m_1.vertex_b,
                                m_1.vertex_c, m_2.vertex_a, m_1.type))

        if cumulative:
            rhombs.sort(key=lambda rhomb:
                        (
                            (rhomb.vertex_a.real - rhomb.vertex_c.real)**2
                            + (rhomb.vertex_a.imag - rhomb.vertex_c.imag)**2
                            + (rhomb.vertex_b.real - rhomb.vertex_d.real)**2
                            + (rhomb.vertex_b.imag - rhomb.vertex_d.imag)**2),
                        reverse=True)
        return rhombs


class Chairs(object):
    """
    A chair is defined by origin, orientation and size.
    Origin being the coordinate of vertex_a.
    """

    def __init__(self, size, max_division, cumulative):

        def divide(chairs):
            res = []
            for chair in chairs:
                res.extend(chair.divide())
            return res

        origin = complex(0, 0)
        max_division = int(min(max_division, 5))
        size = int(max_division * (size / max_division))

        size = int(64 * (size / 64))
        chairs = [Chair(origin, 1, size)]

        if cumulative:
            self.chairs = []
            for _ in range(max_division):
                chairs = divide(chairs)
                self.chairs += chairs
        else:
            for _ in range(max_division):
                chairs = divide(chairs)
            self.chairs = chairs


class Chair(object):
    """
    A chair is defined by origin, orientation and size.
    Origin being the coordinate of vertex_a.
    """

    def __init__(self, origin, orientation, size):
        self.origin = origin
        self.orientation = orientation
        self.size = size
        self.vertex_a = None
        self.vertex_b = None
        self.vertex_c = None
        self.vertex_d = None
        self.vertex_e = None
        self.vertex_f = None

    def divide(self):
        """
        make 4 subversions, p,q,s,r
        """

        if self.orientation == 1:
            chair_p = Chair(self.origin + complex(self.size/4, self.size/4),
                            1, self.size/2)

            chair_q = Chair(self.origin + complex(0, self.size),
                            2, self.size/2)

            chair_r = Chair(self.origin + complex(self.size, 0),
                            3, self.size/2)

            chair_s = Chair(self.origin, 1, self.size/2)

        elif self.orientation == 2:
            chair_p = Chair(self.origin + complex(self.size/4, -self.size/4),
                            2, self.size/2)

            chair_q = Chair(self.origin + complex(self.size, 0),
                            4, self.size/2)

            chair_r = Chair(self.origin - complex(0, self.size),
                            1, self.size/2)

            chair_s = Chair(self.origin, 2, self.size/2)

        elif self.orientation == 3:
            chair_p = Chair(self.origin + complex(-self.size/4, self.size/4),
                            3, self.size/2)

            chair_q = Chair(self.origin - complex(self.size, 0),
                            1, self.size/2)

            chair_r = Chair(self.origin + complex(0, self.size),
                            4, self.size/2)

            chair_s = Chair(self.origin, 3, self.size/2)

        elif self.orientation == 4:
            chair_p = Chair(self.origin - complex(self.size/4, self.size/4),
                            4, self.size/2)

            chair_q = Chair(self.origin - complex(0, self.size),
                            3, self.size/2)

            chair_r = Chair(self.origin - complex(self.size, 0),
                            2, self.size/2)

            chair_s = Chair(self.origin, 4, self.size/2)

        return [chair_s, chair_q, chair_p, chair_r]

    def get_vertices(self):
        """
        Get the vertices of the chair
        """
        if self.orientation == 1:
            vertex_a = self.origin
            vertex_b = vertex_a + complex(0, self.size)
            vertex_c = vertex_b + complex(self.size/2, 0)
            vertex_d = vertex_c + complex(0, -self.size/2)
            vertex_e = vertex_d + complex(self.size/2, 0)
            vertex_f = vertex_a + complex(self.size, 0)
        elif self.orientation == 2:
            vertex_a = self.origin
            vertex_b = vertex_a + complex(self.size, 0)
            vertex_c = vertex_b + complex(0, -self.size/2)
            vertex_d = vertex_c + complex(-self.size/2, 0)
            vertex_e = vertex_d + complex(0, - self.size/2)
            vertex_f = vertex_e + complex(-self.size/2, 0)
        elif self.orientation == 3:
            vertex_a = self.origin
            vertex_b = vertex_a + complex(-self.size, 0)
            vertex_c = vertex_b + complex(0, self.size/2)
            vertex_d = vertex_c + complex(self.size/2, 0)
            vertex_e = vertex_d + complex(0, self.size/2)
            vertex_f = vertex_e + complex(self.size/2, 0)
        elif self.orientation == 4:
            vertex_a = self.origin
            vertex_b = vertex_a + complex(0, -self.size)
            vertex_c = vertex_b - complex(self.size/2, 0)
            vertex_d = vertex_c + complex(0, self.size/2)
            vertex_e = vertex_d - complex(self.size/2, 0)
            vertex_f = vertex_e + complex(0, self.size/2)

        self.vertex_a = vertex_a
        self.vertex_b = vertex_b
        self.vertex_c = vertex_c
        self.vertex_d = vertex_d
        self.vertex_e = vertex_e
        self.vertex_f = vertex_f


class Pinwheel(object):
    """
    Given a guide size (unit) and a division (division)
    return a pinwheel tiling.
    """
    def __init__(self, unit, max_division, cumulative):
        self.unit = int(unit / 2.)
        max_division = int(min(max_division, 4))
        vertex_a = complex(0.0, 0.0)
        vertex_b = complex(self.unit * 2.2, 0.0)
        vertex_c = complex(self.unit * 2.2, self.unit)
        vertex_d = complex(0.0, self.unit)

        triangles = [Triangle(vertex_a, vertex_b,
                              vertex_c, 0),
                     Triangle(vertex_c, vertex_d,
                              vertex_a, 0)]
        if cumulative:
            self.triangles = []
            for _ in range(max_division):
                triangles = self.divide_triangles(triangles)
                self.triangles += triangles
        else:
            for _ in range(max_division):
                triangles = self.divide_triangles(triangles)
            self.triangles = triangles

    def divide_triangles(self, triangles):
        def divide(triangle):
            factor = 0.4
            vertex_a = triangle.vertex_a
            vertex_b = triangle.vertex_b
            vertex_c = triangle.vertex_c
            vertex_f = vertex_a + (vertex_b - vertex_a) * 0.5
            vertex_e = vertex_a + (vertex_c - vertex_a) * factor
            vertex_d = vertex_a + (vertex_c - vertex_a) * factor * 2.0
            vertex_g = vertex_d + (vertex_b - vertex_d) * 0.5

            return [Triangle(vertex_a, vertex_e, vertex_f, 1),
                    Triangle(vertex_d, vertex_e, vertex_f, 2),
                    Triangle(vertex_f, vertex_g, vertex_d, 3),
                    Triangle(vertex_f, vertex_g, vertex_b, 4),
                    Triangle(vertex_b, vertex_d, vertex_c, 5)]

        res = []
        for triangle in triangles:
            res.extend(divide(triangle))
        return res


class Robinson(object):
    def __init__(self, unit, max_division, cumulative):

        def divide(triangles):
            ratio = (1 + math.sqrt(5)) / 2
            res = []
            for triangle in triangles:
                vertex_a = triangle.vertex_a
                vertex_b = triangle.vertex_b
                vertex_c = triangle.vertex_c
                if triangle.type == 1:
                    vertex_d = vertex_a + \
                               (vertex_b - vertex_a) / ratio
                    res += [Triangle(vertex_c, vertex_d, vertex_b, 1),
                            Triangle(vertex_d, vertex_c, vertex_a, 2)]
                else:
                    vertex_e = vertex_b + \
                               (vertex_a - vertex_b) / ratio
                    vertex_f = vertex_b + \
                               (vertex_c - vertex_b) / ratio
                    res += [Triangle(vertex_f, vertex_c, vertex_a, 2),
                            Triangle(vertex_e, vertex_f, vertex_b, 2),
                            Triangle(vertex_f, vertex_e, vertex_a, 1)]
            return res

        self.unit = int(unit / 2.0)
        triangles = []
        phi = math.pi / 10
        vertex_a = complex(0, 0)
        triangle_type = 1
        for i in range(10):
            phim = (2*i - 1) * phi
            phip = (2*i + 1) * phi
            vertex_b = self.unit * (math.cos(phim) + math.sin(phim)*1j)
            vertex_c = self.unit * (math.cos(phip) + math.sin(phip)*1j)
            if i % 2 == 0:
                # flip
                vertex_b, vertex_c = vertex_c, vertex_b
            triangles.append(Triangle(vertex_a, vertex_b, vertex_c,
                                      triangle_type))

        addition = complex(self.unit, self.unit)
        for triangle in triangles:
            triangle.vertex_a += addition
            triangle.vertex_b += addition
            triangle.vertex_c += addition

        if cumulative:
            self.triangles = []
            for _ in range(max_division):
                triangles = divide(triangles)
                self.triangles += triangles
        else:
            for _ in range(max_division):
                triangles = divide(triangles)
            self.triangles = triangles


class Danzer(object):
    """
    Danzer triangles
    3 of them
    1 sides b, c, c
    2 sides a, b, c
    3 sides a, a, c
    unit is initially c
    The inflation factor is 1+sin(2 pi/7)/sin(pi/7).
    The three different edge lengths are proportional to
    sin(pi/7), sin(2 pi/7), sin(3 pi/7).
    """

    def __init__(self, unit, max_division, cumulative):
        self.unit = unit
        max_division = int(min(max_division, 4))
        theta = math.radians(180 / 7.0)  # 25.714
        theta_x2 = math.radians(360 / 7.0)   # 51.429
        theta_x3 = math.radians(540 / 7.0)  # 77.143
        sin_theta = math.sin(theta)
        sin_theta_x2 = math.sin(theta_x2)
        sin_theta_x3 = math.sin(theta_x3)
        self.length_b = self.unit * sin_theta
        self.length_a = self.unit * sin_theta_x2
        self.length_c = self.unit * sin_theta_x3
        self.abc = self.length_a + self.length_b + self.length_c
        self.acc = self.length_a + self.length_c + self.length_c
        self.ab = self.length_a + self.length_b
        self.ac = self.length_a + self.length_c

        vertex_c = complex(0.0, 0.0)
        vertex_b = complex(self.length_c, 0.0)
        vertex_a = complex(self.length_a * math.cos(theta_x2),
                           self.length_a * math.sin(theta_x2))

        triangles = [Triangle(vertex_a, vertex_b, vertex_c, 3)]

        if cumulative:
            self.triangles = []
            for _ in range(max_division):
                triangles = self.divide(triangles)
                self.triangles += triangles
        else:
            for _ in range(max_division):
                triangles = self.divide(triangles)
            self.triangles = triangles

    def divide(self, triangles):
        res = []
        for triangle in triangles:
            if triangle.type == 1:
                res.extend(self.divide_1(triangle))
            elif triangle.type == 2:
                res.extend(self.divide_2(triangle))
            elif triangle.type == 3:
                res.extend(self.divide_3(triangle))
        return res

    def divide_1(self, triangle_in):
        """
        Divide a triangle_1 into 8 triangles
        """
        vertex_a = triangle_in.vertex_a
        vertex_b = triangle_in.vertex_b
        vertex_c = triangle_in.vertex_c

        vertex_e = vertex_b + (vertex_a - vertex_b) * \
            self.length_c / self.acc
        vertex_d = vertex_b + (vertex_a - vertex_b) * \
            self.ac / self.acc
        vertex_f = vertex_c + (vertex_b - vertex_c) * \
            self.length_a / self.ab
        vertex_g = vertex_c + (vertex_a - vertex_c) * \
            self.length_c / self.acc
        vertex_h = vertex_c + (vertex_a - vertex_c) * \
            self.ac / self.acc
        vertex_i = vertex_f + (vertex_h - vertex_f) * \
            self.length_a / self.ac

        return [Triangle(vertex_a, vertex_d, vertex_h, 1),
                Triangle(vertex_i, vertex_d, vertex_h, 1),
                Triangle(vertex_g, vertex_h, vertex_i, 2),
                Triangle(vertex_e, vertex_d, vertex_i, 2),
                Triangle(vertex_c, vertex_g, vertex_i, 1),
                Triangle(vertex_b, vertex_i, vertex_e, 1),
                Triangle(vertex_f, vertex_i, vertex_b, 2),
                Triangle(vertex_f, vertex_c, vertex_i, 3)]

    def divide_2(self, triangle_in):
        """
        Divide a triangle_1 into 6 triangles
        """

        vertex_a = triangle_in.vertex_a
        vertex_b = triangle_in.vertex_c
        vertex_c = triangle_in.vertex_b

        vertex_d = vertex_b + (vertex_a - vertex_b) * \
            self.length_b / self.ab
        vertex_f = vertex_c + (vertex_a - vertex_c) * \
            self.length_c / self.abc
        vertex_g = vertex_c + (vertex_a - vertex_c) * \
            self.ac / self.abc
        vertex_h = vertex_c + (vertex_b - vertex_c) * \
            self.length_c / self.acc
        vertex_e = vertex_c + (vertex_b - vertex_c) * \
            self.ac / self.acc

        return [Triangle(vertex_d, vertex_e, vertex_b, 2),
                Triangle(vertex_d, vertex_a, vertex_e, 3),
                Triangle(vertex_g, vertex_e, vertex_a, 2),
                Triangle(vertex_g, vertex_f, vertex_e, 3),
                Triangle(vertex_h, vertex_e, vertex_f, 2),
                Triangle(vertex_c, vertex_f, vertex_h, 1)]

    def divide_3(self, triangle_in):
        """
        Divide a triangle_3 into 11 triangles
        """

        vertex_a = triangle_in.vertex_a
        vertex_b = triangle_in.vertex_b
        vertex_c = triangle_in.vertex_c

        vertex_g = vertex_c + (vertex_b - vertex_c) * \
            self.length_c / self.acc
        vertex_f = vertex_c + (vertex_b - vertex_c) * \
            self.ac / self.acc
        vertex_h = vertex_c + (vertex_a - vertex_c) * \
            self.length_c / self.abc
        vertex_i = vertex_c + (vertex_a - vertex_c) * \
            self.ac / self.abc
        vertex_e = vertex_b + (vertex_a - vertex_b) * \
            self.length_b / self.abc
        vertex_d = vertex_b + (vertex_a - vertex_b) * \
            self.ab / self.abc
        vertex_k = vertex_c + (vertex_d - vertex_c) * \
            self.length_c / self.abc
        vertex_j = vertex_c + (vertex_d - vertex_c) * \
            self.ac / self.abc

        ret = [Triangle(vertex_c, vertex_h, vertex_k, 1),
               Triangle(vertex_c, vertex_g, vertex_k, 1),
               Triangle(vertex_g, vertex_f, vertex_k, 2),
               Triangle(vertex_j, vertex_k, vertex_f, 3),
               Triangle(vertex_k, vertex_j, vertex_h, 2),
               Triangle(vertex_i, vertex_h, vertex_j, 3),
               Triangle(vertex_i, vertex_j, vertex_a, 2),
               Triangle(vertex_a, vertex_j, vertex_d, 1),
               Triangle(vertex_j, vertex_f, vertex_d, 2),
               Triangle(vertex_e, vertex_d, vertex_f, 3),
               Triangle(vertex_e, vertex_f, vertex_b, 2)]
        return ret


class Hare(object):
    """
    Turtle graphics!
    current position is position
    current direction is angle
    current increment is step
    angle increment is delta_angle
    """
    def __init__(self, commands, delta):
        self.commands = commands
        self.stack = []
        delta = math.radians(delta)
        self.delta_angle = delta
        self.position = complex(0, 0j)
        self.angle = 270.0
        self.angle = math.radians(self.angle)
        self.step = complex(math.cos(self.angle),
                            math.sin(self.angle))
        self.path = []
        self.x_min = self.position.real
        self.x_max = self.position.real
        self.y_min = self.position.imag
        self.y_max = self.position.imag

    def get_path(self, l_system):
        self.stack = []
        segment = []
        self.path = []
        for command in l_system:
            if command in self.commands['Draw']:
                segment.append(self.position)
                self.position += self.step
                segment.append(self.position)
                self.path.append(segment)
                segment = []
            elif command == self.commands['Move']:
                self.position += self.step
            elif command == self.commands['Turn left']:
                self.angle += self.delta_angle
                self.step = complex(math.cos(self.angle),
                                    math.sin(self.angle))
            elif command == self.commands['Turn right']:
                self.angle -= self.delta_angle
                self.step = complex(math.cos(self.angle),
                                    math.sin(self.angle))
            elif command == self.commands['Push']:
                item = (self.position, self.angle)
                self.stack.append(item)
            elif command == self.commands['Pop']:
                item = self.stack.pop()
                self.position = item[0]
                self.angle = item[1]
                self.step = complex(math.cos(self.angle),
                                    math.sin(self.angle))
            else:
                # unrecognised symbol so do nothing
                pass

        # get range

        for segment in self.path:
            self.x_min = int(min(self.x_min, segment[0].real, segment[1].real))
            self.x_max = int(max(self.x_max, segment[0].real, segment[1].real))
            self.y_min = int(min(self.y_min, segment[0].imag, segment[1].imag))
            self.y_max = int(max(self.y_max, segment[0].imag, segment[1].imag))


class Lindenmayer(object):
    """
    Lindenmayer or L-Systems
    Here I've just implemented a few simple examples using a limited
    set of commands.
    """
    def __init__(self, axiom, productions, iterations):
        self.commands = {
            'Draw': set(['F', 'G', '1']),
            'Move': 'f',
            'Turn left': '+',
            'Turn right': '-',
            'Push': '[',
            'Pop': ']'}

        current = axiom
        next_bit = ""

        for _ in range(iterations):
            for symbol in range(len(current)):
                found_it = False
                for letter, production in list(productions.items()):
                    if current[symbol] == letter:
                        next_bit = next_bit + production
                        found_it = True
                        break
                if not found_it:
                    next_bit = next_bit + current[symbol]
            current = next_bit
            next_bit = ""

        self.l_system = current


class TilingPattern(object):
    def __init__(self, settings, master, board, lsys_name):
        self.settings = settings
        self.board = board
        self.board.photoimage = None
        self.master = master
        self.draw = None
        self.image = None
        self.triangles = None
        self.line_number = None
        self.triangle_number = None
        self.triangle_count = None
        self.chairs = None
        self.chair_number = None
        self.chair_count = None
        self.delay = None
        self.scale = None
        self.addition = None
        self.line_width = None

        if self.settings.dic['animation'] == 'On':
            self.cumulative = True
        else:
            self.cumulative = False

        self.settings.plot_width = Sgg.max_ghost_width
        self.settings.plot_height = Sgg.max_ghost_height
        if lsys_name != '':
            self.start_game_l(lsys_name)
        else:
            name = self.settings.dic['Pattern']
            if name == 'Pinwheel':
                self.do_pinwheel()
            elif name == 'Danzer':
                self.do_danzer()
            elif name == 'Robinson':
                self.do_robinson()
            elif name == 'Rhombs':
                self.do_rhombs()
            elif name == 'Chair':
                self.do_chair()

    def make_background(self, width, height):
        image = Image.new("RGB", (width, height),
                          self.settings.dic['Table_Colour'])
        return image

    def do_robinson(self):
        max_division = self.settings.divisions
        max_ani_time = self.settings.max_ani_time * 1000
        unit = int((2 * min(self.settings.plot_width,
                            self.settings.plot_height)) / 2)
        width = unit
        height = unit
        image = self.make_background(width, height)
        self.draw = ImageDraw.Draw(image)
        self.image = image
        self.triangles = []
        triangles = Robinson(unit, max_division, self.cumulative).triangles
        if self.cumulative:
            self.triangles = triangles
            self.triangle_number = 0
            self.triangle_count = len(self.triangles)
            self.delay = int(max(1, max_ani_time / self.triangle_count))
            self.animate_r()
        else:
            for triangle in triangles:
                coords = self.get_coords(triangle, 1)
                self.draw_shape(coords, None, triangle.type)

    def do_rhombs(self):
        max_division = self.settings.divisions
        max_ani_time = self.settings.max_ani_time * 1000

        unit = int(min(self.settings.plot_width, self.settings.plot_height))
        width = unit
        height = unit
        image = self.make_background(width, height)
        self.draw = ImageDraw.Draw(image)
        self.image = image
        self.triangles = []
        max_division = int(min(max_division, 10))
        self.triangles = Rhombs(unit, max_division, self.cumulative).triangles
        if self.cumulative:
            self.triangle_count = len(self.triangles)
            self.triangle_number = 0
            self.delay = int(max(1, max_ani_time / self.triangle_count))
            self.animate_rh()
        else:
            for rhomb in self.triangles:
                coords = self.get_coords(rhomb, 2)
                self.draw_shape(coords, None, rhomb.type)

    def do_pinwheel(self):
        max_division = self.settings.divisions
        max_ani_time = self.settings.max_ani_time * 1000
        unit = int(max(self.settings.plot_width, self.settings.plot_height))
        width = int((2 * unit) / 2)
        height = int((2 * unit) / 4)
        image = self.make_background(width, height)
        self.draw = ImageDraw.Draw(image)
        self.image = image
        triangles = Pinwheel(unit, max_division, self.cumulative).triangles

        if self.cumulative:
            self.triangles = triangles
            self.triangle_number = 0
            self.triangle_count = len(self.triangles)
            self.delay = int(max(1, max_ani_time / self.triangle_count))
            self.animate_r()
        else:
            for triangle in triangles:
                coords = self.get_coords(triangle, 1)
                self.draw_shape(coords, None, triangle.type)

    def do_danzer(self):
        max_division = self.settings.divisions
        max_ani_time = self.settings.max_ani_time * 1000
        unit = int(max(self.settings.plot_width, self.settings.plot_height))
        width = int((2 * unit) / 2)
        height = int((2 * unit) / 3)
        image = self.make_background(width, height)
        self.draw = ImageDraw.Draw(image)
        self.image = image
        triangles = Danzer(unit, max_division, self.cumulative).triangles
        if self.cumulative:
            self.triangles = triangles
            self.triangle_number = 0
            self.triangle_count = len(self.triangles)
            self.delay = int(max(1, max_ani_time / self.triangle_count))
            self.animate_r()

        else:
            for triangle in triangles:
                coords = self.get_coords(triangle, 1)
                self.draw_shape(coords, None, triangle.type)

    def do_chair(self):
        max_division = self.settings.divisions
        max_ani_time = self.settings.max_ani_time * 1000
        unit = int(min(self.settings.plot_width, self.settings.plot_height))
        width = unit
        height = unit
        image = self.make_background(width, height)
        self.draw = ImageDraw.Draw(image)
        self.image = image
        chairs = Chairs(unit, max_division, self.cumulative).chairs
        if self.cumulative:
            self.chairs = chairs
            self.chair_number = 0
            self.chair_count = len(self.chairs)
            self.delay = int(max(1, max_ani_time / self.chair_count))
            for chair in self.chairs:
                chair.get_vertices()
            self.animate_c()

        else:
            count = 0
            for chair in chairs:
                chair.get_vertices()
                coords = self.get_coords(chair, 3)
                self.draw_shape(coords, chair.orientation,
                                count % 4 + 1)
                count += 1

    def start_game_l(self, name):
        """
        Define the codes for various l-systems.
        Mostly taken from Prusinkiewicz and Lindenmayer (page nos)
        """

        iterations = None
        if name == 'Koch a':
        # p10 a
            iterations = 4
            delta = 90
            axiom = "F-F-F-F"
            productions = {'F': 'FF-F-F-F-F-F+F'}
            side_ratio = 1.0
        elif name == 'Koch b':
        # p10 b
            delta = 90
            iterations = 4
            axiom = "F-F-F-F"
            productions = {'F': 'FF-F-F-F-FF'}
            side_ratio = 1.0
        elif name == 'Koch c':
        # p10 c
            delta = 90
            iterations = 3
            axiom = "F-F-F-F"
            productions = {'F': 'FF-F+F-F-FF'}
            side_ratio = 1.0
        elif name == 'Koch d':
        # p10 d
            delta = 90
            iterations = 4
            axiom = "F-F-F-F"
            productions = {'F': 'FF-F--F-F'}
            side_ratio = 1.0
        elif name == 'Koch e':
        # p10 e
            delta = 90
            iterations = 5
            axiom = "F-F-F-F"
            productions = {'F': 'F-FF--F-F'}
            side_ratio = 1.0
        elif name == 'Koch f':
        # p10 f
            delta = 90
            iterations = 4
            axiom = "F-F-F-F"
            productions = {'F': 'F-F+F-F-F'}
            side_ratio = 1.0
        elif name == 'Islands and Lakes':
        # p9, 1.8
            iterations = 2
            delta = 90
            axiom = "F+F+F+F"
            productions = {'F': 'F+f-FF+F+FF+Ff+FF-f+FF-F-FF-Ff-FFF',
                           'f': 'ffffff'}
            side_ratio = 1.0
        elif name == 'Quadratic Koch island':
            # p9 a
            iterations = 2
            delta = 90
            axiom = 'F-F-F-F'
            productions = {'F': 'F+FF-FF-F-F+F+FF-F-F+F+FF+FF-F'}
            side_ratio = 1.0
        elif name == 'Quadratic snowflake':
        # p9, 1.7b
            iterations = 4
            delta = 90
            axiom = "-F"
            productions = {'F': 'F+F-F-F+F'}
            side_ratio = 1.0
        elif name == 'Dragon':
        # p11 a
            iterations = 10
            delta = 90
            axiom = "F"
            productions = {'F': 'F+G+', 'G': '-F-G'}
            side_ratio = 1.0
        elif name == 'Sierpinski gasket':
        # p11 b
            iterations = 8
            delta = 60
            axiom = 'G'
            productions = {'F': 'G+F+G', 'G': 'F-G-F'}

            side_ratio = 1.0
        elif name == 'Plant 1':
        # p25 a
            iterations = 5
            delta = 27.7
            axiom = 'F'
            productions = {'F': 'F[+F]F[-F]F'}
            side_ratio = 0.4
        elif name == 'Plant 2':
        # p25 b
            iterations = 5
            delta = 20
            axiom = 'F'
            productions = {'F': 'F[+F]F[-F][F]'}
            side_ratio = 0.6
        elif name == 'Plant 3':
        # p25 c
            iterations = 4
            delta = 22.5
            axiom = 'F'
            productions = {'F': 'FF-[-F+F+F]+[+F-F-F]'}
            side_ratio = 0.7
        elif name == 'Plant 4':
        # p25 d
            iterations = 6
            delta = 20
            axiom = 'X'
            productions = {'X': 'F[+X]F[-X]+X',
                           'F': 'FF'}
            side_ratio = 0.7
        elif name == 'Plant 5':
        # p25 e
            iterations = 7
            delta = 25.7
            axiom = 'X'
            productions = {'X': 'F[+X][-X]FX', 'F': 'FF'}
            side_ratio = 0.5
        elif name == 'Plant 6':
        # p25 f
            iterations = 5
            delta = 22.5
            axiom = 'X'
            productions = {'X': 'F-[[X]+X]+F[+FX]-X',
                           'F': 'FF'}
            side_ratio = 0.7

        elif name == 'Sierpinski carpet':
            iterations = 4
            delta = 90.0
            axiom = 'F'
            productions = {'F': 'F+F-F-F-f+F+F+F-F',
                           'f': 'fff'}
            side_ratio = 1.0

        elif name == 'Penrose':
            iterations = 5
            delta = 36.0
            axiom = '[7]++[7]++[7]++[7]++[7]'
            productions = {'6': '81++91----71[-81----61]++',
                           '7': '+81--91[---61--71]+',
                           '8': '-61++71[+++81++91]-',
                           '9': '--81++++61[+91++++71]--71',
                           '1': ''}
            side_ratio = 1.0

        if iterations is not None:
            self.do_l_systems(iterations, delta, axiom,
                              productions, side_ratio)

    def do_l_systems(self, iterations, delta, axiom, productions, side_ratio):
        unit = int(min(self.settings.plot_width, self.settings.plot_height))
        height = int(unit * side_ratio)
        width = int(unit * side_ratio)
        height = unit
        thing = Lindenmayer(axiom, productions, iterations)
        hare = Hare(thing.commands, delta)
        image = self.make_background(width, height)
        self.draw = ImageDraw.Draw(image)
        self.image = image

        hare.get_path(thing.l_system)
        x_min = hare.x_min
        x_max = hare.x_max
        y_min = hare.y_min
        y_max = hare.y_max
        if self.cumulative:
            self.init_animate_ls(hare.path,
                                 x_min, x_max, y_min, y_max, width, height)
        else:
            self.draw_segments(hare.path,
                               x_min, x_max, y_min, y_max, width, height)

    def draw_segments(self, segments, x_min, x_max, y_min, y_max,
                      width, height):
        colour_names = [
            'Colour_1',
            'Colour_2',
            'Colour_3',
            'Colour_4',
            'Colour_5',
            'Colour_6',
            'Colour_7',
            'Colour_8',
            'Colour_9']
        outline = self.settings.dic['Colour_9']
        fill = self.settings.dic['Colour_1']
        line_width = 2
        x_min -= line_width
        x_max += line_width
        y_min -= line_width
        y_max += line_width
        b = int(max(width, height))
        z = int(max(abs(x_max - x_min), abs(y_max - y_min)))
        scale = abs(b / z)
        coords = []
        addition = complex(x_min, y_min)
        items = len(segments)
        for item_index in range(items):
            segment = segments[item_index]
            coords = []
            for coord in segment:
                coord -= addition
                coord *= scale
                x_pos = coord.real
                y_pos = coord.imag
                coords.append(x_pos)
                coords.append(y_pos)
            if self.settings.dic['draw_style'] == 'random':
                colour_index = randint(0, len(colour_names)-1)
                fill = self.settings.dic[colour_names[colour_index]]
                outline = self.settings.dic[colour_names[colour_index]]
            elif self.settings.dic['draw_style'] == 'type':
                if coords[0] < coords[2]:
                    fill = self.settings.dic['Colour_2']
                    outline = self.settings.dic['Colour_2']
                else:
                    fill = self.settings.dic['Colour_3']
                    outline = self.settings.dic['Colour_3']
            elif self.settings.dic['draw_style'] == 'outline':
                fill = self.settings.dic['Colour_1']
                outline = self.settings.dic['Colour_1']

            self.draw.polygon(coords, fill=fill, outline=outline)

    def init_animate_ls(self,
                        segments, x_min, x_max, y_min, y_max, width, height):
        line_width = 2
        x_min -= line_width
        x_max += line_width
        y_min -= line_width
        y_max += line_width
        b = int(max(width, height))
        z = int(max(abs(x_max - x_min), abs(y_max - y_min)))
        self.scale = abs(b / z)
        self.addition = complex(x_min, y_min)
        self.line_width = line_width
        self.triangles = segments
        self.triangle_count = len(self.triangles)
        max_ani_time = self.settings.max_ani_time * 1000
        self.delay = int(round(max(1, max_ani_time /
                                   float(self.triangle_count))))
        self.triangle_number = 0
        self.animate_ls()

    def animate_ls(self):
        if self.triangle_number < self.triangle_count:
            triangle = self.triangles[self.triangle_number]
            coords = []
            for coord in triangle:
                coord -= self.addition
                coord *= self.scale
                x_pos = coord.real
                y_pos = coord.imag
                coords.append(x_pos)
                coords.append(y_pos)

            fill = self.settings.dic['Colour_1']
            if self.settings.dic['draw_style'] == 'random':
                colour_names = [
                    'Colour_1',
                    'Colour_2',
                    'Colour_3',
                    'Colour_4',
                    'Colour_5',
                    'Colour_6',
                    'Colour_7',
                    'Colour_8',
                    'Colour_9']
                colour_index = randint(0, len(colour_names)-1)
                fill = self.settings.dic[colour_names[colour_index]]
            elif self.settings.dic['draw_style'] == 'type':
                if coords[0] < coords[2]:
                    fill = self.settings.dic['Colour_2']
                else:
                    fill = self.settings.dic['Colour_3']
            elif self.settings.dic['draw_style'] == 'outline':
                fill = self.settings.dic['Colour_1']

            canvas_id = self.board.create_line(coords,
                                               width=self.line_width,
                                               fill=fill)
            self.triangle_number += 1
            self.master.after(self.delay, self.animate_ls)

        return

    def animate_rh(self):
        if self.triangle_number < self.triangle_count:
            triangle = self.triangles[self.triangle_number]
            coords = self.get_coords(triangle, 2)
            self.draw_shape(coords, None, triangle.type)
            self.triangle_number += 1
            self.master.after(self.delay, self.animate_rh)
        return

    def animate_r(self):
        if self.triangle_number < self.triangle_count:
            triangle = self.triangles[self.triangle_number]
            coords = self.get_coords(triangle, 1)
            self.draw_shape(coords, None, triangle.type)
            self.triangle_number += 1
            self.master.after(self.delay, self.animate_r)
        return

    def animate_c(self):
        if self.chair_number < self.chair_count:
            chair = self.chairs[self.chair_number]
            coords = self.get_coords(chair, 3)
            self.draw_shape(coords, chair.orientation,
                            self.chair_number % 4 + 1)
            self.chair_number += 1
            self.master.after(self.delay, self.animate_c)
        return

    def get_coords(self, shape, shape_type):
        """
        Convert complex vertices to real coordinates
        for triangles, rhombs and chairs
        """
        coords = []
        if shape_type == 3:
            # chair
            coords.append(shape.vertex_a.real)
            coords.append(shape.vertex_a.imag)
            coords.append(shape.vertex_b.real)
            coords.append(shape.vertex_b.imag)
            coords.append(shape.vertex_c.real)
            coords.append(shape.vertex_c.imag)
            coords.append(shape.vertex_d.real)
            coords.append(shape.vertex_d.imag)
            coords.append(shape.vertex_e.real)
            coords.append(shape.vertex_e.imag)
            coords.append(shape.vertex_f.real)
            coords.append(shape.vertex_f.imag)
        elif shape_type == 2:
            # rhomb
            coords.append(shape.vertex_a.real)
            coords.append(shape.vertex_a.imag)
            coords.append(shape.vertex_b.real)
            coords.append(shape.vertex_b.imag)
            coords.append(shape.vertex_d.real)
            coords.append(shape.vertex_d.imag)
            coords.append(shape.vertex_c.real)
            coords.append(shape.vertex_c.imag)
        elif shape_type == 1:
            # triangle
            coords.append(shape.vertex_a.real)
            coords.append(shape.vertex_a.imag)
            coords.append(shape.vertex_b.real)
            coords.append(shape.vertex_b.imag)
            coords.append(shape.vertex_c.real)
            coords.append(shape.vertex_c.imag)
        return coords

    def draw_shape(self, coords, orientation, shape_type):
        outline = self.settings.dic['Colour_9']
        # set default fill
        fill = self.settings.dic['Colour_1']
        if self.settings.dic['draw_style'] == 'outline':
            # outline only
            fill = self.settings.dic['Colour_1']
        elif self.settings.dic['draw_style'] == 'orientation':
            # orientation dependent
            if orientation == 1:
                fill = self.settings.dic['Colour_2']
            elif orientation == 2:
                fill = self.settings.dic['Colour_3']
            elif orientation == 3:
                fill = self.settings.dic['Colour_4']
            elif orientation == 4:
                fill = self.settings.dic['Colour_5']
        elif self.settings.dic['draw_style'] == 'type':
            # char_type dependent
            if shape_type == 1:
                fill = self.settings.dic['Colour_2']
            elif shape_type == 2:
                fill = self.settings.dic['Colour_3']
            elif shape_type == 3:
                fill = self.settings.dic['Colour_4']
            elif shape_type == 4:
                fill = self.settings.dic['Colour_5']
            elif shape_type == 5:
                fill = self.settings.dic['Colour_6']
        elif self.settings.dic['draw_style'] == 'random':
            # random
            colour_names = [
                'Colour_1',
                'Colour_2',
                'Colour_3',
                'Colour_4',
                'Colour_5',
                'Colour_6',
                'Colour_7',
                'Colour_8',
                'Colour_9']
            colour_index = randint(0, len(colour_names)-1)
            fill = self.settings.dic[colour_names[colour_index]]
        else:
            fill = self.settings.dic['Colour_1']

        if self.settings.dic['animation'] == 'On':
            for _ in coords:
                self.board.create_polygon(coords, fill=fill, outline=outline)
        else:
            for _ in coords:
                self.draw.polygon(coords, fill=fill, outline=outline)


class BoundObjects(object):
    """
    Manage our own bindings.
    Tell them how to behave but let the controller control.
    0 = pick up tile
    1 = move tile
    2 = release tile
    3 = rotate tile
    4 = enhance ghost
    """
    def __init__(self, widget):
        self.widget = widget
        self.events = [('<ButtonPress-1>', 0),
                       ('<B1-Motion>', 1),
                       ('<ButtonRelease-1>', 2),
                       ('<ButtonRelease-3>', 3),
                       ('<ButtonRelease-2>', 4)]
        self.func = None

    def add_callback(self, func):
        self.func = func

    def add_bindings(self):
        tag = 'tile'
        for events in self.events[0:4]:
            event = events[0]
            event_code = events[1]
            self.add_binding(tag, event, event_code)
        events = self.events[4]
        event = events[0]
        event_code = events[1]
        tag = 'ghost'
        self.add_binding(tag, event, event_code)

    def add_binding(self, tag, event, event_code):
        def handler(event, self=self, event_code=event_code):
            return self.despatcher(event_code, event)
        self.widget.tag_bind(tag, event, handler)

    def despatcher(self, event_code, event):
        self.func(event_code, event)

class Board(tk.Canvas):
    def __init__(self, parent, settings):
        self.parent = parent
        self.settings = settings
        self.datum = None
        self.photoimage = None
        self.ghost = None
        self.id_dic = {}
        self.make_empty_board()

    def make_empty_board(self):
        tk.Canvas.__init__(self, self.parent, width=Sgg.canvas_width,
                           height=Sgg.canvas_height,
                           bg=self.settings.dic['Table_Colour'])
        self.grid(row=0, column=0, sticky='NSEW')
        self.photoimage = None
        self.make_stretchy()
        self.update_idletasks()

    def make_puzzle(self):
        """
        make board with scattered tiles and ghost
        """
        self.id_dic = {}
        self.photoimage = None
        canvas_id = self.create_image(Sgg.ghost_origin.x,
                                      Sgg.ghost_origin.y,
                                      anchor='nw',
                                      image=self.ghost,
                                      tags="ghost")

        # put pieces between ghost and edge
        margin = int(Sgg.tile_size / 2.0)
        if Sgg.canvas_width - Sgg.ghost_width < Sgg.tile_size \
                or Sgg.canvas_height - Sgg.ghost_height < Sgg.tile_size:
            margin = 0
        inside_1 = Sgg.ghost_origin.x + Sgg.ghost_width
        inside_2 = Sgg.canvas_width - margin
        inside_3 = Sgg.ghost_origin.y + Sgg.ghost_height
        inside_4 = Sgg.canvas_height - margin
        max_tries = 100
        for datum in self.datum:
            tries = 0
            while True:
                tries += 1
                x = randrange(0, Sgg.canvas_width)
                y = randrange(0, Sgg.canvas_height)
                if (x < Sgg.ghost_origin.x and x > margin) or\
                   (x > inside_1 and x < inside_2) or\
                   (y < Sgg.ghost_origin.y and y > margin) or\
                   (y > inside_3 and y < inside_4):
                    break
                elif tries > max_tries:
                    break
            if self.settings.dic['Rotation'] == 'On':
                orientation = randrange(0, 4)
                datum.rotate(orientation)
            tile = datum.photo
            canvas_id = self.create_image(x, y, image=tile,
                                          tags="tile")
            datum.canvas_id = canvas_id
            bbox = self.bbox(canvas_id)
            datum.now = Vec2D(bbox[0], bbox[1])
        self.make_stretchy()
        self.update_idletasks()

    def make_stretchy(self):
        """
        Do the magic required to make the canvas stretchy
        """
        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)
        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)


class View(ViewB):
    """
    Stick blobs on the screen and respond to callbacks
    """

    def __init__(self, master, settings):
        ViewB.__init__(self, master, settings)
        self.datum = None
        self.ghost = None
        self.balls = None
        self.boids = None
        self.board_frame = None
        self.image_dic = {}
        self.make_image_dic()
        self.image_dic['start'] = [self.images.start_jigsaw, 0, True]
        self.do_layout(self)

    def make_image_dic(self):
        """
        image, button_index, start
        """
        dic = {}
        dic['start'] = [self.images.start_jigsaw, 0, True]
        dic['hint'] = [self.images.hint, 1, True]
        dic['hint_on'] = [self.images.hint_on, 1, False]
        dic['show_solution'] = [self.images.smiley, 2, True]
        dic['length'] = [None, 3, False]
        self.image_dic = dic

    def do_layout(self, root):
        bg = self.settings.green
        self.outer_frame = tk.Frame(root, bg=bg)
        self.outer_frame.grid(sticky='NSEW')
        bg = self.settings.green
        self.control_frame = tk.Frame(self.outer_frame,
                                      bd=2, bg=bg, relief='raised')
        self.control_frame.grid(row=0, column=0, sticky='EW')
        self.menu_bar = tk.Frame(self.control_frame, relief='raised',
                                 bd=1, bg=bg)
        self.menu_bar.grid(row=0, column=0, sticky='W')
        self.menu = Menu(self.settings, self.menu_bar, self.images.file)
        self.toolbar = Toolbar(self.settings, self.image_dic,
                               self.control_frame)
        self.toolbar.grid(row=0, column=1, sticky='W')

        self.number_frame = tk.Frame(self.control_frame, relief='raised',
                                     bg=bg)
        self.number_frame.grid(row=0, column=2, sticky='NS')
        self.clock_box = ClockBox(self, self.number_frame)
        self.clock_box.grid(row=0, column=0, sticky='NSEW')
        self.top_frame = tk.Frame(self.outer_frame, relief='flat',
                                  bd=1, bg=bg)

        self.top_frame.grid(row=1, column=0, sticky='NSEW')
        self.board_frame = tk.Frame(self.top_frame, relief='raised',
                                    bd=2, bg=bg)
        self.board_frame.grid(row=0, column=0, sticky='NSEW')

    def new_game(self, datum, ghost):
        self.clock_init()
        self.clock_restart()
        self.datum = datum
        self.ghost = ghost
        self.board.datum = self.datum
        self.board.ghost = self.ghost
        self.board.make_puzzle()

    def make_stretchy(self):
        """
        Do the magic required to make the canvas stretchy
        """
        top = self.winfo_toplevel()
        top.rowconfigure(0, weight=1)
        top.columnconfigure(0, weight=1)
        self.outer_frame.rowconfigure(0, weight=1)
        self.outer_frame.rowconfigure(1, weight=100)
        self.outer_frame.columnconfigure(0, weight=1)
        self.top_frame.rowconfigure(0, weight=1)
        self.top_frame.columnconfigure(0, weight=1)
        self.board_frame.rowconfigure(0, weight=1)
        self.board_frame.columnconfigure(0, weight=1)

    def display_ghost(self, ghost):
        self.board.delete("ghost")
        photo = ImageTk.PhotoImage(ghost)
        self.ghost = photo
        canvas_id = self.board.create_image(Sgg.ghost_origin.x,
                                            Sgg.ghost_origin.y,
                                            anchor='nw',
                                            image=self.ghost,
                                            tags="ghost")
        self.board.tag_lower(canvas_id)

    def make_empty_board(self):
        """
        make board (stretchy)
        """
        self.board = Board(self.board_frame, self.settings)
        self.make_stretchy()
        self.board_frame.update_idletasks()

    def draw_balls(self):
        for ball in self.balls.balls:
            inc_x = ball.incs.x
            inc_y = ball.incs.y
            self.board.move(ball.tag, inc_x, inc_y)

    def draw_boids(self):
        for boid in self.boids:
            inc_x = boid.incs.x
            inc_y = boid.incs.y
            if boid.flying:
                self.board.tag_raise(boid.tag)
            self.board.move(boid.tag, inc_x, inc_y)

    def sg_open_file(self, write, title):
        """
        Use gui to open and file for reading or writing
        """
        init_dir = PzlG.pictures
        filetypes = [("Image files",
                      "*.JPG *jpg *.PNG *.png *.JPEG *.jpeg *.gif *.GIF")]
        this_file = OpenFileI(self, init_dir, filetypes, write, title)
        # note this version returns filename too
        return this_file.my_file, this_file.filename


class Datum(object):
    """
    Data item for a square
    """
    def __init__(self, my_index, x_now, y_now, image):
        self.neighbours = [None, None, None, None]
        self.index = my_index
        self.now = Vec2D(x_now, y_now)
        self.origin = Vec2D(x_now, y_now)
        self.inc = Vec2D(None, None)
        self.orientation = 0
        self.image = image
        self.photo = ImageTk.PhotoImage(self.image)
        self.get_neighbours()
        self.tile_set = set([my_index])
        self.canvas_id = None

    def rotate(self, turns):
        """
        Rotate image and photo by turns multiples of 90 degrees
        Only PIL image can be rotated so we have to replace photo.
        """
        angle = 90 * turns
        self.image = self.image.rotate(angle)
        self.photo = ImageTk.PhotoImage(self.image)
        self.orientation += turns
        self.orientation %= 4

    def reset(self):
        self.neighbours = [None, None, None, None]

    def get_neighbours(self):
        """
        Get indexes of neighbouring squares
        """
        self.neighbours = [None, None, None, None]
        grid_count = Sgg.tiles_across * Sgg.tiles_down

        # up
        neighbour = self.index - Sgg.tiles_across
        if neighbour > -1:
            self.neighbours[0] = neighbour

        # right
        neighbour = self.index + 1
        if neighbour % Sgg.tiles_across != 0:
            self.neighbours[1] = neighbour

        # down
        neighbour = self.index + Sgg.tiles_across
        if neighbour < grid_count:
            self.neighbours[2] = neighbour

        # left
        neighbour = self.index - 1
        if self.index % Sgg.tiles_across != 0:
            self.neighbours[3] = neighbour


class Boid2D(object):
    def __init__(self, position, home, tag):
        self.position = position
        self.home = home
        self.velocity = Vec2D(1.0, 1.0)
        self.incs = Vec2D(0.0, 0.0)
        self.flying = True
        self.tag = tag


class Flock2D(object):
    """
    https://en.wikipedia.org/wiki/Boids
    separation: steer to avoid crowding local flockmates
    alignment: steer towards the average heading of local flockmates
    cohesion: steer to move toward the average position of local flockmates
    Used the pseudocode provided by Conrad Parker
    http://www.kfish.org/boids/pseudocode.html
    """

    def __init__(self, datum):
        self.datum = datum
        x_max = Sgg.canvas_width
        y_max = Sgg.canvas_height
        velocity_limit = 50
        cohesion_multiplier = 1.0
        separation_multiplier = 1.0
        alignment_multiplier = 1.0
        homing_multiplier = 0
        self.x_min = 0
        self.x_max = x_max
        self.y_min = 0
        self.velocity_limit = velocity_limit
        self.y_max = y_max
        self.cohesion_multiplier = cohesion_multiplier
        self.separation_multiplier = separation_multiplier
        self.alignment_multiplier = alignment_multiplier
        self.homing_multiplier = homing_multiplier
        self.toler = 1.
        self.toler_inc = 0.1
        self.homing_inc = 0.00000
        self.n_boids = len(datum)
        self.velocity_limit = velocity_limit
        self.boids_active = False
        self.boids = []
        self.make_boids()

    def rules(self, this_boid):
        """
        separation: steer to avoid crowding local flockmates
        alignment: steer towards the average heading of local flockmates
        cohesion: steer to move toward the average position of local flockmates
        """
        cohesion = Vec2D(0.0, 0.0)
        separation = Vec2D(0.0, 0.0)
        alignment = Vec2D(0.0, 0.0)
        homing = Vec2D(800.0, 500.0)
        for boid in self.boids:
            if boid != this_boid:
                cohesion += boid.position
                alignment += boid.velocity
                if abs(boid.position - this_boid.position) < 10:
                    separation -= boid.position - this_boid.position
            else:
                homing -= boid.position
        cohesion /= (self.n_boids - 1)
        cohesion -= this_boid.position
        cohesion /= 100
        alignment /= (self.n_boids - 1)
        alignment -= this_boid.velocity / 8
        return cohesion, separation, alignment, homing

    def check_velocity(self, boid):
        """
        Slow the boid down
        """

        if abs(boid.velocity) > self.velocity_limit:
            boid.velocity = boid.velocity / abs(boid.velocity)
            boid.velocity *= self.velocity_limit

    def check_home(self, boid):
        if boid.flying:
            if abs(boid.position - boid.home) < self.toler:
                if boid.position.x < boid.home.x:
                    boid.incs.x = boid.home.x - boid.position.x
                else:
                    boid.incs.x = boid.home.x - boid.position.x
                if boid.position.y < boid.home.y:
                    boid.incs.y = boid.home.y - boid.position.y
                else:
                    boid.incs.y = boid.home.y - boid.position.y
                boid.flying = False

    def check_position(self, boid):
        """
        By slowing the boid when out of range
        make it return.
        """

        velocity = Vec2D(0.0, 0.0)
        if boid.position.x < self.x_min:
            velocity.x = 10
        elif boid.position.x > self.x_max:
            velocity.x = -10
        if boid.position.y < self.y_min:
            velocity.y = 10
        elif boid.position.y > self.y_max:
            velocity.y = -10
        return velocity

    def step_boids(self):
        self.homing_multiplier += self.homing_inc
        self.toler += self.toler_inc
        self.boids_active = False
        for boid in self.boids:
            if boid.flying:
                self.check_home(boid)
                self.boids_active = True
            else:
                boid.position = boid.home
                boid.incs = Vec2D(0, 0)
                boid.velocity = Vec2D(0, 0)
                self.toler_inc += 0.0001

            if boid.flying:
                cohesion, separation, alignment, homing = self.rules(boid)
                cohesion *= self.cohesion_multiplier
                separation *= self.separation_multiplier
                alignment *= self.alignment_multiplier
                homing *= self.homing_multiplier
                boid.velocity += cohesion + separation + alignment
                self.check_velocity(boid)
                position = boid.position
                boid.position += boid.velocity + homing
                boid.incs = boid.position - position
                position = self.check_position(boid)
                boid.velocity += position

    def datum_to_boid(self, datum):
        tag = datum.canvas_id
        return Boid2D(datum.now, datum.origin, tag)

    def make_boids(self):
        self.boids = []
        for datum in self.datum:
            self.boids.append(self.datum_to_boid(datum))

    def explode_boids(self):
        centre = Vec2D(Sgg.canvas_width / 2., Sgg.canvas_height / 2.)
        restore_force = 50000.0
        for boid in self.boids:
            f = 1.0 / (boid.position.x - centre.x + 0.01)
            boid.velocity.x += restore_force * f
            f = 2.0 / (boid.position.y - centre.y + 0.01)
            boid.velocity.y += restore_force * f


class Ball(object):

    def __init__(self, position, orig_position, tag):
        self.position = position
        self.orig_position = orig_position
        self.velocity = Vec2D(0.0, 0.0)
        self.incs = Vec2D(0.0, 0.0)
        self.radius = 20
        self.tag = tag


class Balls(object):

    def __init__(self, datum):
        x_max = Sgg.canvas_width
        y_max = Sgg.canvas_height
        self.balls = None
        self.datum = datum
        self.x_min = 0
        self.x_max = x_max
        self.y_min = 0
        self.y_max = y_max
        self.canvas_width = x_max
        self.canvas_height = y_max
        self.frame_interval = 20
        self.radius = 10
        self.radius = 20
        self.mouse_x = 0
        self.mouse_y = 0
        self.frame_interval = 20
        self.steps = 0
        self.balls_active = False

    def datum_to_ball(self, datum):
        tag = datum.canvas_id
        return Ball(datum.now, datum.origin, tag)

    def make_balls(self):
        self.balls = []
        for datum in self.datum:
            self.balls.append(self.datum_to_ball(datum))

    def explode_balls(self):
        self.balls_active = True
        self.steps = 0
        centre = Vec2D(Sgg.canvas_width / 2., Sgg.canvas_height / 2.)
        restore_force = 0.02 * self.frame_interval
        restore_force = 50.0 * self.frame_interval
        for ball in self.balls:
            f = 1.0 / (ball.position.x - centre.x + 0.01)
            ball.velocity.x += restore_force * f

            f = 1.0 / (ball.position.y - centre.y + 0.01)
            ball.velocity.y += restore_force * f

    def is_finished(self):
        ret = False
        self.steps += 1
        dist = 0.0
        for ball in self.balls:
            dist += abs(ball.position - ball.orig_position)
        if dist < 1.0 or self.steps > 3000:
            self.balls_active = False
        return ret

    def step_balls(self):
        def restore(ball):
            restore_force = 0.002 * self.frame_interval
            if ball.position.x > ball.orig_position.x:
                ball.velocity.x -= restore_force
            else:
                ball.velocity.x += restore_force

            if ball.position.y > ball.orig_position.y:
                ball.velocity.y -= restore_force
            else:
                ball.velocity.y += restore_force

        def friction(velocity):
            friction_fact = 0.0005 * self.frame_interval
            if velocity > 0:
                velocity -= friction_fact
            elif velocity < 0:
                velocity += friction_fact
            return velocity

        def bounce(ball_position, ball_velocity, limits):
            collision_damper = 0.3
            force = 1 - collision_damper
            if ball_position < limits[0]:
                ball_position = self.radius - 2
                ball_velocity *= -1
                ball_velocity *= force
            if ball_position > limits[1]:
                ball_position = limits[1] - self.radius - 2
                ball_velocity *= -1
                ball_velocity *= force
            return ball_position, ball_velocity

        self.is_finished()

        for ball in self.balls:

            # set ball position based on velocity

            incs = ball.position
            ball.position += ball.velocity

            # restore force

            restore(ball)

            # friction

            ball.velocity.x = friction(ball.velocity.x)
            ball.velocity.y = friction(ball.velocity.y)

            # edge condition

            limits = (0, self.canvas_width - self.radius - 2)
            ball.position.x, ball.velocity.x = bounce(ball.position.x,
                                                      ball.velocity.x, limits)
            limits = (0, self.canvas_height - self.radius - 2)
            ball.position.y, ball.velocity.y = bounce(ball.position.y,
                                                      ball.velocity.y, limits)
            ball.incs = ball.position - incs


class Model(object):
    """
    Datums and dics plus board from View
    """
    def __init__(self):
        self.board = None
        self.datum = []
        self.canvas_move_start = Vec2D(None, None)
        self.move_origin = Vec2D(None, None)
        self.id_dic = {}
        self.index_dic = {}
        self.zero_canvas_move_start()
        self.moving_tile_set = None
        self.hint_status = Sgg.GIVE_HINT
        self.finished = False
        self.finished_callback = False
        self.balls = None
        self.flock = None

    def add_callback(self, func):
        self.finished_callback = func

    def is_finished(self):
        ret = True
        for datum in self.datum:
            neighbours = datum.neighbours
            for neighbour in neighbours:
                if neighbour is not None:
                    ret = False
                    break
            if not ret:
                break
        self.finished = ret
        self.finished_callback(ret)

    def make_id_dic(self):
        """
        Make a dic equating canvas_id to datum.index
        Given a canvas id we get a datum index
        """
        id_dic = {}
        for datum in self.datum:
            id_dic[datum.canvas_id] = datum.index
        self.id_dic = id_dic

    def make_index_dic(self):
        """
        Make a dic equating datum.index to canvas_id
        Given a datum.index we get a canvas id
        """
        id_dic = {}
        for datum in self.datum:
            id_dic[datum.index] = datum.canvas_id
        self.index_dic = id_dic

    def zero_canvas_move_start(self):
        self.canvas_move_start = Vec2D(0, 0)

    def rotate(self, canvas_id_in):
        """
        Rotate a tile by 90 degrees
        and update its coordinates accordingly.
        """
        datum_index = self.id_dic[canvas_id_in]
        datum = self.datum[datum_index]
        datum.rotate(1)
        canvas_id = self.board.create_image(int(datum.now.x
                                                + Sgg.tile_size/2.0),
                                            int(datum.now.y
                                                + Sgg.tile_size/2.0),
                                            image=datum.photo, tags="tile")
        datum.canvas_id = canvas_id
        self.id_dic[datum.canvas_id] = datum.index
        self.index_dic[datum.index] = datum.canvas_id
        self.board.delete(canvas_id_in)

    def orient(self, datum_index):
        """
        Rotate datum_index until it is in its original orientation
        """
        datum = self.datum[datum_index]
        canvas_id_in = datum.canvas_id
        if datum.orientation != 0:
            while datum.orientation % 4 != 0:
                datum.rotate(1)

            canvas_id = self.board.create_image(int(datum.now.x
                                                    + Sgg.tile_size/2.0),
                                                int(datum.now.y
                                                    + Sgg.tile_size/2.0),
                                                image=datum.photo, tags="tile")
            datum.canvas_id = canvas_id
            self.id_dic[datum.canvas_id] = datum.index
            self.index_dic[datum.index] = datum.canvas_id
            self.board.delete(canvas_id_in)

    def start_drag(self, canvas_id, new_x, new_y):
        """
        Initialise a drag event
        """
        drag_datum_index = self.id_dic[canvas_id]
        self.canvas_move_start = Vec2D(new_x, new_y)
        self.move_origin = Vec2D(new_x, new_y)
        self.moving_tile_set = self.datum[drag_datum_index].tile_set

    def end_drag(self, new_x, new_y):
        """
        Finalise a drag event by updating the coordinates
        """
        new = Vec2D(new_x, new_y)
        distance = self.move_origin - new
        for datum_index in self.moving_tile_set:
            datum = self.datum[datum_index]
            datum.now -= distance

    def raise_moving_tile_set(self):
        for datum_index in self.moving_tile_set:
            canvas_id = self.index_dic[datum_index]
            self.board.tag_raise(canvas_id)

    def join_datum(self, touchers):
        """
        datum_0 was moved and found to be touching datum_1
        We form the union of their tile_sets and then update the tile_sets
        for all those tiles in the tile_set so that
        tile_sets are shared by the tiles involved
        Initially they are set to themselves but when a link is found
        their tile_sets are combined
        Also need to remove neighbours
        """
        datum_0_index = touchers[0]
        datum_1_index = touchers[1]
        if datum_1_index is not None:
            datum_0 = self.datum[datum_0_index]
            datum_1 = self.datum[datum_1_index]
            tile_set = datum_0.tile_set.union(datum_1.tile_set)
            for datum_index in tile_set:
                self.datum[datum_index].tile_set = tile_set

            for datum_index in tile_set:
                neighbours = self.datum[datum_index].neighbours
                for direction in range(4):
                    neighbour = neighbours[direction]
                    if neighbour in tile_set:
                        neighbours[direction] = None

        self.is_finished()

    def do_balls(self):
        self.balls = Balls(self.datum)
        self.balls.make_balls()
        self.balls.explode_balls()

    def step_balls(self):
        self.balls.step_balls()

    def do_boids(self):
        self.flock = Flock2D(self.datum)
        self.flock.explode_boids()
        self.flock.boids_active = True

    def step_boids(self):
        self.flock.step_boids()


def new_position(datum_0, datum):
    datum_index = datum_0.index
    neighbours = datum.neighbours
    new = None
    delta = None
    if datum_index == neighbours[0]:
        new = Vec2D(0, -Sgg.tile_size)
    elif datum_index == neighbours[1]:
        new = Vec2D(Sgg.tile_size, 0)
    elif datum_index == neighbours[2]:
        new = Vec2D(0, Sgg.tile_size)
    elif datum_index == neighbours[3]:
        new = Vec2D(-Sgg.tile_size, 0)
    if new is not None:
        new += datum.now
        delta = new - datum_0.now
    return delta


class Controller(object):
    """
    Set up and control
    """

    def __init__(self, root):
        self.enhance_factors = [0.1, 0.2, 0.3, 0.4, 0.5,
                                0.6, 0.7, 0.8, 0.9, 1.0, 0.0]
        self.enhance_index = 0
        self.master = root
        self.flasher = None
        self.settings = Settings()
        self.set_piece_count()
        self.model = None
        self.view = View(root, self.settings)
        self.bound_objects = None
        screen_shot_state = False
        self.screen_shot = ScreenShot(PzlG.app_name, 'dump_',
                                      screen_shot_state)
        self.finished = True
        self.max_steps = 100
        self.steps = 0
        self.default_image = None
        self.ghost = None
        self.smallest_tile_set = None
        self.largest_tile_set = None
        self.animation_finished = True
        self.view.make_empty_board()
        self.set_bound_objects()
        self.add_callbacks()
        self.view.game_over()
        # the Rowe variable
        self.finger_trouble = False
        self.demo_number = 0
        self.demo_max = 100
        self.demo_id = None

    def service_menu(self, *args):
        """
        Respond to menu choices
        """
        strargs = ''
        for item in args:
            strargs += ' ' + item
        PzlG.logger.logger.info("service_menu " + strargs)
        name = args[0]
        if name == 'get_picture':
            self.get_picture()
        elif name == 'Pattern':
            self.get_pattern('')
        elif name == 'l-systems':
            choice = args[1]
            self.get_pattern(choice)
        elif name == 'set_divisions':
            self.set_divisions()
        elif name == 'animation':
            self.animation()
        elif name == 'set_piece_count':
            self.set_piece_count()
        elif name == 'set_max_ani_time':
            self.set_animation_delay()
        elif name == 'set_colour':
            choice = args[1]
            self.set_colour(choice)
        elif name == 'save_settings':
            self.settings.preferences.save()
        elif name == 'about':
            self.view.about()
        elif name == 'demo':
            if self.demo_id is not None:
                self.master.after_cancel(self.demo_id)
                self.demo_id = None
                self.settings.dic['animation'] = 'Off'
                self.demo_number = self.demo_max + 1
            else:
                self.do_demo()
                self.demo_id = None
        elif name == 'help':
            self.view.help()

    def service_toolbar(self, button_index, mouse_index):
        PzlG.logger.logger.info("service_toolbar " +
                                str(button_index) +
                                ' ' +
                                str(mouse_index))

        if mouse_index == 2:
            self.view.do_tooltip(button_index, None)

        if self.settings.dic['animation'] == 'Off':
            if mouse_index == 1:
                if button_index == 0:
                    if self.default_image is not None:
                        self.start_game()
                if not self.finished:
                    if button_index == 1:
                        self.give_hint()
                        self.screen_shot.snap()
                    elif button_index == 2:
                        self.show_solution()
                        self.screen_shot.snap()

    def event_handler(self, event_index, event):
        """
        Tiles are moved by button-press, motion, button-release.
        On button press the canvas will give us the nearest tile id
        but we need to work using tile_sets.
        Datum store the x,y of the tile corner.
        Canvas events return the x,y of the mouse.
        When we start to drag the canvas will tell us the nearest tile.
        We record the initial mouse x,y as model.move_origin for Datums
        and self.model.canvas_move_start. The latter is reset each time
        we invoke the canvas.move function on a tile so we can tell the
        canvas how far to move the image, but the former is only used to
        work out the total distance moved when the move is completed.
        At this point we update the Datum x,y.

        self.func = None
        self.events = [('<ButtonPress-1>', 0),
                       ('<B1-Motion>', 1),
                       ('<ButtonRelease-1>', 2),
                       ('<ButtonRelease-3>', 3),
                       ('<ButtonRelease-2>', 4)]

        """
        # force hint status to simplify hints
        self.model.hint_status = Sgg.GIVE_HINT
        self.view.hint_off()

        if event_index == 0:
            # button press
            # start drag of an object
            # record the tile and its location
            canvas_id = self.view.board.find_closest(event.x, event.y)[0]

            # sometimes this returns the ghost id!
            # so check it's a tile
            try:
                datum_index = self.model.id_dic[canvas_id]
                self.model.start_drag(canvas_id, event.x, event.y)
                self.model.raise_moving_tile_set()
                self.finger_trouble = False
            except KeyError:
                self.finger_trouble = True
        elif event_index == 1 and not self.finger_trouble:
            # motion
            # here we just manage the movement and keep track
            # of the new coords.
            # Only when the movement ends with mouse release
            # do we update the tile positions.
            # how much has tile moved?
            delta = Vec2D(event.x, event.y) - self.model.canvas_move_start
            # tell the canvas to move the touching images
            # the appropriate amount
            index_dic = self.model.index_dic
            tile_set = self.model.moving_tile_set
            for datum_index in tile_set:
                canvas_id = index_dic[datum_index]
                self.view.board.move(canvas_id, delta.x, delta.y)

            # record the new canvas position
            self.model.canvas_move_start = Vec2D(event.x, event.y)
        elif event_index == 2 and not self.finger_trouble:
            # button 1 release
            # end drag of an object
            # so now we update the tile coordinates
            # reset the drag data
            self.model.zero_canvas_move_start()
            self.model.end_drag(event.x, event.y)
            touchers = self.are_touching()
            if touchers is not None:
                self.fit(touchers)
                self.model.join_datum(touchers)
        elif event_index == 3:
            if self.settings.dic['Rotation'] == 'On':
                canvas_id = self.view.board.find_closest(event.x, event.y)[0]
                # sometimes this returns the ghost id!
                # so check it's a tile
                try:
                    datum_index = self.model.id_dic[canvas_id]
                    tile_set = self.model.datum[datum_index].tile_set
                    # only rotate if not attached
                    # because all attached are in the correct orientation
                    if len(tile_set) == 1:
                        self.model.rotate(canvas_id)
                except KeyError:
                    pass
        elif event_index == 4:
            self.play_ghost()

    def start_game(self):
        self.model = Model()
        self.add_model_callback()
        self.make_pieces()
        self.view.new_game(self.model.datum, self.ghost)
        self.flasher = Flasher(self.view.clock.box.label,
                               self.master, self.settings)
        self.model.board = self.view.board
        self.model.make_id_dic()
        self.model.make_index_dic()
        self.set_bound_objects()
        self.add_callbacks()
        self.view.game_on()
        self.finished = False
        self.model.hint_status = Sgg.GIVE_HINT
        self.finger_trouble = False

    def animate_d(self):
        draw_style = ['outline', 'random', 'orientation', 'type']
        pattern_names = ['Pinwheel', 'Danzer', 'Robinson', 'Rhombs', 'Chair']
        l_names = [
            'Koch a', 'Koch b', 'Koch c', 'Koch d', 'Koch e',
            'Koch f', 'Islands and Lakes',
            'Quadratic Koch island',
            'Quadratic snowflake', 'Dragon', 'Sierpinski gasket',
            'Plant 1', 'Plant 2', 'Plant 3',
            'Plant 4', 'Plant 5', 'Plant 6', 'Sierpinski carpet',
            'Penrose']
        if self.demo_number < self.demo_max:
            choice = randint(0, 1)
            if choice == 0:
                name = ''
                choice = pattern_names[randint(0, len(pattern_names) - 1)]
                self.settings.dic['Pattern'] = choice
                max_time = self.settings.max_ani_time * 3000
            else:
                name = l_names[randint(0, len(l_names) - 1)]
                self.settings.dic['l-systems'] = name
                max_time = self.settings.max_ani_time * 4000
            choice = draw_style[randint(0, len(draw_style) - 1)]
            self.settings.dic['draw_style'] = choice
            self.get_pattern(name)
            self.demo_number += 1
            self.demo_id = self.master.after(max_time, self.animate_d)

    def do_demo(self):
        self.settings.dic['animation'] = 'On'
        self.settings.dic['Table_Colour'] = '#2c2c2c'
        self.demo_number = 0
        self.animate_d()

    def set_bound_objects(self):
        self.bound_objects = BoundObjects(self.view.board)
        self.bound_objects.add_callback(self.event_handler)
        self.bound_objects.add_bindings()

    def add_callbacks(self):
        self.view.add_menu_callback(self.service_menu)
        self.view.add_toolbar_callback(self.service_toolbar)

    def add_model_callback(self):
        self.model.add_callback(self.service_model)

    def service_model(self, finished):
        self.finished = finished
        if self.finished:
            self.view.game_over()
            self.default_image = None
            self.view.clock_stop()
            self.flasher.flash()

    def animate_hint(self):
        """
        Move all hint tile_set
        """
        if self.steps < self.max_steps:
            for datum_index in self.model.moving_tile_set:
                datum = self.model.datum[datum_index]
                canvas_id = self.model.index_dic[datum_index]
                self.view.board.move(canvas_id, datum.inc.x, datum.inc.y)
                # update coords
                bbox = self.view.board.bbox(canvas_id)
                datum.now.x = bbox[0]
                datum.now.y = bbox[1]
            self.steps += 1
            self.master.after(10, self.animate_hint)
        else:
            self.animation_finished = True

    def prepare_hint(self):
        """
        Calculate the increments for moving a tile_set
        to the top left of the board
        """
        # make sure they are in correct orientation
        for datum_0_index in self.largest_tile_set:
            self.model.orient(datum_0_index)
        for datum_0_index in self.model.moving_tile_set:
            self.model.orient(datum_0_index)
            datum_0 = self.model.datum[datum_0_index]
            datum_0.inc = datum_0.now / self.max_steps
            datum_0.inc *= -1

    def give_hint(self):
        """
        Find the largest tile_set.
        Find the smallest tile_set which is a neighbour
        Move smallest tile_set to top left
        """

        self.steps = 0
        if self.model.hint_status == Sgg.GIVE_HINT:
            self.view.hint_on()
            self.model.hint_status = Sgg.APPLY_HINT
            largest_tile_set = set([])
            smallest_tile_set = None
            for datum in self.model.datum:
                if len(datum.tile_set) > len(largest_tile_set):
                    largest_tile_set = datum.tile_set
            for datum_index in largest_tile_set:
                datum = self.model.datum[datum_index]
                for neighbour in datum.neighbours:
                    if neighbour is not None:
                        neighbour_datum = self.model.datum[neighbour]
                        if smallest_tile_set is None:
                            smallest_tile_set = neighbour_datum.tile_set
                        elif len(neighbour_datum.tile_set) < \
                                len(smallest_tile_set):
                            smallest_tile_set = neighbour_datum.tile_set
            self.model.moving_tile_set = smallest_tile_set
            self.model.raise_moving_tile_set()
            self.smallest_tile_set = smallest_tile_set
            self.largest_tile_set = largest_tile_set
            self.prepare_hint()
            self.animation_finished = False
            self.animate_hint()
            self.animation_finished = True
        else:
            self.view.hint_off()
            self.model.hint_status = Sgg.GIVE_HINT
            self.apply_hint()

    def prepare_apply_hint(self):
        """
        Calculate the increments for moving smallest_tile_set
        to largest_tile_set.
        First find an adjacent pair.
        """

        for datum_0_index in self.smallest_tile_set:
            datum_0 = self.model.datum[datum_0_index]
            for neighbour in datum_0.neighbours:
                if neighbour is not None:
                    for datum_1_index in self.largest_tile_set:
                        if neighbour == datum_1_index:
                            # neighbour and datum_1_index touch. How?
                            datum_1 = self.model.datum[datum_1_index]
                            delta = new_position(datum_0, datum_1)
        for datum_0_index in self.smallest_tile_set:
            datum_0 = self.model.datum[datum_0_index]
            datum_0.inc = delta / self.max_steps

    def apply_hint(self):
        """
        Move smallest_tile_set to largest_tile_set
        """

        self.prepare_apply_hint()
        self.model.moving_tile_set = self.smallest_tile_set
        self.model.raise_moving_tile_set()
        self.animation_finished = False
        self.steps = 0
        self.animate_hint()

    def make_ghost(self, image):
        """
        return a faint copy of the input image
        """
        factor = 0.2
        enh = ImageEnhance.Contrast(image)
        ghost = enh.enhance(factor)
        return ghost

    def play_ghost(self):
        """
        rotate brightness of the input image
        """

        factor = self.enhance_factors[self.enhance_index]
        self.enhance_index += 1
        self.enhance_index %= len(self.enhance_factors)
        image = self.default_image
        enh = ImageEnhance.Contrast(image)
        ghost = enh.enhance(factor)
        self.view.display_ghost(ghost)

    def fiddle_board_size(self):
        """
        Problem with winfo_width/height not always
        being uptodate
        """
        a = max(Sgg.min_canvas_width,
                self.view.board_frame.winfo_width())
        b = max(Sgg.min_canvas_height,
                self.view.board_frame.winfo_height())
        return a, b

    def get_pattern(self, lsys_name):
        """
        Generate a tiling pattern to make a default image.
        """
        self.view.make_empty_board()

        board_width, board_height = self.fiddle_board_size()
        Sgg.max_ghost_width = board_width
        Sgg.max_ghost_height = board_height
        self.default_image = TilingPattern(
            self.settings, self.master, self.view.board, lsys_name).image
        self.do_scaling()
        image = self.default_image
        Sgg.ghost_origin = Vec2D(0, 0)
        self.view.display_ghost(image)
        self.finished = True
        if self.settings.dic['animation'] == 'Off':
            self.view.game_over()

    def do_scaling(self):
        """
        make square tiles, having scaled image (if necessary)
        """
        board_width, board_height = self.fiddle_board_size()
        image = self.default_image
        full_image = image
        image_width = float(image.size[0])
        image_height = float(image.size[1])
        scale_factor = 1.0
        scaled_width = image_width
        scaled_height = image_height
        if image_width > board_width or image_height > board_height:
            # scaling required
            scale_factor = min(float(board_width) / image_width,
                               float(board_height) / image_height)
            scaled_width = scale_factor * image_width
            scaled_height = scale_factor * image_height
            size = int(scaled_width), int(scaled_height)
            full_image = full_image.resize(size, Image.ANTIALIAS)

        tile_area = scaled_width * scaled_height
        tile_size = int((tile_area / Sgg.pieces) ** 0.5)
        tiles_across = int(scaled_width / tile_size)
        tiles_down = int(scaled_height / tile_size)
        ghost_width = tiles_across * tile_size
        ghost_height = tiles_down * tile_size
        # The final image has size ghost_width, ghost_height

        """
        Even a scaled image won't have an exact number of square tiles
        along its edges so we try to use a rectangle from the centre
        of the image for the final puzzle image.
        """
        dx = scaled_width - ghost_width
        dy = scaled_height - ghost_height
        start_across = int(dx / 2)
        start_down = int(dy / 2)
        box = (start_across, start_down,
               start_across + ghost_width, start_down + ghost_height)

        full_image = full_image.crop(box)
        self.default_image = full_image

        ghost = self.make_ghost(full_image)
        photo = ImageTk.PhotoImage(ghost)
        self.ghost = photo
        Sgg.ghost_width = ghost_width
        Sgg.ghost_height = ghost_height
        Sgg.tile_size = tile_size
        Sgg.tiles_across = tiles_across
        Sgg.tiles_down = tiles_down

    def make_pieces(self):
        full_image = self.default_image
        board_width, board_height = self.fiddle_board_size()
        Sgg.canvas_width = board_width
        Sgg.canvas_height = board_height
        a = int((Sgg.canvas_width - Sgg.ghost_width) / 2.0)
        b = int((Sgg.canvas_height - Sgg.ghost_height) / 2.0)
        Sgg.ghost_origin = Vec2D(a, b)
        left = 0
        upper = 0
        tile_width = Sgg.tile_size
        tile_height = Sgg.tile_size
        right = tile_width
        lower = tile_height
        datum = []
        count = 0
        for row in range(Sgg.tiles_down):
            for _ in range(Sgg.tiles_across):
                x_now = left + Sgg.ghost_origin.x
                y_now = upper + Sgg.ghost_origin.y
                box = (left, upper, right, lower)
                image_tile = full_image.crop(box)
                datum.append(Datum(count, x_now, y_now,
                                   image_tile))
                left += tile_width
                right += tile_width
                count += 1
            left = 0
            right = tile_width
            upper += tile_height
            lower += tile_height
        self.model.datum = datum

    def is_touching(self, datum_index):
        """
        Compare coords of datum against its neighbours
        and return an index of any it is touching.
        If the one it is touching is already touching others
        its tile_set will contain their indexes also and so
        the join function will add datum to this tile_set
        and then it will become the tile_set for all the datums
        in tile_set.
        """
        ret = None
        factor = 4.0
        touching = 1 + int(Sgg.tile_size / factor)
        datum = self.model.datum[datum_index]
        datum_x = datum.now.x
        datum_y = datum.now.y
        for direction in range(4):
            neighbour = datum.neighbours[direction]
            if neighbour is not None and \
               self.model.datum[neighbour].orientation == 0:
                neighbour_datum = self.model.datum[neighbour]
                nd = neighbour_datum.now
                if direction == 0:
                    delta_x = abs(datum_x - nd.x)
                    delta_y = abs(datum_y -
                                  (nd.y + Sgg.tile_size))
                    if delta_x < touching and delta_y < touching:
                        ret = neighbour
                        break
                elif direction == 2:
                    delta_x = abs(datum_x - nd.x)
                    delta_y = abs(datum_y -
                                  (nd.y - Sgg.tile_size))
                    if delta_x < touching and delta_y < touching:
                        ret = neighbour
                        break
                elif direction == 1:
                    delta_y = abs(datum_y - nd.y)
                    delta_x = abs(datum_x -
                                  (nd.x - Sgg.tile_size))
                    if delta_x < touching and delta_y < touching:
                        ret = neighbour
                        break
                elif direction == 3:
                    delta_y = abs(datum_y - nd.y)
                    delta_x = abs(datum_x -
                                  (nd.x + Sgg.tile_size))
                    if delta_x < touching and delta_y < touching:
                        ret = neighbour
                        break
        return ret

    def are_touching(self):
        """
        Button released, so are we touching any tiles?
        Need to compare moving_tile_set's neighbours.
        Also need to check if it is in the correct orientation
        (they are all in the same orientation).
        """
        ret = None
        for datum_index in self.model.moving_tile_set:
            if self.model.datum[datum_index].orientation == 0:
                toucher = self.is_touching(datum_index)
                if toucher is not None:
                    # we have a pair that touch
                    ret = datum_index, toucher
                    break

        return ret

    def fit(self, pair):
        """
        drag_datum_index has just been moved to touch toucher.
        Need to adjust the position of drag_datum_index so that
        it exactly fits with toucher.
        NB if toucher is attached to other tiles they will all
        have already been through this fitting process and will
        already fit perfectly so we only need to fit drag_datum_index
        to toucher.
        However, if drag_datum_index is also attached to other tiles
        we need to move them by the same amount.

        It's more complicated: drag_datum_index is the tile I happen
        to click on but if it is not adjacent to the tile(s) I'm trying
        to fit to the neighbours won't match so the join will not be found!
        All the matching algs need a rethink.


        """

        datum_0_index = pair[0]
        datum_1_index = pair[1]
        datum_0 = self.model.datum[datum_0_index]
        datum_1 = self.model.datum[datum_1_index]
        delta = new_position(datum_0, datum_1)

        # now move drag_datum and its tile_set accordingly

        index_dic = self.model.index_dic
        tile_set = self.model.moving_tile_set
        for datum_index in tile_set:
            # make adjustment
            canvas_id = index_dic[datum_index]
            self.view.board.move(canvas_id, delta.x, delta.y)
            # update coords
            bbox = self.view.board.bbox(canvas_id)
            datum = self.model.datum[datum_index]
            datum.now = Vec2D(bbox[0], bbox[1])

    def stick(self):
        """
        Show solution has just been used so now we tidy up
        and glue the tiles together by making final tiny adjustments
        to their positions and updating the tile sets
        """

        for datum_0_index in range(len(self.model.datum) - 1):
            datum_0 = self.model.datum[datum_0_index]
            for neighbour in datum_0.neighbours:
                if neighbour is not None:
                    datum_1 = self.model.datum[neighbour]
                    touchers = [datum_0_index, datum_1.index]
                    self.model.join_datum(touchers)

    def animate_marry(self):
        """
        Move all datums to their original positions.
        """

        if self.steps < self.max_steps:
            for datum in self.model.datum:
                canvas_id = self.model.index_dic[datum.index]
                self.view.board.move(canvas_id, datum.inc.x, datum.inc.y)
                # update coords
                bbox = self.view.board.bbox(canvas_id)
                datum.now = Vec2D(bbox[0], bbox[1])
            self.steps += 1
            self.screen_shot.snap()
            self.master.after(20, self.animate_marry)
        else:
            self.stick()

    def orient_all(self):
        for datum in self.model.datum:
            self.model.orient(datum.index)

    def prepare_assembly(self):
        """
        Calculate the increments for moving each piece back
        to its original position.
        """
        self.orient_all()
        for datum in self.model.datum:
            datum.inc = (datum.origin - datum.now) / self.max_steps

    def assemble(self):
        """
        Move pieces, in turn, to their original positions
        """
        self.max_steps = 100
        self.steps = 0
        self.prepare_assembly()
        self.animate_marry()

    def do_balls(self):
        self.orient_all()
        self.model.do_balls()
        self.view.balls = self.model.balls
        self.view.draw_balls()
        self.update_balls()

    def update_balls(self):
        if self.model.balls.balls_active:
            self.model.step_balls()
            self.view.draw_balls()
            self.master.after(20, self.update_balls)

    def do_boids(self):
        self.orient_all()
        self.model.do_boids()
        self.view.boids = self.model.flock.boids
        self.view.draw_boids()
        self.update_boids()

    def update_boids(self):
        if self.model.flock.boids_active:
            self.model.step_boids()
            self.view.draw_boids()
            self.master.after(20, self.update_boids)

    def show_solution(self):
        if self.settings.dic['show_solution'] == 'Bouncing':
            self.do_balls()
            self.stick()
        elif self.settings.dic['show_solution'] == 'Flocking':
            self.do_boids()
            self.stick()
        else:
            self.assemble()

        self.view.hint_off()
        self.view.game_over()
        self.view.clock_stop()

    def get_picture(self):
        self.default_image = None
        self.finished = True
        my_file, file_name = self.view.sg_open_file(False, _('Image file'))
        if my_file:
            self.view.make_empty_board()
            self.default_image = Image.open(file_name)
            self.do_scaling()
            image = self.default_image
            Sgg.ghost_origin = Vec2D(0, 0)
            self.view.display_ghost(image)
            # force animation off!
            self.settings.dic['animation'] = 'Off'
            self.view.game_over()

    def set_divisions(self):
        divisions = int(self.settings.dic['set_divisions'])
        self.settings.divisions = divisions

    def set_animation_delay(self):
        self.settings.max_ani_time = int(self.settings.dic['set_max_ani_time'])

    def set_piece_count(self):
        Sgg.pieces = int(self.settings.dic['set_piece_count'])

    def set_colour(self, choice):
        try:
            bg_colour = self.settings.dic[choice]
            result = tkinter.colorchooser.askcolor(bg_colour)
            if result[1] is not None:
                self.settings.dic[choice] = result[1]
        except KeyError:
            PzlG.logger.logger.error("set_colour " + choice)

    def animation(self):
        if self.settings.dic['animation'] == 'On':
            self.view.game_over()


INITIALISE = PzlG()
if __name__ == '__main__':
    try:
        ROOT = tk.Tk()
        ROOT.withdraw()
        APP = Controller(ROOT)
        ROOT.mainloop()
        PzlG.logger.logger.info("Stop program")
    except:
        PzlG.logger.logger.info("Failure!")
        PzlG.logger.logger.exception("Unhandled exception during main")
