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

# Copyright (C) 2014 Rod Engerstad

import random
import Tkinter as tk

from dawg import ModelData
from pzl_lib import PzlG
from pzl_lib import SettingsB
from pzl_lib import Preferences
from pzl_lib import MenuItem
from pzl_lib import EntryBox
from pzl_lib import Flasher
from pzl_lib import ScreenShot
from pzl_lib import ViewB
from pzl_lib import Menu
from pzl_lib import Toolbar
from pzl_lib import ScoreBox
from pzl_lib import ClockBox
from pzl_lib import ColourRamp
from pzl_lib import TextSquare

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

    grid_size = 4
    crossover = False
    min_word_length = 4


class Settings(SettingsB):

    def __init__(self):
        SettingsB.__init__(self)
        self.original_square_size = 36
        self.square_size = self.original_square_size
        self.list_box_width = 21
        self.original_entry_box_width = 18
        self.original_list_box_width = 21
        self.candidate_font_size = -15
        self.entry_box_bg = self.white
        self.entry_box_fg = self.black
        self.dic = {}
        self.dic['font_size'] = '2'
        self.dic['game_type'] = 'find_all'
        self.dic['shading'] = 'shade_on'
        self.dic['input_mode'] = 'mouse'
        self.dic['game_time'] = '120'
        self.dic['min_word_length'] = '4'
        self.dic['grid_size'] = '5'
        self.dic['crossover'] = 'crossover_off'

        self.preferences = Preferences(self.dic)
        self.preferences.load()
        if self.dic['game_type'] == 'against_clock':
            self.max_time = int(self.dic['game_time'])
        Sgg.grid_size = int(self.dic['grid_size'])
        Sgg.min_word_length = int(self.dic['min_word_length'])
        Sgg.crossover = False
        if self.dic['crossover'] == 'crossover_on':
            Sgg.crossover = True
        self.menu_items = []
        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 randomly selected grid. Find the words hidden in the grid. Define words by left clicking on the letters (in order). Right click to finish a word, middle click to undo a letter. Alternatively enter the words using the keyboard after first placing the mouse cursor in the entry box. Letters can only be used once per word and must be selected from touching squares. Apart from those at the edge of the grid, each square touches 8 others (up, down, left, right and four diagonals). Words of sufficient length that are in the programs dictionary will be transferred to the scrolling list. You can play against the clock or try to find all words and see how long it takes. Results show the number of words you found, the number remaining and the percentage that you found. The optional colour coding shows how many new words can be found starting from each square, white meaning none. The colour fades as the words are found. At the top of the display is a Toolbar. The jigsaw piece button at the left is a menu which has options for choosing the game type, selecting shading, choosing the input mode, the maximum game duration, crossover, minimum word length, grid size and font size. Note well that option changes will not come into effect until a new game is started using the adjacent boggle icon. To the right of the puzzle icon is the boggle icon used to start a new game, and a sad smiley used to show the solution. Right again is a counter for the number of words found. Right again is a clock which ticks every 5 seconds.")

    def load_menus(self):
        self.menu_items.append(MenuItem(
            _('Game type'),
            [_('Against clock'),
             _('Find all')],
            '',
            ['against_clock',
             'find_all'],
            'game_type'
        ))
        self.menu_items.append(MenuItem(
            _('Shade cells'),
            [_('Shade on'),
             _('Shade off')],
            '',
            ['shade_on',
             'shade_off'],
            'shading'
        ))
        self.menu_items.append(MenuItem(
            _('Input mode'),
            [_('Mouse'),
             _('Keyboard')],
            '',
            ['mouse',
             'keyboard'],
            'input_mode'
        ))
        self.menu_items.append(MenuItem(
            _('Game time'),
            ['20',
             '40',
             '60',
             '80',
             '100',
             '120',
             '140',
             '160',
             '180',
             '200',
             '220',
             '240'],
            '',
            ['20',
             '40',
             '60',
             '80',
             '100',
             '120',
             '140',
             '160',
             '180',
             '200',
             '220',
             '240'],
            'game_time'
        ))
        self.menu_items.append(MenuItem(
            _('Min word length'),
            ['3',
             '4',
             '5',
             '6'],
            '',
            ['3',
             '4',
             '5',
             '6'],
            'min_word_length'
        ))
        self.menu_items.append(MenuItem(
            _('Grid size'),
            ['3',
             '4',
             '5',
             '6'],
            '',
            ['3',
             '4',
             '5',
             '6'],
            'grid_size'
        ))
        self.menu_items.append(MenuItem(
            _('Crossover'),
            [_('Crossover on'),
             _('Crossover off')],
            '',
            ['crossover_on',
             'crossover_off'],
            'crossover'
        ))
        self.menu_items.append(MenuItem(
            _('Font Size'),
            ['1',
             '2',
             '3',
             '4',
             '5',
             '6',
             '7',
             '8',
             '9',
             '10',
             '11',
             '12',
             '13'],
            '',
            ['1',
             '2',
             '3',
             '4',
             '5',
             '6',
             '7',
             '8',
             '9',
             '10',
             '11',
             '12',
             '13'],
            'font_size'
            ))
        self.menu_items.append(MenuItem(
            _('Save Settings'),
            '',
            '',
            '',
            'save_settings'
        ))
        self.menu_items.append(MenuItem(
            _('Help'),
            '',
            '',
            '',
            'help'
        ))
        self.menu_items.append(MenuItem(
            _('About'),
            '',
            '',
            '',
            'about'
        ))


