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

# Copyright (C) 2014 Rod Engerstad


import random
from random import shuffle
import Tkinter as tk
import tkColorChooser
from bisect import bisect_right

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 ScoreBox
from pzl_lib import Toolbar
from pzl_lib import Menu
from pzl_lib import OpenFileI
from pzl_lib import LFWidgetB
from pzl_lib import ClockBox
from pzl_lib import ViewB

class Settings(SettingsB):

    def __init__(self):
        SettingsB.__init__(self)
        self.entry_box_bg = self.white
        self.entry_box_fg = self.black
        self.original_entry_box_width = 18
        self.entry_box_width = self.original_entry_box_width
        self.original_square_size = 36
        self.square_size = self.original_square_size
        self.letter_font_size = -18
        self.counter_font_size = -24
        self.score_box_width = 10
        self.clock_box_width = 6
        self.tick_interval = 1
        self.dic = {}
        self.dic['players_tile_colour'] = '#e7d57c'
        self.dic['machine_tile_colour'] = 'red'
        self.dic['single_letter_colour'] = '#82c026'
        self.dic['double_letter_colour'] = '#76b5e1'
        self.dic['triple_letter_colour'] = '#086fc5'
        self.dic['double_word_colour'] = '#d6a399'
        self.dic['triple_word_colour'] = '#e02f2c'
        self.dic['tile_text_colour'] = self.black
        self.dic['set_colour'] = 'players_tile_colour'
        self.dic['game_iq'] = '50'
        self.dic['game_time'] = '30'
        self.dic['font_size'] = '2'
        self.menu_items = []
        self.preferences = Preferences(self.dic)
        self.preferences.load()
        self.max_time = int(self.dic['game_time']) * 60
        self.game_iq = float(self.dic['game_iq'])
        self.load_menus()
        self.count = len(self.menu_items)
        self.set_fonts()
        self.make_help()
        self.make_tooltip()
        self.boardscores = []
        self.set_board_scores()

    def make_tooltip(self):
        self.tooltip_text = [_('New game'), _('Give Hint/Position Hint'), _('Show solution'), _('Swap Letters'), _('Stop Clock')]

    def make_help(self):
        self.help_text = _("The jigsaw icon at the top left of the window contains a menu which allows games to be saved and loaded, allows the maximum time to be changed and for the difficulty level to be set. The difficulty level is termed the program's IQ, and ranges from 10 to 100. Winning against IQ 100 is very hard. The Toolbar buttons next to the menu start a new game (dots), request a hint (wand), request the computer take over your moves (sad smiley), request letter swapping (pair of arrows), and stop the player's clock (clockface). Start a new game by left-clicking on the dots icon. The program randomly decides who gets first turn. If the clock is ticking, it is you.  Type your word in the Entrybox to the left of the letter rack. When you hit 'Enter' the program will check the word. If the Entrybox turns double word score colour the word is unacceptable. You can edit it or enter another. If the Entrybox is white the word is accepted. Note that even if you are only adding a single letter to an existing word you must enter the complete word. Now you have to tell the program where to put the word. This is done by clicking the board square which is at the start of the word. A left-click means the word should be placed across the board, a right-click means it should go down the board. If the position and direction are accepted the letters will be drawn on the board. If the Entrybox turns double letter score colour you've got the position or direction wrong. Note, you must enter the word before defining its position on the board and once a word is accepted it cannot be changed. Once the word has appeared on the board punch the program's clock by clicking on the clockface in the Toolbar. This will stop your clock and update your score. The program will then take its turn and restart your clock. If you are unable to go or are unhappy with your current letters you can replace any number of them (while there are still at least 7 in the notional sack) by clicking on the swap icon and then typing the letters you want to discard into the Entrybox. Again finish with 'Enter'. If the Entrybox turns triple word score colour the letters contain an error. Otherwise the letters will be replaced. Punch the program's clock. When it is your turn and a move exists you can request a hint by clicking on the wand in the Toolbar. A word will appear in the Entrybox. A second click on the wand will put the word on the board. Punch the program's clock. When the clock flashes and the clock icon is greyed out the game is over and the final scores are shown.")

    def load_menus(self):
        self.menu_items.append(MenuItem(
            _('Game Time'),
            ['20',
             '25',
             '30',
             '35',
             '40',
             '45',
             '50',
             '55',
             '60'],
            '',
            ['20',
             '25',
             '30',
             '35',
             '40',
             '45',
             '50',
             '55',
             '60'],
            'game_time'
        ))

        self.menu_items.append(MenuItem(
            _('Game IQ'),
            ['10',
             '20',
             '30',
             '40',
             '50',
             '60',
             '70',
             '80',
             '90',
             '100'],
            '',
            ['10',
             '20',
             '30',
             '40',
             '50',
             '60',
             '70',
             '80',
             '90',
             '100'],
            'game_iq'
        ))

        self.menu_items.append(MenuItem(
            _('Set Colour'),
            [_('Players Tiles'),
             _('Machines Tiles'),
             _('Tile text'),
             _('Single Letter Square'),
             _('Double Letter Square'),
             _('Triple Letter Square'),
             _('Double Word Square'),
             _('Triple Word Square')],
            'N',
            ['players_tile_colour',
             'machine_tile_colour',
             'tile_text_colour',
             'single_letter_colour',
             'double_letter_colour',
             'triple_letter_colour',
             'double_word_colour',
             'triple_word_colour'],
            'set_colour'
        ))
        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 Game'),
            '',
            '',
            '',
            'save_game'
        ))
        self.menu_items.append(MenuItem(
            _('Load Game'),
            '',
            '',
            '',
            'load_game'
        ))
        self.menu_items.append(MenuItem(
            _('Save Settings'),
            '',
            '',
            '',
            'save_settings'
        ))
        self.menu_items.append(MenuItem(
            _('Help'),
            '',
            '',
            '',
            'help'
        ))
        self.menu_items.append(MenuItem(
            _('About'),
            '',
            '',
            '',
            'about'
        ))

    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]
        else:
            pass
        return text, title

    def set_board_scores(self):
        """
        One day might want to allow user to choose scores
        so initialise them here. At present only used by
        View to colour board.
        """
        self.boardscores = [
            4, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 4,
            0, 3, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 3, 0,
            0, 0, 3, 0, 0, 0, 1, 0, 1, 0, 0, 0, 3, 0, 0,
            1, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 1,
            0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0,
            0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0,
            0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0,
            4, 0, 0, 1, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 4,
            0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0,
            0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0,
            0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0,
            1, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 1,
            0, 0, 3, 0, 0, 0, 1, 0, 1, 0, 0, 0, 3, 0, 0,
            0, 3, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 3, 0,
            4, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 4]


class Rack(tk.Frame):

    def __init__(self, parent, settings):
        self.settings = settings
        self.size = self.settings.square_size
        self.cells = [None] * 7
        tk.Frame.__init__(self, parent, relief='sunken', bg='white')
        self.grid(row=0, column=0, sticky='W')
        self.make_rack()

    def make_rack(self):
        self.cells = [None] * 7
        width = self.size
        height = self.size
        row = 0
        for i in range(7):
            cell = tk.Frame(
                self,
                bd=4,
                highlightthickness=1,
                width=width,
                height=height,
                bg='white',
                )
            cell.grid(row=row, column=i)
            cell.grid_propagate(0)
            self.cells[i] = cell
        self.update_idletasks()

    def my_destroy(self):
        for cell in self.cells:
            if cell is not None:
                cell.destroy()
        self.destroy()

    def colour_bg(self, cell_index, bg):
        cell = self.cells[cell_index]
        if cell is not None:
            cell.configure(bg=bg)

    def set_relief(self, cell_index, relief):
        cell = self.cells[cell_index]
        if cell is not None:
            cell.configure(relief=relief)


