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

# Copyright (C) 2020 Rod Engerstad

import random
import Tkinter as tk
import tkColorChooser
import glob
import os

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 Toolbar
from pzl_lib import Menu
from pzl_lib import EntryBox
from pzl_lib import ScoreBox


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

    logger = None

    # max width and height
    MAX_SIZE = 1001


class Settings(SettingsB):

    def __init__(self):
        SettingsB.__init__(self)
        self.max_ani_time = None
        self.dic = {}
        self.dic['pattern_files'] = PzlG.app_data_path + 'RLE'
        self.dic['pattern_name'] = 'Glasses'
        self.dic['set_colour'] = 'Born_Colour'
        self.dic['Table_Colour'] = 'black'
        self.dic['Born_Colour'] = 'yellow'
        self.dic['Survived_Colour'] = 'red'
        self.dic['font_size'] = '1'
        self.dic['max_cells'] = '90000'
        self.dic['max_ticks'] = '50'
        self.dic['cell_size'] = '5'
        self.dic['margin_size'] = '10'
        self.dic['selection_mode'] = 'Select'
        self.dic['delay'] = '100'
        self.dic['max_ani_time'] = '10'
        self.dic['edge_wrapping'] = 'wrapping_on'
        self.counter_font_size = -20
        self.original_list_box_width = 24
        self.list_box_width = self.original_list_box_width
        self.entry_box_width = 30
        self.original_entry_box_width = 30
        self.score_box_width = 4
        self.board_width = 50
        self.board_height = 50
        self.menu_items = []
        self.preferences = Preferences(self.dic)
        self.preferences.load()
        self.max_cells = int(self.dic['max_cells'])
        self.max_ticks = int(self.dic['max_ticks'])
        self.cell_size = int(self.dic['cell_size'])
        self.margin = int(self.dic['margin_size'])
        self.delay = int(self.dic['delay'])
        self.max_ani_time = int(self.dic['max_ani_time'])
        self.load_menus()
        self.count = len(self.menu_items)
        self.set_fonts()
        self.make_help()
        self.make_tooltip()

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

    def make_help(self):
        self.help_text = _('The program starts by displaying a blank board and a scrollable list of pattern names. The top of the scrollable list has two boxes labelled "Text" and "Plot". Patterns are selected by first clicking on their name and then clicking on "Text" or "Plot". The first simply displays a box of text containing the description from the RLE file and the second starts plotting the pattern on the board. At the top of the board is a jigsaw puzzle symbol containing a menu. Menu options include: "Edge Wrapping" which determines if the cells at the edges of the plot wrap around or are treated as dead; the plot will stop after "Max Ticks"; cells in the plot are of "Cell Size" pixels; the initial pattern is drawn surrounded by "Margin Size" number of blank cells; if the selection mode is "Random" each pattern will be plotted for "Max Animation Time" seconds; "Delay" is the time between ticks in milliseconds; the colours of the plotting surface, the born and survived cells can be set; if the "Selection Mode" is set to "Random" the program will display randomly selected patterns until it is stopped. "Max Cells" defines the maximum number of cells allowed on the board: patterns with more cells will not be loaded when the program starts. The number loaded is shown at the base of the pattern selector box. Next to the jigsaw symbol is a button which starts the plotting of a randomly selected pattern. To its right is a pause button which will stop and restart the current plot. To its right is a stop button.')

    def load_menus(self):
        self.menu_items.append(MenuItem(
            _('Edge Wrapping'),
            [_('wrapping on'),
             _('wrapping off')],
            '',
            ['wrapping_on',
             'wrapping_off'],
            'edge_wrapping'
        ))
        self.menu_items.append(MenuItem(
            _('Max ticks'),
            [
                '10',
                '20',
                '30',
                '40',
                '50',
                '60',
                '70',
                '80',
                '90',
                '100',
                '1000',
            ],
            '',
            [
                '10',
                '20',
                '30',
                '40',
                '50',
                '60',
                '70',
                '80',
                '90',
                '100',
                '1000',
            ],
            'max_ticks'
        ))

        self.menu_items.append(MenuItem(
            _('Cell Size'),
            [
                '1',
                '2',
                '3',
                '4',
                '5',
                '6',
                '7',
                '8',
                '9',
                '10',
            ],
            '',
            [
                '1',
                '2',
                '3',
                '4',
                '5',
                '6',
                '7',
                '8',
                '9',
                '10',
            ],
            'cell_size'
        ))

        self.menu_items.append(MenuItem(
            _('Margin Size'),
            [
                '10',
                '20',
                '30',
                '40',
            ],
            '',
            [
                '10',
                '20',
                '30',
                '40',
            ],
            'margin_size'
        ))

        self.menu_items.append(MenuItem(
            _('Max Animation Time'),
            [
                '5',
                '6',
                '7',
                '8',
                '9',
                '10',
                '11',
                '12',
                '13',
                '14',
                '15',
                '16',
                '17',
                '18',
                '19',
                '20',
            ],
            '',
            [
                '5',
                '6',
                '7',
                '8',
                '9',
                '10',
                '11',
                '12',
                '13',
                '14',
                '15',
                '16',
                '17',
                '18',
                '19',
                '20',
            ],
            'max_ani_time'
        ))

        self.menu_items.append(MenuItem(
            _('Delay'),
            [
                '10',
                '50',
                '100',
                '200',
                '300',
                '400',
                '500',
                '600',
                '700',
                '800',
                '900',
                '1000',
            ],
            '',
            [
                '10',
                '50',
                '100',
                '200',
                '300',
                '400',
                '500',
                '600',
                '700',
                '800',
                '900',
                '1000',
            ],
            'delay'
        ))
        
        self.menu_items.append(MenuItem(
            _('Max cells'),
            [
                '90000',
                '180000',
                '360000',
                '720000',
                '1440000',
                '2880000',
            ],
            '',
            [
                '90000',
                '180000',
                '360000',
                '720000',
                '1440000',
                '2880000',
            ],
            'max_cells'
        ))
        self.menu_items.append(MenuItem(
            _('Set Colour'),
            [_('Table_Colour'),
             _('Born_Colour'),
             _('Survived_Colour'),
            ],
            'N',
            ['Table_Colour',
             _('Born_Colour'),
             _('Survived_Colour'),
            ],
            'set_colour'
        ))

        self.menu_items.append(MenuItem(
            _('Selection Mode'),
            [_('Select'),
             _('Random'),
            ],
            'N',
            [_('Select'),
             _('Random'),
            ],
            'selection_mode'
        ))

        self.menu_items.append(MenuItem(
            _('Save Settings'),
            '',
            '',
            '',
            'save_settings'
        ))
        self.menu_items.append(MenuItem(
            _('Help'),
            '',
            '',
            '',
            'help'
        ))
        self.menu_items.append(MenuItem(
            _('About'),
            '',
            '',
            '',
            'about'
        ))