class ListBox(object):
    """
    Create and manage a ListBox
    """
    def __init__(self, parent_frame, settings):
        font = settings.candidate_font
        self.y_scroll = tk.Scrollbar(parent_frame, orient='vertical')
        self.y_scroll.grid(row=0, column=1, sticky='NS')
        self.box = tk.Listbox(parent_frame, font=font,
                              yscrollcommand=self.y_scroll.set,
                              width=settings.list_box_width,
                              height=20)
        self.box.grid(row=0, column=0, sticky='NSEW')
        self.y_scroll["command"] = self.box.yview
        self.clear()

    def clear(self):
        self.box.delete(0, last='end')
        self.box.update_idletasks()


class Board(tk.Frame):
    def __init__(self, parent, settings, datum, colour_ramp):
        self.settings = settings
        self.datum = datum
        self.colour_ramp = colour_ramp
        shading = False
        if self.settings.dic['shading'] == 'shade_on':
            shading = True
        self.grid_widgets = []
        bg = self.settings.green
        tk.Frame.__init__(self, parent, relief='flat',
                          highlightbackground=self.settings.grey,
                          highlightthickness=0,
                          bd=2, bg=bg)
        self.board = tk.Frame(self, bg='gray', bd=2)
        self.board.grid(row=0, column=0)
        fg = 'black'
        square_index = 0
        for row in range(Sgg.grid_size):
            for column in range(Sgg.grid_size):
                datum = self.datum[square_index]
                text = datum.letter
                colour_index = 0
                if shading:
                    colour_index = self.datum[square_index].word_count
                bg = self.get_colour(colour_index)
                square = TextSquare(self.board, self.settings, square_index, fg,
                                    bg, text)
                square.grid(row=row, column=column)
                square.grid_propagate(0)
                self.grid_widgets.append(square)
                square_index += 1
        bg = self.settings.green
        self.text_frame = tk.Frame(self, relief='flat',
                                   bd=2, bg=bg)
        self.text_frame.grid(row=2, column=0, sticky='NSEW')
        self.entry_box_frame = tk.Frame(self.text_frame, relief='sunken')
        self.entry_box_frame.grid(row=0, column=0)
        self.entry_box = EntryBox(self.entry_box_frame,
                                  self.settings)
        self.list_box_frame = tk.Frame(self.text_frame, relief='sunken')
        self.list_box_frame.grid(row=1, column=0)
        self.list_box = ListBox(self.list_box_frame,
                                self.settings)
        self.grid(row=0, column=0)

    def my_destroy(self):
        for widget in self.grid_widgets:
            widget.my_destroy()
        self.destroy()

    def add_callback(self, func):
        for widget in self.grid_widgets:
            widget.callback = func

    def get_colour(self, colour_index):
        colour_index = max(colour_index, 0)
        colour_index = min(colour_index, self.colour_ramp.length - 1)
        return self.colour_ramp.colours[colour_index]

    def update_square(self, square_index):
        colour_index = 0
        if self.settings.dic['shading'] == 'shade_on':
            colour_index = self.datum[square_index].word_count
        bg = self.get_colour(colour_index)
        widget = self.grid_widgets[square_index]
        widget.colour_bg(bg)


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.colour_ramp = ColourRamp(None)
        self.image_dic = {}
        self.make_image_dic()
        self.do_layout(self)

    def make_image_dic(self):
        """
        image, button_index, start
        """
        dic = {}
        dic['start'] = [self.images.start_boggle, 0, True]
        dic['show_solution'] = [self.images.smiley, 1, True]
        dic['length'] = [None, 2, 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()
        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.score_box = ScoreBox(self.settings, self.number_frame)
        self.score_box.grid(row=0, column=1, sticky='NSEW')
        self.clock_box = ClockBox(self, self.number_frame)
        self.clock_box.grid(row=0, column=2, sticky='NSEW')
        self.top_frame = tk.Frame(self.outer_frame, relief='flat',
                                  bd=1, bg=bg)
        self.top_frame.grid(row=1, column=0)
        self.board = None

    def make_board(self):
        """
        draw the squares and the entrybox and listbox
        """

        if self.board is not None:
            self.board.my_destroy()
        self.board = Board(self.top_frame, self.settings,
                           self.datum, self.colour_ramp)
        self.board.entry_box.activate()

    def update_score_box(self, score):
        self.score_box.my_update(score)

    def new_game(self, datum):
        self.clock_init()
        self.clock_restart()
        self.datum = datum
        self.make_board()
        self.board.list_box.clear()

    def show_remainder(self, word_origins_dic, total_words, found_words):
        square_dic = {}
        for word, square_list in list(word_origins_dic.items()):
            for square_index in square_list:
                try:
                    words = square_dic[square_index]
                    words.append(word)
                except KeyError:
                    # new square
                    square_dic[square_index] = [word]
        perc = 100.0*(float(found_words) / total_words)
        text = str(total_words) + ' ' + \
            str(found_words) + ' ' + str(round(perc, 1))+'%'
        self.board.list_box.box.insert('end', text)
        for square_index in range(Sgg.grid_size * Sgg.grid_size):
            try:
                words = square_dic[square_index]
                for word in words:
                    text = str(square_index + 1) + ' ' + word
                    self.board.list_box.box.insert('end', text)
            except KeyError:
                pass
        self.board.list_box.box.update_idletasks()


class Datum(object):
    """
    Data item for a square
    """
    def __init__(self, my_index):
        self.neighbours = []
        self.index = my_index
        self.letter = ''
        self.word_count = 0
        self.get_neighbours()

    def add_letter(self, letter):
        self.letter = letter

    def reset(self):
        self.neighbours = []
        self.letter = ''

    def add_word(self):
        self.word_count += 1

    def subtract_word(self):
        self.word_count -= 1

    def get_neighbours(self):
        """
        Get indexes of neighbouring squares
        """
        self.neighbours = []
        grid_count = Sgg.grid_size * Sgg.grid_size
        wrap_around = Sgg.grid_size * (Sgg.grid_size - 1)

        # up
        neighbour = self.index - Sgg.grid_size
        if neighbour > -1:
            self.neighbours.append(neighbour)
        elif Sgg.crossover:
            neighbour = self.index + wrap_around
            self.neighbours.append(neighbour)

        # up right
        neighbour = self.index - Sgg.grid_size + 1
        if neighbour < 1 or (self.index + 1) % Sgg.grid_size == 0:
            neighbour = -1
        if neighbour > -1:
            self.neighbours.append(neighbour)
        elif Sgg.crossover:
            if self.index < Sgg.grid_size:
                neighbour = self.index + wrap_around + 1
                if self.index == Sgg.grid_size - 1:
                    neighbour = wrap_around
            else:
                neighbour = self.index - 2 * Sgg.grid_size + 1
            self.neighbours.append(neighbour)

        # right
        neighbour = self.index + 1
        if neighbour % Sgg.grid_size == 0:
            neighbour = -1
        if neighbour > -1:
            self.neighbours.append(neighbour)
        elif Sgg.crossover:
            neighbour = self.index - Sgg.grid_size + 1
            self.neighbours.append(neighbour)

        # down right
        neighbour = self.index + Sgg.grid_size + 1
        if neighbour > (grid_count) or \
                (self.index + 1) % Sgg.grid_size == 0:
            neighbour = -1
        if neighbour > -1:
            self.neighbours.append(neighbour)
        elif Sgg.crossover:
            if self.index > wrap_around - 1:
                neighbour = self.index - wrap_around + 1
                if self.index == grid_count - 1:
                    neighbour = 0
            else:
                neighbour = self.index + 1
            self.neighbours.append(neighbour)

        # down
        neighbour = self.index + Sgg.grid_size
        if neighbour >= grid_count:
            neighbour = -1
        if neighbour > -1:
            self.neighbours.append(neighbour)
        elif Sgg.crossover:
            neighbour = self.index - wrap_around
            self.neighbours.append(neighbour)

        # down left
        neighbour = self.index + Sgg.grid_size - 1
        if neighbour >= grid_count or \
                self.index % Sgg.grid_size == 0:
            neighbour = -1
        if neighbour > -1:
            self.neighbours.append(neighbour)
        elif Sgg.crossover:
            if self.index > wrap_around - 1:
                neighbour = self.index - wrap_around - 1
                if self.index == wrap_around:
                    neighbour = Sgg.grid_size - 1
            else:
                neighbour = self.index + 2*Sgg.grid_size - 1
            self.neighbours.append(neighbour)

        # left
        neighbour = self.index - 1
        if self.index % Sgg.grid_size == 0:
            neighbour = -1
        if neighbour > -1:
            self.neighbours.append(neighbour)
        elif Sgg.crossover:
            neighbour = self.index + Sgg.grid_size - 1
            self.neighbours.append(neighbour)

        # up left
        neighbour = self.index - Sgg.grid_size - 1
        if neighbour < 0 or self.index % Sgg.grid_size == 0:
            neighbour = -1
        if neighbour > -1:
            self.neighbours.append(neighbour)
        elif Sgg.crossover:
            if self.index < Sgg.grid_size:
                neighbour = self.index - 1 + wrap_around
                if self.index == 0:
                    neighbour = grid_count - 1
            else:
                neighbour = self.index - 1
            self.neighbours.append(neighbour)


class Model(object):
    """
    Dictionary is stored as a dawg.
    For each board we find all the words that start on each square
    and store them in word_origins_dic.
    word_origins_dic['word'] = [square_index_1, square_index_2,,,]
    To show the user the words missed we invert this dictionary so
    that we can list the words and all the possible places they originate.
    When the user correctly enters a word we use word_origins_dic to
    subtract 1 from the word counts for all the squares from which
    the word originates, and then delete the word from the dic.
    The View is given a list of these square_indexes so it can fade them.
    """
    alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ.$'
    end_of_node_shift = 5
    end_of_word_shift = 6
    offset_shift = 7
    letter_mask = 0o37

    def __init__(self, model_data):
        self.model_data = model_data
        self.datum = []
        self.word = ''
        self.word_pairs = []
        self.my_path = []
        self.word_indexes = []
        self.origin_index = None
        self.word_origins_dic = {}
        self.total_words = 0
        self.found_words = 0
        self.this_word = ''
        self.letters = ''
        self.init_letters()
        self.init_datum()

    def new_word(self):
        self.this_word = ''

    def init_datum(self):
        self.datum = []
        for datum_index in range(Sgg.grid_size * Sgg.grid_size):
            self.datum.append(Datum(datum_index))

    def add_letters(self, letters):
        letter_index = 0
        for datum in self.datum:
            datum.add_letter(letters[letter_index])
            letter_index += 1

    def init_letters(self):
        # counts from boggle dice as supplied by ARC (except Qu)
        freqs = ['A', 8, 'B', 3, 'C', 3, 'D', 4, 'E', 10, 'F', 2,
                 'G', 3, 'H', 3, 'I', 7, 'J', 1, 'K', 2, 'L', 5,
                 'M', 3, 'N', 5, 'O', 6, 'P', 3, 'Q', 0, 'R', 4,
                 'S', 5, 'T', 5, 'U', 4, 'V', 2, 'W', 2, 'X', 1,
                 'Y', 3, 'Z', 1]

        letters = ''
        for item in range(1, 52, 2):
            letters += freqs[item-1] * freqs[item]
        self.letters = letters

    def add_letter(self, square_index):
        letter = self.datum[square_index].letter
        self.this_word += letter
        return letter

    def delete_letter(self):
        word_len = len(self.this_word) - 1
        if word_len >= 0:
            self.this_word = self.this_word[0:word_len]
        else:
            self.this_word = ''

    def all_words_found(self):
        ret = False
        if self.found_words == self.total_words:
            ret = True
        return ret

    def good_user_word(self, user_word):
        """
        User has entered a word.
        If it exists in word_origins_dic, remove it.
        Also decrement word counts for datum origin squares.
        Add to count of words found.
        """
        word_found = False
        word_origins = []
        try:
            word_origins = self.word_origins_dic[user_word]
            for square_index in word_origins:
                self.datum[square_index].subtract_word()
            word_found = True
            self.found_words += 1
        except KeyError:
            pass
        return word_found, word_origins

    def subtract_word(self, word):
        try:
            del self.word_origins_dic[word]
        except KeyError:
            PzlG.logger.logger.error("subtract_word: word not found! ")

    def new_game(self):
        min_words = 10
        test = 0
        while test < min_words:
            quiz = []
            for item in range(Sgg.grid_size * Sgg.grid_size):
                quiz += self.letters[random.randrange(len(self.letters))]
            """
            quiz = ['T', 'R', 'F', 'E', 'H', 'G', 'A', 'D', 'T', 'C',
                    'I', 'I', 'N', 'Y', 'Y', 'T', 'N', 'L', 'H', 'I',
                    'C', 'R', 'I', 'Y', 'U']
            quiz = ['I', 'M', 'A', 'L', 'O', 'J', 'D', 'D', 'S', 'A',
                    'R', 'I', 'E', 'D', 'A', 'L', 'T', 'L', 'N', 'E',
                    'D', 'T', 'P', 'B', 'S', 'A', 'B', 'P', 'F', 'E',
                    'S', 'E', 'D', 'A', 'F', 'Y']
            """
            self.add_letters(quiz)
            self.get_words()
            test = self.total_words
        strargs = 'new_game '
        for item in quiz:
            strargs += ' ' + item
        PzlG.logger.logger.info(strargs)
        self.found_words = 0
        self.new_word()

    def letter_in_node(self, node_in, letter_in):
        """
        is letter_in in node_in?
        return answer, next_record, end_of_word flag
        """

        letter_in_index = Model.alphabet.index(letter_in)
        record_index = node_in
        next_record = -1
        ret = False
        end_of_node = False
        end_of_word = False
        while True:
            record = self.model_data.dawg[record_index]
            letter_index = record & Model.letter_mask
            if record & (1 << Model.end_of_node_shift):
                end_of_node = True
            if letter_index != letter_in_index:
                if not end_of_node:
                    record_index += 1
                else:
                    break
            else:
                ret = True
                if record & (1 << Model.end_of_word_shift):
                    end_of_word = True
                next_record = record >> Model.offset_shift
                break
        return ret, next_record, end_of_word

    def next_square(self, node):
        """
        Recurse through the grid trying all neighbours to look
        for words in the dawg.
        Store hits as word_pairs, ie [word, word_origin]
        All hits are stored, even when the same word can be made
        in more than one way from the same square. Such duplications
        are removed when we create word_origins_dic from the word_pairs.
        All other variable and lists are temporary.
        """
        this_item = self.my_path[-1]
        first = this_item[1]
        last = this_item[2]
        current = first
        while current < last:
            square_index = this_item[3][current]
            if square_index not in self.word_indexes:
                this_letter = self.datum[square_index].letter
                self.word += this_letter
                self.word_indexes.append(square_index)
                in_node, next_node, end_of_word = \
                    self.letter_in_node(node, this_letter)
                if in_node:
                    if end_of_word:
                        if len(self.word) >= Sgg.min_word_length:
                            self.word_pairs.append([self.word,
                                                    self.origin_index])
                    neighbours = self.datum[square_index].neighbours
                    my_path = [square_index, 0, len(neighbours), neighbours]
                    self.my_path.append(my_path)
                    # recurse!!!!!!!
                    self.next_square(next_node)
                else:
                    self.word = self.word[:-1]
                    self.word_indexes = self.word_indexes[:-1]
            current += 1
        self.my_path.pop()
        self.word = self.word[:-1]
        self.word_indexes = self.word_indexes[:-1]

    def search(self):
        """
        word_indexes: the squares in this word (to prevent using letters twice)
        word_pairs:   [(word, word_origin)]
        We find all words starting from each square, including duplicates.
        """
        self.word_pairs = []
        for self.origin_index in range(Sgg.grid_size * Sgg.grid_size):
            self.word = self.datum[self.origin_index].letter
            in_node, next_node, end_of_word = \
                self.letter_in_node(0, self.word)

            if in_node:
                self.word_indexes = [self.origin_index]
                neighbours = self.datum[self.origin_index].neighbours
                self.my_path = [[self.origin_index, 0,
                                 len(neighbours), neighbours]]
                self.next_square(next_node)

    def get_words(self):
        """
        word_pairs contains all the words, made in every possible way,
        from each square on the board.
        Here we create word_origins_dic a dictionary indexed on words, which
        contains the square indexes from which each word originates. In the
        process we remove duplicate ways of making the same word from each
        square.
        """
        self.search()
        self.word_origins_dic = {}
        self.total_words = 0
        for word_pair in self.word_pairs:
            word = word_pair[0]
            square_index = word_pair[1]
            try:
                word_origins = self.word_origins_dic[word]
                if square_index not in word_origins:
                    word_origins.append(square_index)
                    self.datum[square_index].add_word()
                self.word_origins_dic[word] = word_origins
            except KeyError:
                #new word
                self.word_origins_dic[word] = [square_index]
                self.datum[square_index].add_word()
                self.total_words += 1


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

    def __init__(self, root):
        self.settings = Settings()
        self.master = root
        self.model_data = ModelData()
        self.model = None
        self.flasher = None
        self.view = View(root, self.settings)
        screen_shot_state = False
        self.screen_shot = ScreenShot(PzlG.app_name,
                                      'dump_', screen_shot_state)
        self.active = False
        self.start_game()

    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 == 1:
            if button_index == 0:
                self.start_game()
            elif button_index == 1:
                if self.active:
                    self.view.game_over()
                    self.view.clock_stop()
                    self.view.show_remainder(self.model.word_origins_dic,
                                             self.model.total_words,
                                             self.model.found_words)
                    self.screen_shot.snap()
                    self.active = False

    def start_game(self):
        self.model = Model(self.model_data)
        self.model.new_game()
        self.view.new_game(self.model.datum)
        self.view.game_on()
        self.add_callbacks()
        self.view.board.entry_box.clear()
        self.view.update_score_box(0)
        self.flasher = Flasher(self.view.clock.box.label,
                               self.master, self.settings)
        self.active = True

    def service_keyboard(self, text):
        """
        User has typed in a word (text)
        """
        if self.active:
            PzlG.logger.logger.info("service_keyboard " + text)
            user_word = text.upper()
            self.model.this_word = user_word
            self.view.board.entry_box.clear()
            good_word, word_origins = \
                self.model.good_user_word(user_word)
            self.model.new_word()
            if good_word:
                self.view.board.list_box.box.insert('end', user_word)
                for square_index in word_origins:
                    self.view.board.update_square(square_index)
                self.view.update_score_box(self.model.found_words)
                self.model.subtract_word(user_word)
            self.check_game_over()

    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 == 'crossover':
            self.set_crossover_mode()
        elif name == 'grid_size':
            self.set_grid_size()
        elif name == 'min_word_length':
            self.set_min_word_length()
        elif name == 'game_type':
            self.set_game_type()
        elif name == 'game_time':
            self.set_game_time()
        elif name == 'input_mode':
            self.set_input_mode()
        elif name == 'font_size':
            self.set_font_size()
        elif name == 'save_settings':
            self.settings.preferences.save()
        elif name == 'about':
            self.view.about()
        elif name == 'help':
            self.view.help()

    def square_event(self, square_index, mouse_index):
        """
        Respond to mouse cursor in grid
        """
        if self.active:
            if self.settings.dic['input_mode'] == 'mouse':
                if mouse_index == 1:
                    # add letter
                    letter = self.model.add_letter(square_index)
                    self.view.board.entry_box.add_letter(letter)
                    self.screen_shot.snap()
                elif mouse_index == 2:
                    #delete letter
                    self.model.delete_letter()
                    self.view.board.entry_box.delete_letter()
                elif mouse_index == 3:
                    # finished word
                    self.view.board.entry_box.clear()
                    user_word = self.model.this_word
                    good_word, word_origins = \
                        self.model.good_user_word(user_word)
                    self.model.new_word()
                    if good_word:
                        self.view.board.list_box.box.insert('end', user_word)
                        for square_index in word_origins:
                            self.view.board.update_square(square_index)
                        self.view.update_score_box(self.model.found_words)
                        self.model.subtract_word(user_word)
                        self.screen_shot.snap()
                self.check_game_over()

    def add_callbacks(self):
        self.view.add_menu_callback(self.service_menu)
        self.view.add_toolbar_callback(self.service_toolbar)
        self.view.board.entry_box.add_callback(self.service_keyboard)
        self.view.board.add_callback(self.square_event)
        self.view.clock.callback = self.check_game_over

    def set_input_mode(self):
        if self.settings.dic['input_mode'] == 'mouse':
#            self.unbind('Any-KeyPress')
            self.view.board.entry_box.deactivate()
        else:
            self.view.board.entry_box.activate()

    def set_game_type(self):
        self.settings.max_time = None
        if self.settings.dic['game_type'] == 'against_clock':
            self.settings.max_time = int(self.settings.dic['game_time'])

    def set_game_time(self):
        self.settings.max_time = int(self.settings.dic['game_time'])

    def set_crossover_mode(self):
        if self.settings.dic['crossover'] == 'crossover_off':
            Sgg.crossover = False
        else:
            Sgg.crossover = True

    def set_grid_size(self):
        Sgg.grid_size = int(self.settings.dic['grid_size'])

    def set_min_word_length(self):
        Sgg.min_word_length = int(self.settings.dic['min_word_length'])


    def set_font_size(self):
        self.settings.do_scaling()
        self.start_game()

    def check_game_over(self):
        if self.settings.dic['game_type'] == 'against_clock':
            if self.view.clock.finished:
                self.active = False
        else:
            if self.model.all_words_found():
                self.active = False
        if not self.active:
            self.view.game_over()
            self.view.clock_stop()
            self.view.clock.finished = False
            self.view.show_remainder(self.model.word_origins_dic,
                                     self.model.total_words,
                                     self.model.found_words)
            self.flasher.flash()


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