class BoardSquare(tk.Frame):
    """
    When the board is active squares without tiles will respond
    to enter and leave with raise and flat reliefs. Those with tiles
    will not raise and flatten.
    When board is active all squares will respond to clicks by reporting
    their square_index
    """
    def __init__(self, parent, settings, bg, square_index):
        self.settings = settings
        self.size = self.settings.square_size
        self.square_index = square_index
        self.callback = None
        self.board_active = False
        self.active = False
        self.occupied = False
        width = self.size
        height = self.size
        tk.Frame.__init__(self,
                          parent,
                          bd=4,
                          relief='flat',
                          highlightthickness=1,
                          bg=bg,
                          width=width,
                          height=height)
        self.add_bindings()

    def my_destroy(self):
        self.destroy()

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

    def add_bindings(self):
        self.bind("<Enter>", self.command)
        self.bind("<Leave>", self.command)
        self.bind("<ButtonRelease-1>", self.command)
        self.bind("<ButtonRelease-3>", self.command)

    def command(self, event):
        if self.board_active:
            if event.type == "7" and not self.occupied:
                self.active = True
                relief = 'raised'
                self.config(relief=relief)
            elif event.type == "8" and not self.occupied:
                self.active = False
                relief = 'flat'
                self.config(relief=relief)
            elif event.type == "5" and self.active:
                mouse_index = event.num
                self.callback(self.square_index,
                              mouse_index)

    def colour_bg(self, bg):
        self.configure(bg=bg)

    def occupy(self):
        self.occupied = True

    def activate(self):
        self.board_active = True

    def deactivate(self):
        self.board_active = False


class LFWidget(LFWidgetB):
    """
    Used for scrabble and codeword
     """
    def __init__(self, parent, square_index, big_text,
                 big_font, small_text, small_font,
                 bg, fg, labelanchor, relief, bind, size, func):
        LFWidgetB.__init__(self, parent, square_index, big_text,
                           big_font, small_text, small_font,
                           bg, fg, labelanchor, relief, bind, size, func)

        if self.bind:
            self.add_bindings()

    def add_bindings(self):
        self.frame_widget.bind("<Enter>", self.command)
        self.frame_widget.bind("<Enter>", self.command)
        self.frame_widget.bind("<Leave>", self.command)
        self.frame_widget.bind("<Leave>", self.command)
        self.frame_widget.bind("<ButtonRelease-1>", self.command)
        self.frame_widget.bind("<ButtonRelease-3>", self.command)
        self.lf_widget.bind("<ButtonRelease-1>", self.command)
        self.lf_widget.bind("<ButtonRelease-3>", self.command)
        self.label_widget.bind("<ButtonRelease-1>", self.command)
        self.label_widget.bind("<ButtonRelease-3>", self.command)

    def command(self, event):
        if self.board_active:
            if event.type == "7":
                self.active = True
            elif event.type == "8":
                self.active = False
            if event.type == "5" and self.active:
                mouse_index = event.num
                self.callback(self.square_index,
                              mouse_index)