class SelectorBox(object):
    """
    Create and manage a List_Box
    """
    def __init__(self, parent_frame, settings):
        self.func_1 = None
        self.func_2 = None
        button_frame = tk.Frame(parent_frame)
        button_frame.grid(row=0, column=0)
        list_box_frame = tk.Frame(parent_frame)
        list_box_frame.grid(row=1, column=0)
        text_button = tk.Button(button_frame, text='Text', width=10, height=2,
                                bg=settings.green,
                                command=self.get_text)
        text_button.grid(row=0, column=0)
        plot_button = tk.Button(button_frame, text='Plot', width=10, height=2,
                                bg=settings.green,
                                command=self.get_plot)
        plot_button.grid(row=0, column=1)
        self.settings = settings
        self.text = tk.StringVar()
        font = settings.counter_font
        self.y_scroll = tk.Scrollbar(list_box_frame, orient='vertical')
        self.y_scroll.grid(row=0, column=1, sticky='NS')
        self.box = tk.Listbox(list_box_frame, font=font,
                              yscrollcommand=self.y_scroll.set,
                              selectmode='tk.SINGLE',
                              listvariable=self.text,
                              width=self.settings.list_box_width,
                              height=30)
        self.box.grid(row=0, column=0)
        self.y_scroll["command"] = self.box.yview

    def add_callback(self, func_1, func_2):
        self.func_1 = func_1
        self.func_2 = func_2

    def get_name(self):
        self.text = ''
        selec = self.box.curselection()
        if selec:
            self.text = self.box.get(selec)

    def get_plot(self):
        self.get_name()
        if self.text:
            self.func_1()

    def get_text(self):
        self.get_name()
        if self.text:
            self.func_2()


class PatternSelector(tk.Toplevel):
    """
    Get data from widget in self.lines
    An empty self.lines signals the caller that we've given up
    """
    def __init__(self, pattern_names, settings, func_1, func_2):
        self.settings = settings
        self.pattern_names = pattern_names

        self.expose_ourselves(func_1, func_2)

    def expose_ourselves(self, func_1, func_2):
        """
        expose the window
        """
        tk.Toplevel.__init__(self)
        self.title('Select Pattern')
        self.lines = []
        self.resizable(False, False)
        base_frame = tk.Frame(self)
        base_frame.grid(row=0, column=0)
        data_frame = tk.Frame(base_frame)
        data_frame.grid(row=0, column=0)
        count_frame = tk.Frame(base_frame)
        count_frame.grid(row=1, column=0)
        self.list_box_frame = tk.Frame(data_frame, relief='sunken')
        self.list_box_frame.grid(row=0, column=0)
        self.list_box = SelectorBox(self.list_box_frame, self.settings)
        font = self.settings.counter_font
        count = 0
        for name in self.pattern_names:
            count += 1
            self.list_box.box.insert('end', name)
        text = count
        count_box = tk.Label(count_frame,
                             bd=1,
                             padx=0,
                             pady=0,
                             highlightthickness=1,
                             fg='black',
                             bg=self.settings.green,
                             font=font,
                             width=10,
                             height=1,
                             text=text)
        count_box.grid()
        self.list_box.box.update_idletasks()
        self.add_callback(func_1, func_2)
        self.focus_set()

    def add_callback(self, func_1, func_2):
        self.list_box.func_1 = func_1
        self.list_box.func_2 = func_2

    def my_destroy(self):
        self.destroy()