class Board(tk.Frame):
    def __init__(self, parent, settings):
        self.settings = settings
        self.tile_callback = None
        self.size = self.settings.square_size
        self.squares = []
        self.laid_tiles = []
        self.grid_colours = []
        self.players_tile_colour = None
        self.machine_tile_colour = None
        self.set_colours()
        bg = self.settings.green
        tk.Frame.__init__(self, parent, relief='raised',
                          bd=2, bg=bg)
        self.last_word_index = 0
        square_index = 0
        for row in range(15):
            for column in range(15):
                bg = self.grid_colours[self.settings.boardscores[square_index]]
                square = BoardSquare(parent, self.settings, bg, square_index)
                square.grid(row=row, column=column)
                square.grid_propagate(0)
                self.squares.append(square)
                square_index += 1
        self.grid(row=0, column=0)

    def my_destroy(self):
        for square in self.squares:
            square.my_destroy()
        self.destroy()

    def deactivate(self):
        for square in self.squares:
            square.deactivate()
        for tile in self.laid_tiles:
            tile.deactivate()

    def activate(self):
        for square in self.squares:
            square.activate()
        for tile in self.laid_tiles:
            tile.activate()

    def add_callback(self, func):
        # NB Setting tile_callback here as well
        self.tile_callback = func
        square_index = 0
        for row in range(15):
            for column in range(15):
                self.squares[square_index].add_callback(func)
                square_index += 1

    def set_colours(self):
        self.grid_colours = [
            self.settings.dic['single_letter_colour'],
            self.settings.dic['double_letter_colour'],
            self.settings.dic['triple_letter_colour'],
            self.settings.dic['double_word_colour'],
            self.settings.dic['triple_word_colour'],
            self.settings.dic['triple_word_colour']]
        self.players_tile_colour = self.settings.dic['players_tile_colour']
        self.machine_tile_colour = self.settings.dic['machine_tile_colour']

    def initial_tiles(self, squares, bg):
        relief = 'raised'
        fg = self.settings.dic['tile_text_colour']
        big_font = self.settings.letter_font
        small_font = self.settings.code_font
        labelanchor = 'es'
        bind = True
        for square_index in range(225):
            if squares[square_index] != ' ':
                letter = squares[square_index]
                tile_frame = self.squares[square_index]
                j = Model.alphabet.index(letter)
                small_text = Model.alphabet_scores[j]
                big_text = letter
                tile_widget = LFWidget(tile_frame, square_index, big_text,
                                       big_font, small_text, small_font,
                                       bg, fg, labelanchor, relief, bind,
                                       self.size, self.tile_callback)
                self.laid_tiles.append(tile_widget)

    def lay_tiles(self, hit, player):
        """
        self.laid_tiles is used to store the widgets of the tiles which
        are put on the board.
        """
        if hit.strip_index < 15:
            inc = 1
            square_index = 15 * hit.strip_index + hit.strip_offset
        else:
            inc = 15
            square_index = 15 * hit.strip_offset + hit.strip_index - 15

        if player == 0:
            old_tile_colour = self.settings.dic['players_tile_colour']
        else:
            old_tile_colour = self.settings.dic['machine_tile_colour']
        # reset the colour of the last tiles laid
        for i in range(self.last_word_index, len(self.laid_tiles)):
            self.laid_tiles[i].colour_bg(old_tile_colour)
        self.last_word_index = len(self.laid_tiles)
        """
        Add new tiles to the board
        """
        bg = 'white'
        relief = 'raised'
        fg = self.settings.dic['tile_text_colour']
        big_font = self.settings.letter_font
        small_font = self.settings.code_font
        labelanchor = 'es'
        bind = True
        for i in range(len(hit.text)):
            if hit.text[i] == ' ':
                letter = hit.word[i]
                self.squares[square_index].occupy()
                tile_frame = self.squares[square_index]
                j = Model.alphabet.index(letter)
                small_text = Model.alphabet_scores[j]
                big_text = letter
                tile_widget = LFWidget(tile_frame, square_index, big_text,
                                       big_font, small_text, small_font,
                                       bg, fg, labelanchor, relief, bind,
                                       self.size, self.tile_callback)

                self.laid_tiles.append(tile_widget)
            square_index += inc
        self.deactivate()


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.image_dic = {}
        self.make_image_dic()
        self.players_tile_colour = self.settings.dic['players_tile_colour']
        self.machine_tile_colour = self.settings.dic['machine_tile_colour']
        self.player_frame = None
        self.player_rack_frame = None
        self.player_rack = None
        self.score_frame0 = None
        self.score_frame1 = None
        self.score_box0 = None
        self.score_box1 = None
        self.tile_callback = None
        self.do_layout(self)
        self.clock = None
        self.rack_tiles = []
        self.entry_box.activate()

    def make_image_dic(self):
        """
        image, button_index, start
        """
        dic = {}
        dic['start'] = [self.images.start_scrabbler, 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['swap'] = [self.images.swap, 3, True]
        dic['clock'] = [self.images.clock, 4, True]
        dic['length'] = [None, 5, False]
        self.image_dic = dic

    def do_layout(self, root):
        bg = self.settings.grey
        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.clock_box = ClockBox(self, self.number_frame)
        self.clock_box.grid(row=0, column=0, sticky='NSEW')
        self.score_frame0 = tk.Frame(self.number_frame, relief='flat',
                                     bg=bg)
        self.score_frame0.grid(row=0, column=2, sticky='NSEW')
        self.score_frame1 = tk.Frame(self.number_frame, relief='flat',
                                     bg=bg)
        self.score_frame1.grid(row=0, column=1, sticky='NSEW')
        self.score_box0 = ScoreBox(self.settings, self.score_frame0)
        self.score_box0.grid(row=0, column=0, sticky='NSEW')
        self.score_box1 = ScoreBox(self.settings, self.score_frame1)
        self.score_box1.grid(row=0, column=1, sticky='NSEW')
        self.make_player_frame()

    def make_player_frame(self):
        if self.top_frame is not None:
            self.top_frame.destroy()
            self.player_frame.destroy()
        bg = self.settings.green
        self.player_frame = tk.Frame(self.outer_frame, relief='flat',
                                     bd=1, bg=bg)

        self.player_frame.grid(row=1, column=0, sticky='NSEW')
        self.entry_box = EntryBox(self.player_frame,
                                  self.settings)
        self.player_rack_frame = tk.Frame(self.player_frame,
                                          relief='raised', bd=2,
                                          bg='white')
        self.player_rack_frame.grid(row=0, column=1, sticky='W')
        self.player_rack = Rack(self.player_rack_frame, self.settings)
        self.top_frame = tk.Frame(self.outer_frame, relief='flat',
                                  bd=1, bg=bg)
        self.top_frame.grid(row=2, column=0, sticky='NW')
        self.board = None

    def make_board(self):
        if self.board is not None:
            self.board.my_destroy()
        self.board = Board(self.top_frame, self.settings)

    def update_rack(self, tiles):
        for i in range(len(self.rack_tiles)):
            tile_widget = self.rack_tiles[i]
            tile_widget.my_destroy()
            self.player_rack.colour_bg(i, 'white')
            self.player_rack.set_relief(i, 'flat')

        self.rack_tiles = []

        bg = self.settings.dic['players_tile_colour']
        relief = 'raised'
        fg = self.settings.dic['tile_text_colour']
        big_font = self.settings.letter_font
        small_font = self.settings.code_font
        labelanchor = 'es'
        bind = False
        size = self.settings.square_size
        for i in range(len(tiles)):
            letter = tiles[i]
            if letter is not None:
                square_index = i
                big_text = letter
                tile_frame = self.player_rack.cells[i]
                j = Model.alphabet.index(letter)
                small_text = Model.alphabet_scores[j]
                tile_widget = LFWidget(tile_frame, square_index, big_text,
                                       big_font, small_text, small_font,
                                       bg, fg, labelanchor, relief, bind,
                                       size, self.tile_callback)
                self.rack_tiles.append(tile_widget)

    def add_board_callback(self, func):
        # NB Setting tile_callback here as well
        self.tile_callback = func
        self.board.add_callback(func)

    def update_score_box(self, player, score):
        if player == 0:
            self.score_box0.my_update(score)
        else:
            self.score_box1.my_update(score)

    def hint_available(self):
        self.toolbar.key_on('hint')

    def hint_active(self):
        self.toolbar.key_off('show_solution')
        self.toolbar.key_off('swap')

    def swap_off(self):
        self.toolbar.key_off('swap')

    def player_entering(self):
        self.toolbar.key_off('hint')
        self.toolbar.key_off('show_solution')
        self.toolbar.key_off('swap')

    def sg_open_file(self, write, title):
        """
        Use gui to open and file for reading or writing
        """
        init_dir = PzlG.app_saves
        filetypes = [('All files', '*')]
        this_file = OpenFileI(self, init_dir, filetypes, write, title)
        return this_file.my_file


class Hit(object):

    def __init__(self, strip_index, strip_offset, anchor, text, word,
                 score):
        self.strip_index = strip_index
        self.strip_offset = strip_offset
        self.anchor = anchor
        self.score = score
        self.word = word
        self.text = text


class Strip(object):

    """
    A board comprises 30 Strips: 0-14 are rows, 15-29 are columns
    so that the actual text is duplicated.
    A Strip consists of:
    text[16] (1 sentinel)
    lettersOK[16] the letters compatible with the perpendicular strip
    scoresAbove[15] the cumulative scores from letters "above" the strip
    scoresBelow[15] the cumulative scores from letters "below" the strip
    anchors[]
    These allowed letters and scores must be calculated by scanning along
    the strips perpendicular to this strip.
    """

    def __init__(self, strip_index):
        self.index = strip_index
        self.text = [' '] * 16
        self.scores_above = [0] * 15
        self.scores_below = [0] * 15
        self.letters_ok = [[True] for i in range(16)]
        self.letters_ok[15] = [False]
        self.anchors = []


class Model(object):
    """
    The algorithm used is my interpretation of
    A. Appel and G. Jacobson, ‘The world’s fastest Scrabble program’,
    Commun. ACM, 31, (5), 572–578, (1988).
    The dawg was generated using Steve Hanov's method:
    http://stevehanov.ca/blog/index.php?id=115

    A Model comprises 30 Strips: 0-14 are rows, 15-29 are columns
    so that the actual text is duplicated.
    """

    end_of_node_shift = 5
    end_of_word_shift = 6
    offset_shift = 7
    letter_mask = 0o37
    empty = ' '
    end_of_data = '.'
    blank_tile = ' '

    alphabet = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
                'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
                'U', 'V', 'W', 'X', 'Y', 'Z', '.', ' ', '$', '#',
                'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
                'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
                'u', 'v', 'w', 'x', 'y', 'z']

    alphabet_scores = [1, 3, 3, 2, 1, 4, 2, 4, 1, 8, 5, 1, 3, 1,
                       1, 3, 10, 1, 1, 1, 1, 4, 4, 8, 4, 10, 0, 0,
                       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    board_scores = [
        [4, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 4],
        [0, 3, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 3, 0],
        [0, 0, 3, 0, 0, 0, 1, 0, 1, 0, 0, 0, 3, 0, 0],
        [1, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 1],
        [0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0],
        [0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0],
        [0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0],
        [4, 0, 0, 1, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 4],
        [0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0],
        [0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0],
        [0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0],
        [1, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 1],
        [0, 0, 3, 0, 0, 0, 1, 0, 1, 0, 0, 0, 3, 0, 0],
        [0, 3, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 3, 0],
        [4, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 4],
        [4, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 4],
        [0, 3, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 3, 0],
        [0, 0, 3, 0, 0, 0, 1, 0, 1, 0, 0, 0, 3, 0, 0],
        [1, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 1],
        [0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0],
        [0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0],
        [0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0],
        [4, 0, 0, 1, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 4],
        [0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0],
        [0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0],
        [0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0],
        [1, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 1],
        [0, 0, 3, 0, 0, 0, 1, 0, 1, 0, 0, 0, 3, 0, 0],
        [0, 3, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 3, 0],
        [4, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 4]
    ]
    full_sack = [
        'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A',
        'B', 'B',
        'C', 'C',
        'D', 'D', 'D', 'D',
        'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E',
        'F', 'F',
        'G', 'G', 'G',
        'H', 'H',
        'I', 'I', 'I', 'I', 'I', 'I', 'I', 'I', 'I',
        'J',
        'K',
        'L', 'L', 'L', 'L',
        'M', 'M',
        'N', 'N', 'N', 'N', 'N', 'N',
        'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O',
        'P', 'P',
        'Q',
        'R', 'R', 'R', 'R', 'R', 'R',
        'S', 'S', 'S', 'S',
        'T', 'T', 'T', 'T', 'T', 'T',
        'U', 'U', 'U', 'U',
        'V', 'V',
        'W', 'W',
        'X',
        'Y', 'Y',
        'Z',
        ' ', ' ',
        ]

    def __init__(self, model_data):
        self.model_data = model_data
        self.rack = [[None] * 7, [None] * 7]
        self.scores = [[0, 0], [0, 0]]
        self.strip = []
        self.sack = []
        self.scores_above = None
        self.scores_below = None
        self.current_rack = []
        self.current_rack_in = []
        self.strip_index = None
        self.anchor = None
        self.text = ''
        self.ok = None
        self.extended = None
        self.hits = []
        self.rescan = [True] * 30
        for i in range(30):
            self.strip.append(Strip(i))

    def update_score(self, player, score):
        self.scores[player][1] = score
        self.scores[player][0] += score

    def tiles_score(self, tiles):
        score = 0
        for i in range(len(tiles)):
            j = tiles[i]
            j = Model.alphabet.index(j)
            j = Model.alphabet_scores[j]
            score += j
        return score

    def finalise_scores(self):
        sub = 0
        tiles = self.rack[0]
        if not tiles:
            sub = self.tiles_score(tiles)
            self.update_score(0, sub)
            self.update_score(1, -1 * sub)
        else:
            tiles = self.rack[1]
            sub = self.tiles_score(tiles)
            self.update_score(1, sub)
            self.update_score(0, -1 * sub)

    def prepare_sack(self):
        self.sack = []
        self.sack[:] = Model.full_sack
        shuffle(self.sack)

    def prepare_rack(self):
        self.rack[0] = self.get_tiles(7)
        self.rack[1] = self.get_tiles(7)

    def get_tiles(self, tile_count):
        ret = []
        for i in range(min(len(self.sack), tile_count)):
            ret.append(self.sack.pop())
        return ret

    def replenish_tiles(self, player, used):
        tiles = self.rack[player]
        for i in used:
            if i in tiles:
                tiles.remove(i)
        new_tiles = self.get_tiles(len(used))
        ret = False
        if new_tiles:
            for i in new_tiles:
                tiles.append(i)
            ret = True
        return ret

    def replace_tiles(self, word0):
        new_tiles = self.get_tiles(len(word0))
        ret = False
        if new_tiles:
            for i in word0:
                self.rack[1].remove(i)
            self.rack[1].extend(new_tiles)
            self.return_tiles(word0)
            ret = True
        return ret

    def return_tiles(self, tiles):
        """
        Put swapped tiles back in sack and shuffle it
        """
        for i in range(len(tiles)):
            self.sack.append(tiles[i])
        shuffle(self.sack)

    def set_rack(self, player):
        self.current_rack = []
        self.current_rack = self.rack[player][:]
        self.current_rack_in = []
        self.current_rack_in = self.rack[player][:]

    def map_indexes(self, strip_index, strip_offset):
        """
        switch between strips and offsets
        """

        if strip_index < 15:
            return (15 + strip_offset, strip_index)
        else:
            return (strip_offset, strip_index - 15)

    def add_word(self, strip_index, strip_offset, word):
        text = self.strip[strip_index].text
        ok = self.strip[strip_index].letters_ok
        self.rescan[strip_index] = True
        for i in range(len(word)):
            if word[i] != ' ':
                text[strip_offset + i] = word[i].upper()
                ok[strip_offset + i] = [False]
        (strip_index, strip_offset) = self.map_indexes(
            strip_index,
            strip_offset)

        # text = self.strip[stripIndex].text
        # need to rescan ends of word too

        if strip_index > 0:
            self.rescan[strip_index - 1] = True
        for i in range(len(word)):
            if word[i] != ' ':
                self.strip[strip_index].text[strip_offset] = \
                    word[i].upper()
                self.strip[strip_index].letters_ok[strip_offset] = \
                    [False]
                self.rescan[strip_index] = True
            strip_index += 1
        if strip_index < 15:
            self.rescan[strip_index] = True

    def add_board(self, squares):
        square_index = 0
        for strip_index in range(15):
            text = self.strip[strip_index].text
            ok = self.strip[strip_index].letters_ok
            self.rescan[strip_index] = True
            for strip_offset in range(15):
                text[strip_offset] = squares[square_index]
                if squares[square_index] != ' ':
                    ok[strip_offset] = [False]
                square_index += 1

        for strip_index in range(15, 30):
            square_index = strip_index - 15
            text = self.strip[strip_index].text
            ok = self.strip[strip_index].letters_ok
            self.rescan[strip_index] = True
            for strip_offset in range(15):
                text[strip_offset] = squares[square_index]
                if squares[square_index] != ' ':
                    ok[strip_offset] = [False]
                square_index += 15

    def load_game(self, loaded):
        self.update_score(0, loaded.machine_score)
        self.update_score(1, loaded.player_score)
        self.rack[0] = loaded.machine_tiles
        self.rack[1] = loaded.player_tiles
        self.add_board(loaded.squares)
        self.prepare_sack()
        for tile in loaded.used_tiles:
            if tile in self.sack:
                self.sack.remove(tile)
            elif tile.islower():
                self.sack.remove(' ')

    def first_turn_anchors(self):
        self.strip[7].anchors.append(7)
        self.strip[22].anchors.append(7)

    def update(self):
        for i in range(30):
            if self.rescan[i]:
                self.strip_index = i
                self.cross_checks(
                    self.strip[i].text,
                    self.strip[i].anchors,
                    i,
                    )
        for i in range(30):
            if self.rescan[i]:
                for j in range(15):
                    if self.strip[i].letters_ok[j] != [False]:
                        if self.strip[i].letters_ok[j] != [True]:
                            self.strip[i].anchors.append(j)
                self.rescan[i] = False

        # anchors can get duplicated and out of ascending order
        # solve that here

        for i in range(30):
            p = self.strip[i].anchors
            if len(p) > 1:
                p.sort()
                t = []
                t.append(p[0])
                k = 0
                for j in range(1, len(p)):
                    if p[j] != t[k]:
                        t.append(p[j])
                        k += 1
                self.strip[i].anchors = t

    def get_record(self, record_index):
        record = self.model_data.dawg[record_index]
        end_of_node = False
        if record & 1 << Model.end_of_node_shift:
            end_of_node = True
        end_of_word = False
        if record & 1 << Model.end_of_word_shift:
            end_of_word = True
        letter_index = record & Model.letter_mask
        letter = Model.alphabet[letter_index]
        next_record = record >> Model.offset_shift
        return (letter, next_record, end_of_word, end_of_node)

    def letter_in_node(self, record, letter_in):
        in_node = False
        while True:
            (letter, next_record, end_of_word, end_of_node) = \
                self.get_record(record)
            if letter == letter_in:
                in_node = True
                break
            if end_of_node:
                break
            record += 1
        return (in_node, next_record, end_of_word)

    def find_word(self, word, next_record):
        for letter in word:
            (in_node, next_record, end_of_word) = \
                self.letter_in_node(next_record, letter)
            if not in_node:
                break
        return (in_node, next_record, end_of_word)

    def find_word2(self, word, node):
        """
        example PA-ABLE
        node is the node pointed at by PA
        want the letters for the records from this node which point to ABLE
        """

        letters = []
        record = node
        while True:
            (letter, next_record, end_of_word, end_of_node) = \
                self.get_record(record)
            (in_node, next_record, end_of_word) = self.find_word(
                word,
                next_record)
            if in_node is True and end_of_word:
                letters += letter
            if end_of_node:
                break
            record += 1
        return letters

    def find_word3(self, word):
        """
        example BUM.
        want the letters for the records from the node after BUM
        which end words
        eg BUMS (S) and BUMP (P)
        """

        letters = []
        next_record = 0
        (in_node, record, end_of_word) = self.find_word(
            word,
            next_record)
        while True:
            (letter, next_record, end_of_word, end_of_node) = \
                self.get_record(record)
            if in_node is True and end_of_word is True and letter \
               != Model.end_of_data:
                letters += letter
            if end_of_node:
                break
            record += 1
        return letters

    def encode_strip(self, text):
        a = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        b = [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]
        c = [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]
        d = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        e = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        t = 0
        s = -1
        for i in range(15):
            if text[i] == Model.empty:
                s = -1
                if text[i + 1] != Model.empty:
                    a[i] = 2
                t = 0
            else:
                a[i] = 1
                if s == -1:
                    s = i
                b[i] = s
                j = Model.alphabet.index(text[i])
                t += Model.alphabet_scores[j]
                d[i] = t
        s = -1
        t = 0
        for i in range(14, -1, -1):
            if text[i] == Model.empty:
                s = -1
                if text[i - 1] != Model.empty:
                    a[i] += 3
                t = 0
            else:
                if s == -1:
                    s = i
                c[i] = s
                j = Model.alphabet.index(text[i])
                t += Model.alphabet_scores[j]
                e[i] = t

        return (a, b, c, d, e)

    def cross_checks(self, text, anchors, strip_index):
        """
        get OK letters, scores and anchors
        """

        (a, b, c, d, e) = self.encode_strip(text)
        for i in range(15):
            if a[i] == 2:

                # left end of word so perpendicular restricted letters
                # but not for this strip
                # but here we do set anchors

                anchors.append(i)
                sub_word = text[b[i + 1]:c[i + 1] + 1]
                ok = self.find_word2(sub_word, 0)
                (j, k) = self.map_indexes(strip_index, i)
                self.strip[j].letters_ok[k] = ok
                self.rescan[j] = True
                self.strip[j].scores_above[k] = e[i + 1]
            elif a[i] == 3:

                # right end of word so perpendicular restricted letters
                # but not for this strip
                # but here we do set anchors

                anchors.append(i)
                sub_word = text[b[i - 1]:c[i - 1] + 1]
                ok = self.find_word3(sub_word)
                (j, k) = self.map_indexes(strip_index, i)
                self.strip[j].letters_ok[k] = ok
                self.rescan[j] = True
                self.strip[j].scores_below[k] = d[i - 1]
            elif a[i] == 5:

                # middle of word eg BULL HIT

                anchors.append(i)
                sub_word1 = text[b[i - 1]:c[i - 1] + 1]
                sub_word2 = text[b[i + 1]:c[i + 1] + 1]
                (in_node, next_record, end_of_word) = \
                    self.find_word(sub_word1, 0)
                (j, k) = self.map_indexes(strip_index, i)
                if in_node:
                    ok = self.find_word2(sub_word2, next_record)
                    self.strip[j].letters_ok[k] = ok
                    self.rescan[j] = True
                self.strip[j].scores_above[k] = e[i + 1]
                self.strip[j].scores_below[k] = d[i - 1]

    def letter_is_ok(self, strip_offset, letter):

        # is this letter in the allowed set for cell cell_index?

        ret = False
        if self.ok[strip_offset] == [True] or letter \
           in self.ok[strip_offset]:
            ret = True
        return ret

    def board_word(self, strip_offset):
        w = ''
        i = strip_offset - 1
        while i > -1:
            if self.text[i] != Model.empty:
                w += self.text[i]
                i -= 1
            else:
                break
        return w[::-1]  # reverses w

    def do_blanks(self, start, word_in):

        # replace blank tiles with lowercase

        my_rack = []
        my_rack = self.current_rack_in[:]
        word = list(word_in)
        len_word = len(word)
        startp = start
        for i in range(len_word):
            if self.text[startp] == Model.empty:
                if word[i] not in my_rack:
                    my_rack.remove(Model.blank_tile)
                    word[i] = word[i].lower()
                else:
                    my_rack.remove(word[i])
            startp += 1
        return ''.join(word)

    def make_hit(self, start, anchor, word):
        len_word = len(word)
        if Model.blank_tile in self.current_rack_in:
            word = self.do_blanks(start, word)
        text = []
        word_score = 1
        letters_used = 0
        score = 0
        score_v = 0
        startp = start
        for i in range(len_word):
            if self.text[startp] == Model.empty:
                letters_used += 1
                s_a = self.scores_above[startp]
                s_b = self.scores_below[startp]
                word_score_v = 1
                bc = self.board_scores[startp]
                j = Model.alphabet.index(word[i])
                t = Model.alphabet_scores[j]
                if bc == 1:
                    t *= 2
                elif bc == 2:
                    t *= 3
                elif bc == 3:
                    word_score *= 2
                    word_score_v = 2
                elif bc == 4:
                    word_score *= 3
                    word_score_v = 3
                elif bc == 5:
                    word_score *= 2
                    word_score_v = 2
                score += t
                s_vt = s_a + s_b
                if s_vt > 0:
                    s_vt += t
                    s_vt *= word_score_v
                    score_v += s_vt
            else:
                j = self.text[startp]
                j = Model.alphabet.index(j)
                t = Model.alphabet_scores[j]
                score += t

            text.append(self.text[startp])
            startp += 1
        score *= word_score
        score += score_v
        if letters_used == 7:
            score += 50
        self.hits.append(Hit(self.strip_index, start, anchor, text,
                             word, score))

    def is_anchored(self, anchor, start, len_word):
        ret = True
        if start + len_word <= anchor:
            ret = False
        if start > 0 and self.text[start - 1] != Model.empty:
            ret = False
        if not self.extended:
            ret = False
        return ret

    def left_part(self, partial_word, record, limit, end_of_word,
                  anchor):
        self.extend_right(partial_word, record, anchor, end_of_word,
                          anchor)
        if limit > 0:
            while True:
                (letter, next_record, end_of_word, end_of_node) = \
                    self.get_record(record)
                if letter in self.current_rack:
                    self.current_rack.remove(letter)
                    self.extended = True
                    self.left_part(partial_word + letter, next_record,
                                   limit - 1, end_of_word, anchor)
                    self.current_rack += letter
                elif letter != Model.end_of_data and Model.blank_tile \
                     in self.current_rack:
                    self.current_rack.remove(Model.blank_tile)
                    self.extended = True
                    self.left_part(partial_word + letter, next_record,
                                   limit - 1, end_of_word, anchor)
                    self.current_rack += Model.blank_tile
                if end_of_node:
                    break
                record += 1

    def extend_right(self, partial_word, record, strip_offset, end_of_word,
                     anchor):
        if self.text[strip_offset] == Model.empty:
            if end_of_word:
                len_word = len(partial_word)
                start = strip_offset - len_word
                if self.is_anchored(anchor, start, len_word):
                    self.make_hit(start, anchor, partial_word)
            while True:
                (letter, next_record, end_of_word, end_of_node) = \
                    self.get_record(record)
                if letter in self.current_rack \
                   and self.letter_is_ok(strip_offset, letter):
                    self.current_rack.remove(letter)
                    self.extended = True
                    self.extend_right(
                        partial_word + letter,
                        next_record, strip_offset + 1, end_of_word,
                        anchor)
                    self.current_rack += letter
                elif letter != Model.end_of_data and Model.blank_tile \
                    in self.current_rack and self.letter_is_ok(
                        strip_offset,
                        letter):
                    self.current_rack.remove(Model.blank_tile)
                    self.extended = True
                    self.extend_right(
                        partial_word + letter,
                        next_record, strip_offset + 1, end_of_word,
                        anchor)
                    self.current_rack += Model.blank_tile
                if end_of_node:
                    break
                record += 1
        else:
            letter = self.text[strip_offset]
            (in_node, next_record, end_of_word) = \
                self.letter_in_node(record, letter)
            if in_node:
                self.extend_right(
                    partial_word + letter, next_record,
                    strip_offset + 1, end_of_word, anchor)

    def take_turn(self):
        self.hits = []
        self.update()
        for i in range(30):
            self.strip_index = i
            self.board_scores = Model.board_scores[i]
            self.text = self.strip[i].text
            self.ok = self.strip[i].letters_ok
            self.scores_above = self.strip[i].scores_above
            self.scores_below = self.strip[i].scores_below
            for j in range(len(self.strip[i].anchors)):
                self.extended = False

                # this stops us simply assigning existing words as HITs
                # by making sure we add at least 1 rack letter.

                self.anchor = self.strip[i].anchors[j]
                if j == 0:
                    limit = self.anchor
                else:
                    limit = self.anchor - self.strip[i].anchors[j - 1] \
                        - 1

                if self.anchor > 0 and self.text[self.anchor - 1] \
                   != Model.empty:
                    partial_word = self.board_word(self.anchor)
                    (in_node, next_record, end_of_word) = \
                        self.find_word(partial_word, 0)
                    self.extend_right(
                        partial_word, next_record,
                        self.anchor, False, self.anchor)
                else:
                    self.left_part('', 0, limit, False, self.anchor)

        return self.hits


class LoadGame(object):

    alphabet = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
                'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
                'Y', 'Z', '.']

    def __init__(self, my_file):
        self.machine_score = 0
        self.player_score = 0
        self.used_tiles = []
        self.squares = []
        self.machine_tiles = []
        self.player_tiles = []
        lines = my_file.readlines()
        self.parse_game(lines)
        my_file.close()

    def parse_tiles(self, tiles):
        tiles_out = []
        for tile in tiles:
            if tile in self.alphabet:
                if tile == '.':
                    tile = ' '
                tiles_out.append(tile)
        return tiles_out

    def parse_letters(self, lines):
        """
        which letters have been used
        """
        line_no = 0
        for line in lines:
            for letter in line:
                letter_u = letter.upper()
                if letter_u in self.alphabet:
                    if line_no < 15:
                        if letter_u != '.':
                            if letter.islower():
                                letter_u = ' '
                            self.used_tiles.append(letter_u)
                    else:
                        if letter_u == '.':
                            letter_u = ' '
                        self.used_tiles.append(letter_u)
            line_no += 1

    def parse_squares(self, lines):
        for i in range(15):
            line = lines[i]
            for letter in line:
                if letter.upper() in self.alphabet:
                    if letter == '.':
                        letter = ' '
                    self.squares.append(letter)

    def parse_game(self, lines):
        len_lines = len(lines)
        self.parse_squares(lines)
        self.parse_letters(lines)
        if len_lines > 15:
            tiles = lines[15]
            self.machine_tiles = self.parse_tiles(tiles)
        if len_lines > 16:
            tiles = lines[16]
            self.player_tiles = self.parse_tiles(tiles)
        if not self.player_tiles:
            self.player_tiles = self.machine_tiles
            self.machine_tiles = []
        if len_lines > 17:
            self.machine_score = int(lines[17])
            self.player_score = int(lines[18])


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

    def __init__(self, root):
        self.master = root
        self.settings = Settings()
        self.model_data = ModelData()
        self.model = None
        self.view = View(root, self.settings)
        screen_shot_state = False
        self.flasher = None
        self.screen_shot = ScreenShot(PzlG.app_name,
                                      'dump_', screen_shot_state)
        self.after_id = None
        self.player = None
        self.players_turn = False
        self.letters_entered = False
        self.swap_requested = False
        self.board_active = False
        self.board_clicked = False
        self.clock_clicked = False
        self.word0 = None
        self.direction0 = None
        self.hit = None
        self.hits = []
        self.square_index0 = None
        self.non_moves = None
        self.showing_solution = None
        self.hint_requested = None
        self.good_words = []
        self.finished = False
        self.view.make_board()
        self.add_callbacks()
        self.view.game_over()
        self.start_game()

    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 == 'game_iq':
            self.set_game_iq()
        elif name == 'game_time':
            self.set_game_time()
        elif name == 'set_colour':
            choice = args[1]
            self.set_colour(choice)
        elif name == 'font_size':
            self.set_font_size()
        elif name == 'load_game':
            self.load_game()
        elif name == 'save_game':
            self.save_game()
        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 == 1:
            if button_index == 0:
                self.start_game()
            elif button_index == 1:
                if not self.finished and self.hit is not None:
                    self.give_hint()
            elif button_index == 2:
                if not self.finished:
                    self.show_solution()
                    self.screen_shot.snap()
            elif button_index == 3:
                #swap
                self.view.game_over()
            elif button_index == 4:
                # stop clock
                self.clock_clicked = True
            # nb, unlike other programs, here we go to the event_handler
            # which controls the overall execution of the game
            self.event_handler(2, button_index)

    def event_handler(self, *args):
        """
        Handle all events, real or generated here.
        args[0] gives event type:
        0 square_event event, square_index
        1 entrybox, word
        2 toolbar button_index, mouse_index
        """
        if args[0] == 0:
            self.doit()
        elif args[0] == 1:
            self.doit()
        elif args[0] == 2:
            if args[1] == 4:
                self.end_players_turn()
            elif args[1] == 1:
                if self.hint_requested:
                    self.doit()
            elif args[1] == 3:
                self.swap_requested = True
                self.doit()
        elif args[0] == 10:
            if not self.showing_solution:
                self.start_players_turn()
        elif args[0] == 11:
            if not self.showing_solution:
                self.machine_turn()
        else:
            PzlG.logger.logger.error("event_handler " + str(args[0]))

    def square_event(self, square_index, mouse_index):
        """
        Should only get here if user has entered an accepted board word
        and we are waiting for her to say where to place it.
        We have to get the direction and square (events 0, 1)
        """
        if self.board_active:
            if mouse_index == 1:
                self.board_clicked = True
                self.square_index0 = square_index
                self.direction0 = 0
                PzlG.logger.logger.info("board_event 0 " + str(square_index))
                self.event_handler(0)
            elif mouse_index == 3:
                self.board_clicked = True
                self.square_index0 = square_index
                self.direction0 = 1
                PzlG.logger.logger.info("board_event 2 " + str(square_index))
                self.event_handler(0)

    def add_events(self):
        # logic simplified by using two generated events
        # ie events which don't actually occur but which get added
        # to those picked by the event loop
        self.master.bind("<<StartPlayersClock>>", self.start_players_clock)
        self.master.bind("<<StartMachineClock>>", self.start_machine_clock)

    def add_callbacks(self):
        self.view.add_menu_callback(self.service_menu)
        self.view.add_toolbar_callback(self.service_toolbar)
        self.view.entry_box.add_callback(self.players_word)
        # following adds callback to board squares and to tiles on board
        self.view.add_board_callback(self.square_event)

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

    def update_score(self, player):
        self.view.update_score_box(player, self.model.scores[player])

    def colour_entry_box(self, colour):
        self.view.entry_box.colour_box(colour)

    def activate_entry_box(self):
        self.view.entry_box.activate()

    def deactivate_entry_box(self):
        self.view.entry_box.deactivate()

    def players_word(self, text):
        self.word0 = text
        self.letters_entered = True
        self.event_handler(1)

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

    def set_game_iq(self):
        self.settings.game_iq = int(self.settings.dic['game_iq'])

    def start_game(self):
        self.view.clock_init()
        self.flasher = Flasher(self.view.clock.box.label,
                               self.master, self.settings)
        self.init_game()

        if self.players_turn:
            # add an event to the stack
            self.generate_event(0)
        else:
            # add an event to the stack
            self.generate_event(1)

    def init_game(self):
        self.view.make_player_frame()
        self.model = Model(self.model_data)
        self.player = None
        self.players_turn = False
        self.letters_entered = False
        self.swap_requested = False
        self.board_active = False
        self.board_clicked = False
        self.clock_clicked = False
        self.word0 = None
        self.direction0 = None
        self.hit = None
        self.hits = []
        self.square_index0 = None
        self.non_moves = None
        self.showing_solution = None
        self.hint_requested = None
        self.good_words = []
        self.finished = False
        self.model.prepare_sack()
        self.model.prepare_rack()
        self.update_score(0)
        self.update_score(1)
        self.non_moves = 0
        self.model.first_turn_anchors()
        self.view.make_board()
        self.view.update_rack(self.model.rack[1])
        self.showing_solution = False
        self.add_events()
        self.add_callbacks()
        self.view.game_on()
        self.view.entry_box.clear()
        self.choose_player()

    def rescale(self):
        self.view.make_player_frame()
        self.model = Model(self.model_data)
        self.view.make_board()

    def machine_turn(self):
        self.player = 0
        self.hits = []
        self.hit = None
        if self.non_moves > 5 or self.rack_empty():
            self.finished = True
        else:
            self.get_hits()
            if self.hits:
                self.hit = self.massage_hits()
                PzlG.logger.logger.info("machine_turn " + self.hit.word)
                self.lay_machine_tiles()
                self.non_moves = 0
                tiles_used = self.hit_to_tiles(self.hit)
                ret = self.replenish_tiles(tiles_used)
                if not ret:
                    self.finished = True
            else:
                PzlG.logger.logger.info("machine_turn: no hits ")
                self.hit = None
                if self.swap_ok():
                    tiles = self.model.rack[self.player]
                    PzlG.logger.logger.info("machine_turn: swap tiles " +
                                            tiles)
                    swaps = self.choose_tiles(tiles)
                    ret = self.replenish_tiles(swaps)
                else:
                    PzlG.logger.logger.info("machine_turn: no swap ")
                    self.non_moves += 1
        self.screen_shot.snap()
        if self.finished:
            self.end_game()
        else:
            self.player = 1
            # add an event to the stack
            self.generate_event(0)

    def show_solution(self):
        self.showing_solution = True
        self.view.game_over()
        while not self.finished:
            self.player = 1
            self.hits = []
            self.hit = None
            if self.non_moves > 5 or self.rack_empty():
                self.finished = True
            else:
                self.get_hits()
                if self.hits:
                    self.hit = self.massage_hits()
                    self.lay_players_tiles()
                    self.non_moves = 0
                else:
                    self.hit = None
                    self.non_moves += 1
                    if self.swap_ok():
                        tiles = self.model.rack[self.player]
                        swaps = self.choose_tiles(tiles)
                        ret = self.replenish_tiles(swaps)
            self.player = 0
            # add an event to the stack
            self.generate_event(0)
            self.machine_turn()
        PzlG.logger.logger.info("end_game")
        self.view.clock_stop()
        self.view.game_over()
        self.deactivate_entry_box()
        self.board_active = False
        if self.rack_empty():
            self.model.finalise_scores()
            self.update_score(0)
            self.update_score(1)

    def generate_event(self, event):
        if event == 0:
            self.master.event_generate("<<StartPlayersClock>>", when="tail")
        elif event == 1:
            self.master.event_generate("<<StartMachineClock>>", when="tail")

    def start_players_clock(self, *args):
        """
        Respond to generated event
        """
        self.event_handler(10)

    def machine_clock(self):
        self.event_handler(11)

    def start_machine_clock(self, *args):
        """
        Respond to generated event
        """
        self.event_handler(11)

    def start_players_turn(self):
        PzlG.logger.logger.info("start_players_turn")
        self.view.clock_restart()
        self.player = 1
        self.players_turn = True
        self.board_active = False
        self.hint_requested = False
        self.activate_entry_box()
        self.view.hint_off()
        self.view.game_on()
        self.hits = []
        self.hit = None
        if self.non_moves > 5 or self.rack_empty():
            self.finished = True
        else:
            self.get_hits()
            self.hit = self.massage_hits()
            if self.hit is not None:
                # only offer hint if there is one!
                self.view.hint_available()
                #self.buttons.switch_button(2, True)
            else:
                PzlG.logger.logger.info("start_players_turn: no hits ")

    def end_players_turn(self):
        PzlG.logger.logger.info("end_players_turn")
        self.players_turn = False
        self.letters_entered = False
        self.hint_requested = False
        self.swap_requested = False
        self.board_active = False
        self.board_clicked = False
        self.clock_clicked = False
        self.view.entry_box.clear()
        self.deactivate_entry_box()
        self.view.clock_stop()
        self.view.game_over()
        self.screen_shot.snap()
        if self.finished:
            self.end_game()
        else:
            self.player = 0
            # add an event to the stack
            self.generate_event(1)

    def give_hint(self):
        """
        Usual 2 stage hint.
        First time through here player given the hint word.
        Second time we stick it on the board
        """

        PzlG.logger.logger.info("give_hint")
        if not self.hint_requested:
            self.view.hint_on()
            self.hint_requested = True
            self.word0 = self.hit.word
            PzlG.logger.logger.info("give_hint " + self.word0)
            self.words_matching_word()
            self.deactivate_entry_box()
            self.view.entry_box.clear()
            self.view.entry_box.write_box(self.hit.word)
            self.view.hint_active()
            self.board_active = True
            self.screen_shot.snap()
        else:
            PzlG.logger.logger.info("give_hint laying tiles")
            self.lay_players_tiles()
            self.board_active = False
            self.end_players_turn()

    def doit(self):
        if self.players_turn:
            if self.hint_requested:
                pass
            elif self.letters_entered:
                # set hint off
                if self.swap_requested:
                    PzlG.logger.logger.info("doit: swap requested ")
                    letters_ok = self.check_swap()
                    if letters_ok:
                        colour = 'white'
                        self.colour_entry_box(colour)
                        self.replace_tiles()
                        self.letters_entered = False
                        self.view.game_on()
                    else:
                        PzlG.logger.logger.info("doit: bad swap ")
                        colour = self.settings.dic['triple_word_colour']
                        self.colour_entry_box(colour)
                else:
                    self.view.player_entering()
                    letters_ok = self.check_word()
                    if letters_ok:
                        PzlG.logger.logger.info(
                            "doit: letters ok " +
                            self.word0)
                        # activate board
                        self.board_active = True
                        self.view.board.activate()
                        self.letters_entered = False
                        colour = 'white'
                        self.colour_entry_box(colour)
                    else:
                        PzlG.logger.logger.info(
                            "doit: letters not ok " +
                            self.word0)
                        colour = self.settings.dic['double_word_colour']
                        self.colour_entry_box(colour)

            if self.board_clicked:
                position_ok = self.check_board_position()
                if position_ok:
                    PzlG.logger.logger.info("doit: position ok ")
                    self.lay_players_tiles()
                    self.board_active = False
                else:
                    PzlG.logger.logger.info("doit: position not ok ")
                    self.board_clicked = False
                    colour = self.settings.dic['double_letter_colour']
                    self.colour_entry_box(colour)
            if self.clock_clicked:
                PzlG.logger.logger.info("doit: clock_clicked ")
                self.end_players_turn()

    def choose_player(self):
        self.player = random.randint(0, 1)
        if self.player == 1:
            self.players_turn = True
        else:
            self.players_turn = False

    def check_word(self):
        return self.word_in_hits()

    def check_board_position(self):
        return self.square_in_hits()

    def hit_to_tiles(self, hit):
        used = []
        for i in range(len(hit.word)):
            if hit.text[i] == ' ':
                letter = hit.word[i]
                if letter.islower():
                    letter = ' '
                used.append(letter)
        return used

    def replenish_tiles(self, used):
        ret = self.model.replenish_tiles(self.player, used)
        if self.player == 1:
            self.view.update_rack(self.model.rack[1])
        return ret

    def rack_empty(self):
        ret = False
        if not self.model.rack[0]:
            ret = True
        if not self.model.rack[1]:
            ret = True
        return ret

    def swap_ok(self):
        ret = False
        if len(self.model.sack) > 6:
            ret = True
        return ret

    def check_swap(self):
        ret = True
        for i in self.word0:
            if i not in self.model.rack[self.player]:
                ret = False
                break
        return ret

    def replace_tiles(self):
        ret = self.model.replace_tiles(self.word0)
        if ret:
            self.view.update_rack(self.model.rack[1])

    def get_hits(self):
        self.model.set_rack(self.player)
        self.hits = self.model.take_turn()

    def words_matching_word(self):
        self.good_words = []
        for i in range(len(self.hits)):
            if self.word0.upper() == self.hits[i].word.upper():
                self.good_words.append(self.hits[i])

    def word_in_hits(self):
        ret = False
        self.words_matching_word()
        if self.good_words:
            ret = True
        return ret

    def square_in_hits(self):
        ret = False
        for i in range(len(self.good_words)):
            strip_index = self.good_words[i].strip_index
            strip_offset = self.good_words[i].strip_offset
            if strip_index < 15:
                square_index = 15 * strip_index + strip_offset
            else:
                square_index = strip_index - 15 + 15 * strip_offset
            if self.square_index0 == square_index:
                if self.good_words[i].strip_index < 15:
                    if self.direction0 == 0:
                        ret = True
                        break
                else:
                    if self.direction0 == 1:
                        ret = True
                        break
        if ret:
            self.hit = self.good_words[i]
        return ret

    def choose_tiles(self, tiles_in):
        tiles = []
        tiles = tiles_in[:]
        shuffle(tiles)
        swaps = []
        r = min(len(tiles), 3)
        for i in range(r):
            swaps.append(tiles.pop())
        return swaps

    def lay_machine_tiles(self):
        self.model.add_word(
            self.hit.strip_index,
            self.hit.strip_offset, self.hit.word)
        self.model.update_score(self.player, self.hit.score)
        self.view.board.lay_tiles(self.hit, self.player)
        self.update_score(self.player)

    def lay_players_tiles(self):
        self.view.swap_off()
        self.model.add_word(
            self.hit.strip_index,
            self.hit.strip_offset, self.hit.word)
        self.model.update_score(self.player, self.hit.score)
        self.view.board.lay_tiles(self.hit, self.player)
        self.update_score(self.player)
        self.non_moves = 0
        tiles_used = self.hit_to_tiles(self.hit)
        ret = self.replenish_tiles(tiles_used)
        if not ret:
            self.finished = True

    def massage_hits(self):
        hit = None
        len_hits = len(self.hits)
        if len_hits > 0:
            if self.player == 0:
                if self.settings.game_iq > 90.0:
                    self.hits = sorted(
                        self.hits, key=lambda hit:
                        hit.score)
                    hit = self.hits[len_hits - 1]
                    return hit
                self.hits = sorted(self.hits, key=lambda hit:
                                   len(hit.word))
                self.hits = sorted(self.hits, key=lambda hit: hit.score)
                min_score = self.hits[0].score
                max_score = self.hits[len_hits - 1].score
                score_range = max_score - min_score
                target_score = int(float(score_range) *
                                   self.settings.game_iq / 100.0) \
                    + min_score
                keys = [hit.score for hit in self.hits]
                hit_index = bisect_right(keys, target_score)
                if hit_index:
                    hit = self.hits[hit_index - 1]
                else:
                    raise ValueError
            else:
                self.hits = sorted(self.hits, key=lambda hit: hit.score)
                hit = self.hits[len_hits - 1]
        return hit

    def end_game(self):
        PzlG.logger.logger.info("end_game")
        self.view.clock_stop()
        self.view.game_over()
        self.deactivate_entry_box()
        self.board_active = False
        if self.rack_empty():
            self.model.finalise_scores()
            self.update_score(0)
            self.update_score(1)
        self.flasher.flash()

    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]
                self.view.board.set_colours()
        except KeyError:
            PzlG.logger.logger.error("set_colour " + choice)

    def load_game(self):
        PzlG.logger.logger.info("load_game")
        my_file = self.view.sg_open_file(False, _('Input file'))
        if my_file:
            self.model = Model(self.model_data)
            self.finished = False
            self.non_moves = 0
            self.showing_solution = False
            loaded_game = LoadGame(my_file)
            self.model.load_game(loaded_game)
            self.view.make_board()

            self.add_events()
            self.add_callbacks()
            tile_colour = self.settings.dic['players_tile_colour']
            self.view.board.initial_tiles(loaded_game.squares, tile_colour)
            self.view.update_rack(self.model.rack[1])
            self.view.game_over()
            self.update_score(0)
            self.update_score(1)
            self.player = 1
            self.players_turn = True
            self.view.clock_init()
            self.view.clock_restart()
            self.flasher = Flasher(self.view.clock.box.label,
                                   self.master, self.settings)
            if self.players_turn:
                self.generate_event(0)
            else:
                self.generate_event(1)

    def save_game(self):
        PzlG.logger.logger.info("save_game")
        my_file = self.view.sg_open_file(True, _('Output file'))
        if my_file:
            for i in range(15):
                text = \
                    ''.join(self.model.strip[i].text[0:15]) \
                    + '\n'
                text = text.replace(' ', '.')
                my_file.write(text)
            text = ''.join(self.model.rack[0]) + '\n'
            text = text.replace(' ', '.')
            my_file.write(text)
            text = ''.join(self.model.rack[1]) + '\n'
            text = text.replace(' ', '.')
            my_file.write(text)
            text = str(self.model.scores[0][0]) + '\n'
            my_file.write(text)
            text = str(self.model.scores[1][0]) + '\n'
            my_file.write(text)
            my_file.close()


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