class Board(tk.Canvas):
    def __init__(self, parent, settings):
        self.parent = parent
        self.settings = settings
        scale = self.settings.cell_size
        self.board_width = self.settings.board_width
        self.board_height = self.settings.board_height
        tk.Canvas.__init__(self, self.parent, width=self.board_width*scale,
                           height=self.board_height*scale,
                           bg=self.settings.dic['Table_Colour'])
        self.grid(row=0, column=0)
        self.update_idletasks()


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

    def __init__(self, master, settings):
        ViewB.__init__(self, master, settings)
        self.model = None
        self.settings = settings
        self.after_id = None
        self.board_frame = None
        self.image_dic = {}
        self.make_image_dic()
        self.do_layout(self)
        self.entry_box.activate()
        self.func = None
        self.pattern_selector = None
        self.delay = self.settings.delay
        self.pause_var = tk.IntVar()

    def init_pattern_selector(self):
        pattern_names = self.model.pattern_names
        self.pattern_selector = PatternSelector(pattern_names, self.settings,
                                                self.func,
                                                self.show_pattern_text)

    def show_pattern_text(self):
        pattern_name = self.pattern_selector.list_box.text
        pattern = self.model.get_pattern(pattern_name)
        text = pattern[1]
        message = ''
        for item in text:
            message += item + '\n'
        self.show_message('Pattern', message)

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

    def make_image_dic(self):
        """
        image, button_index, start
        """
        dic = {}
        dic['start'] = [self.images.start_gol, 0, True]
        dic['pause'] = [self.images.pause_gol, 1, True]
        dic['stop'] = [self.images.stop_gol, 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.entry_box = EntryBox(self.number_frame,
                                  self.settings)
        self.score_box = ScoreBox(self.settings, self.number_frame)
        self.score_box.grid(row=0, column=1)
        self.top_frame = tk.Frame(self.outer_frame, relief='flat',
                                  bd=1, bg=bg)

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

    def make_empty_board(self):
        if self.board:
            self.board.destroy()
        self.board = Board(self.board_frame, self.settings)
        self.board_frame.update_idletasks()

    def draw_dots(self, grid_dic):
        self.board.delete('dot')
        fill_1 = self.settings.dic['Born_Colour']
        fill_2 = self.settings.dic['Survived_Colour']
        scale = 8
        scale = self.settings.cell_size
        for key, item in list(grid_dic.items()):
            x0 = key[0] * scale
            y0 = key[1] * scale
            x1 = (key[0] + 1) * scale
            y1 = (key[1] + 1) * scale
            if item == 1:
                self.board.create_rectangle(x0, y0, x1, y1,
                                            width=0, fill=fill_2, tag='dot')
            else:
                self.board.create_rectangle(x0, y0, x1, y1,
                                            width=0, fill=fill_1, tag='dot')

    def cancel_animate(self):
        if self.after_id is not None:
            self.master.after_cancel(self.after_id)

    def animate(self):
        if self.model.ticks < self.model.max_ticks:
            if self.model.keep_going:
                if self.model.pause:
                    self.pause_var = tk.IntVar()
                    self.master.wait_variable(self.pause_var)
                    self.model.keep_going = True
                    self.model.tick()
                    self.draw_dots(self.model.grid_dic_1)
                    self.update_score_box(self.model.ticks)
                    self.model.next_tick()
                    self.after_id = self.master.after(self.delay, self.animate)
                    self.model.pause = False
                else:
                    
                    self.model.tick()
                    self.draw_dots(self.model.grid_dic_1)
                    self.update_score_box(self.model.ticks)
                    self.model.next_tick()
                    self.after_id = self.master.after(self.delay, self.animate)
            else:
                self.cancel_animate()
        return

    def do_it(self):
        self.model.keep_going = True
        self.delay = self.settings.delay
        self.draw_dots(self.model.grid_dic_0)
        self.update_score_box(0)

        var = tk.IntVar()
        self.master.after(2000, var.set, 0)
        self.master.wait_variable(var)
        self.animate()


class Patterns(object):
    """
    Get Game of Life Patterns
    Downloaded from:
    https://www.conwaylife.com/wiki/Main_Page
    then unzipped into ~/.pzl/gol/RLE
    Only the *.rle files are used and the *.cells files can be deleted.
    Rules
    The universe of the Game of Life is an infinite, two-dimensional
    orthogonal grid of square cells, each of which is in one of two
    possible states, alive or dead, (or populated and unpopulated,
    respectively). Every cell interacts with its eight neighbours, which
    are the cells that are horizontally, vertically, or diagonally
    adjacent. At each step in time, the following transitions occur:

    1. Any live cell with fewer than two live neighbours dies, as if by
    underpopulation.

    2. Any live cell with two or three live neighbours lives on to the
    next generation.

    3. Any live cell with more than three live neighbours dies, as if by
    overpopulation.

    4. Any dead cell with exactly three live neighbours becomes a live
    cell, as if by reproduction.

    These rules, which compare the behavior of the automaton to real life,
    can be condensed into the following:

    1. Any live cell with two or three live neighbors survives.
    2. Any dead cell with three live neighbors becomes a live cell.
    3. All other live cells die in the next generation. Similarly, all
    other dead cells stay dead.

    The initial pattern constitutes the seed of the system. The first
    generation is created by applying the above rules simultaneously to
    every cell in the seed; births and deaths occur simultaneously, and
    the discrete moment at which this happens is sometimes called a
    tick. Each generation is a pure function of the preceding one. The
    rules continue to be applied repeatedly to create further generations.
    """
    def __init__(self, rule, max_cells, pattern_files):
        self.rule = rule
        self.max_cells = max_cells
        self.pattern_dic = {}
        files = glob.glob(pattern_files + os.sep + "*.rle")
        count = 0
        good = 0
        for filename in files:
            count += 1
            # ignore very large files

            statinfo = os.stat(filename)
            if statinfo.st_size < self.max_cells:
                pattern = self.get_pattern(filename, rule)
                if pattern:
                    good += 1
                    self.pattern_dic[pattern[0]] = pattern[1]

    def get_pattern(self, filename, rule):
        def from_file(filename):
            # Parse a file into a list of strings
            if PzlG.PY3:
                return open(filename, encoding="ISO-8859-1").read().strip().splitlines()
            return open(filename).read().strip().splitlines()

        def expand_rle(data):
            expand = ''
            count = ''
            for char in data:
                # If the character is numerical...
                if char.isdigit():
                    # ...append it to our count
                    count += char
                else:
                    # Otherwise we've seen a non-numerical
                    # character and need to expand it for
                    # the decoding
                    if not count:
                        # rle allows single alives to have no count!
                        count = '1'
                    expand += char * int(count)
                    count = ''
            return expand

        area = self.max_cells + 1
        rule_OK = False
        pattern = []
        text = []
        lines = from_file(filename)

        # use file name as default pattern name

        name = filename[filename.rfind(os.sep) + 1:-4]
        for line in lines:
            for c in line:
                if 0 <= ord(c) <= 127:
                    # this is an ascii character.
                    pass
                else:
                    # this is a non-ascii character.
                    # Rejecting this file!!!!
                    return pattern
        for line in lines:
            if line[0:2] == '#N':
                name = line[3:]
                break

        # get size
        for i, line in enumerate(lines):
            text.append(line)
            if 'x = ' in line:
                x = area
                y = area
                splits = line.split()
                txt = splits[2]
                x = int(txt[:-1])
                txt = splits[5]
                y = int(txt[:-1])
                size = (x, y)
                area = x * y
                if 'rule = ' in line and rule in line.upper():
                    p_rule = line.upper().find(rule)
                    if p_rule + len(rule) >= len(line):
                        rule_OK = True
                        break
        if rule_OK and area <= self.max_cells and\
           x < Sgg.MAX_SIZE and y < Sgg.MAX_SIZE:

            # get non-comment lines

            rle = ""

            for line in lines[i + 1:]:
                if line and line[0] != '#':
                    rle += line
            # remove trailing '!'
            rle = rle[:-1]
            # get x,y of alives
            alive = []
            xmax = 0
            ymax = 0
            x = 0
            y = 0
            expanded = expand_rle(rle)
            p = 0
            while p < len(expanded):
                if expanded[p] == '$':
                    y += 1
                    x = 0
                elif expanded[p] == 'b':
                    x += 1
                elif expanded[p] == 'o':
                    alive.append((x, y))
                    xmax = max(xmax, x)
                    ymax = max(ymax, y)
                    x += 1
                p += 1
            xmax += 1
            ymax += 1
            if xmax != size[0] or ymax != size[1]:
                pass
            size = (xmax, ymax)
            pattern = [name, [[size, alive], text]]
        return pattern


class ModelData(object):
    def __init__(self, settings):
        # to match, our rule must be upper case
        rule = 'B3/S23'
        self.patterns_dic = Patterns(rule,
                                     settings.max_cells,
                                     settings.dic['pattern_files']).pattern_dic


class Model(object):
    def __init__(self, model_data, settings):
        self.model_data = model_data
        self.patterns_dic = self.model_data.patterns_dic
        self.pattern_name = ''
        self.settings = settings
        self.width = self.settings.board_width
        self.height = self.settings.board_height
        self.max_ticks = self.settings.max_ticks
        self.max_ani_time = self.settings.max_ani_time * 1000
        self.new_pattern()
        self.pattern_names = sorted(self.model_data.patterns_dic.keys())
        self.pause = False
        self.keep_going = False
        self.grid_dic_0 = {}
        self.grid_dic_1 = {}
        self.grid_dic_2 = {}
        self.ticks = 1
        self.grid_id = None
        self.wrap = None

    def wrap_1(self, val, inc, size):
        val += inc
        if val < 0:
            val = size - 1
        elif val == size:
            val = 0
        return val

    def wrap_2(self, val, inc, size):
        newval = val + inc
        if newval < 0 or newval == size:
            newval = None
        return newval

    def new_pattern(self):
        self.grid_dic_0 = {}
        self.grid_dic_1 = {}
        self.grid_dic_2 = {}
        self.ticks = 1
        self.grid_id = None
        self.max_ani_time = self.settings.max_ani_time * 1000
        self.pause = False
        self.keep_going = True

    def get_pattern(self, pattern_name):
        pattern = self.patterns_dic[pattern_name]
        return pattern

    def get_random_pattern(self):
        name_index = random.randint(0, len(self.pattern_names) - 1)
        pattern_name = self.pattern_names[name_index]
        pattern = self.patterns_dic[pattern_name]
        return pattern, pattern_name

    def load_pattern(self, pattern):
        size = pattern[0][0]
        margin = self.settings.margin
        self.width = size[0] + 2 * margin
        self.height = size[1] + 2 * margin
        self.settings.board_width = self.width
        self.settings.board_height = self.height
        offset_x = margin
        offset_y = margin
        dots = pattern[0][1]
        self.grid_dic_0 = {}
        for dot in dots:
            x = offset_x + dot[0]
            y = offset_y + dot[1]
            self.grid_dic_0[x, y] = 1
            self.grid_dic_2[x, y] = 1
        if self.settings.dic['edge_wrapping'] == 'wrapping_on':
            self.wrap = self.wrap_1
        else:
            self.wrap = self.wrap_2
        self.max_ticks = self.settings.max_ticks

    def tick(self):
        """
        1. Any live cell with two or three live neighbors survives.
        2. Any dead cell with three live neighbors becomes a live cell.
        3. All other live cells die in the next generation. Similarly, all
        other dead cells stay dead.
        """
        nincs = [(0, -1), (1, 0), (0, 1), (-1, 0),
                 (1, -1), (1, 1), (-1, 1), (-1, -1)]
        self.ticks += 1

        # get dead cells adjacent to live ones

        # use dic to avoid duplicates

        edges_dic = {}
        for key, value in list(self.grid_dic_0.items()):
            x = key[0]
            y = key[1]
            for mx, my in nincs:
                nx = self.wrap(x, mx, self.width)
                ny = self.wrap(y, my, self.height)
                if nx is not None and ny is not None:
                    if (nx, ny) not in self.grid_dic_0:
                        edges_dic[nx, ny] = 1

        # get born

        for key, value in list(edges_dic.items()):
            x = key[0]
            y = key[1]
            alive = 0
            for mx, my in nincs:
                nx = self.wrap(x, mx, self.width)
                ny = self.wrap(y, my, self.height)
                if (nx, ny) in self.grid_dic_0:
                    alive += 1
            if alive == 3:
                self.grid_dic_1[key] = 2

        # get survivors

        for key, value in list(self.grid_dic_0.items()):
            alive = 0
            x = key[0]
            y = key[1]
            alive = 0
            for mx, my in nincs:
                nx = self.wrap(x, mx, self.height)
                ny = self.wrap(y, my, self.height)
                if (nx, ny) in self.grid_dic_0:
                    alive += 1
            if alive == 3 or alive == 2:
                self.grid_dic_1[key] = 1

    def next_tick(self):
        if self.grid_dic_1 == self.grid_dic_2:
            self.keep_going = False
        self.grid_dic_0 = self.grid_dic_1
        self.grid_dic_2 = self.grid_dic_1.copy()
        self.grid_dic_1 = {}


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

    def __init__(self, root):
        self.master = root
        self.settings = Settings()
        self.model = None
        self.pattern_selector = None
        self.model_data = ModelData(self.settings)
        self.model = Model(self.model_data, self.settings)
        self.view = View(root, self.settings)
        self.view.model = self.model
        self.pattern_name = self.settings.dic['pattern_name']
        self.add_callbacks()
        self.view.make_empty_board()
        self.view.init_pattern_selector()

    def start_game(self):
        self.pattern_name = self.view.pattern_selector.list_box.text
        self.view.cancel_animate()
        self.view.game_over()
        self.model.new_pattern()
        pattern = self.model.get_pattern(self.pattern_name)
        self.model.load_pattern(pattern)
        self.view.entry_box.clear()
        self.view.entry_box.write_box(self.pattern_name)
        self.view.make_empty_board()
        self.view.game_on()
        self.view.do_it()

    def start_random_game(self):
        self.model.keep_going = True
        self.model.pause = False
        self.random_game()

    def random_game(self):
        if self.settings.dic['selection_mode'] == 'Random':
            max_time = self.model.max_ani_time
            self.view.cancel_animate()
            self.view.game_over()
            self.model.new_pattern()
            pattern, self.pattern_name = self.model.get_random_pattern()
            self.model.load_pattern(pattern)
            self.view.entry_box.clear()
            self.view.entry_box.write_box(self.pattern_name)
            self.view.make_empty_board()
            self.view.game_on()
            self.view.do_it()
            random_id = self.master.after(max_time, self.random_game)

    def plot_random_pattern(self):
        pattern, self.pattern_name = self.model.get_random_pattern()
        self.view.cancel_animate()
        self.view.game_over()
        self.model.new_pattern()
        pattern = self.model.get_pattern(self.pattern_name)
        self.model.load_pattern(pattern)
        self.view.entry_box.clear()
        self.view.entry_box.write_box(self.pattern_name)
        self.view.make_empty_board()
        self.view.game_on()
        self.view.do_it()

    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 == 'set_colour':
            choice = args[1]
            self.set_colour(choice)
        elif name == 'cell_size':
            choice = args[1]
            self.set_cell_size()
        elif name == 'max_ticks':
            choice = args[1]
            self.set_max_ticks()
        elif name == 'max_ani_time':
            choice = args[1]
            self.set_max_ani_time()
        elif name == 'delay':
            choice = args[1]
            self.set_delay()
        elif name == 'margin_size':
            choice = args[1]
            self.set_margin_size()
        elif name == 'selection_mode':
            choice = args[1]
            self.selection_mode(choice)
        elif name == 'save_settings':
            self.settings.preferences.save()
        elif name == 'about':
            self.view.about()
        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)

        elif mouse_index == 3:
            if button_index == 0:
                self.start_game()

        elif mouse_index == 1:
            if button_index == 0:
                self.plot_random_pattern()
            elif button_index == 1:
                if not self.model.pause:
                    self.model.pause = True
                else:
                    self.model.pause = False
                    self.view.pause_var.set(1)
            elif button_index == 2:
                self.model.keep_going = False

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

    def set_margin_size(self):
        self.settings.margin = int(self.settings.dic['margin_size'])

    def set_delay(self):
        self.settings.delay = int(self.settings.dic['delay'])

    def set_cell_size(self):
        self.settings.cell_size = int(self.settings.dic['cell_size'])

    def set_max_ticks(self):
        self.settings.max_ticks = int(self.settings.dic['max_ticks'])

    def set_max_cells(self):
        self.settings.max_cells = int(self.settings.dic['max_cells'])

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

    def set_colour(self, choice):
        try:
            bg_colour = self.settings.dic[choice]
            result = tkColorChooser.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 selection_mode(self, choice):
        if choice == 'Random':
            self.start_random_game()


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")
