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

# Copyright (C) 2015 Rod Engerstad

import random
from random import randint
import tkinter as tk
import zlib
import base64
import itertools

from pzl_lib import PzlG
from pzl_lib import SettingsB
from pzl_lib import Preferences
from pzl_lib import MenuItem
from pzl_lib import Flasher
from pzl_lib import ScreenShot
from pzl_lib import ViewB
from pzl_lib import TextSquare

class Sgg(object):
    """
    Game globals
    """
    grid_width = 6
    grid_height = 6
    cell_count = grid_width * grid_height
    GIVE_HINT = 0
    APPLY_HINT = 1
    DELETE = 4
    types = [0] * 8
    UNKNOWN = 5
    UNNUMBERED = 6
    NUMBERED = 0
    BULB = 7
    NEW_BULBS = 1
    BLOCK = 2
    LIT = 3
    HINT = 8
    ALGORITHM_NAMES = list(range(25))


class Settings(SettingsB):

    def __init__(self):
        SettingsB.__init__(self)
        self.original_square_size = 36
        self.square_size = self.original_square_size
        self.dic = {}
        self.dic['font_size'] = '2'
        self.menu_items = []

        self.preferences = Preferences(self.dic)
        self.preferences.load()
        self.load_menus()
        self.count = len(self.menu_items)
        self.set_fonts()
        self.make_help()
        self.make_tooltip()
        self.make_alg_help()

    def make_alg_help(self):
        self.alg_text = [_("Algorithm 0: any black cell containing a 0 can be blocked on all sides."), _("Algorithm 1: If a cell has number N and only N neighbours that can be bulbed, then they are all bulbs."), _("Algorithm 2: If a cell is unlit and can only be lit by itself, it must be a bulb."), _("Algorithm 3: If a cell has number 3 then no bulb can occupy its adjacent diagonals as each would block bulbs along two sides and so it could never be satisfied."), _("Algorithm 4: If a cell can only be lit by one cell, that cell must be a bulb."), _("Algorithm 5: If a cell has number 1 and has two unbulbable adjacent cells arranged diagonally to one another, then no bulb can occupy the cell on the opposite diagonal because it would block both of the 1s possible bulb placements."), _("Algorithm 6: When two unsatisfied 1s are arranged on diagonally adjacent cells and one of them has only two remaining unlit cells, both of which would also satisfy the other 1, one of those two unlit cells must contain a bulb. This bulb will satisfy both 1s and so no other cells touching the 1s can contain a bulb."), _("Algorithm 7: When a 3 requires one more bulb and on an adjacent diagonal has a cell also requiring one more bulb, then one of the two empty cells adjacent to the 3 must satisfy both. Therefore any other unlit cells around  the unsatisfied cell can be blocked."), _("Algorithm 8: When on an adjacent diagonal a 3 has a cell requiring one more bulb, and this bulb can only be next to the 3, we know that the other two cells touching the 3 must be bulbs."), _("Algorithm 9: When a 2 with one touching bulb and an unsatisfied 1 are diagonally adjacent and the 2 must be satisfied with a bulb which also touches the 1, any other cells touching the 1 can be blocked."), _("Algorithm 10: When a 2 is blocked on one side and is not touching any bulbs, a bulb on two of its adjacent diagonals would prevent it from being satisfied. Therefore these diagonals cannot be bulbs."), _("Algorithm 11: When a 2 touching no bulbs is blocked on one side and is also diagonally adjacent to an unsatisfied 1, only one bulb can be placed touching the 2 and the 1. So the unblocked cell not touching the 1 must be a bulb."), _("Algorithm 12: When a 2 touching no bulbs is diagonally adjacent to a cell requiring one more bulb, and that bulb must touch the 2, the other bulb for the 2 must be placed on one of its other two sides. A bulb on their bisecting diagonal would block both of these possibilities, hence this diagonal cell can be blocked."), _("Algorithm 13: When a 2 and a 1 are adjacent and neither touches any bulbs and the 1 is blocked on the side distal from the 2, then the 2 cannot be satisfied by bulbs which would block the 1. So the cell on the side of the 2 distal from the 1 must be a bulb."), _("Algorithm 14: This algorithm looks for cells which if bulbed would block the only way to light another cell. Any such cells can be blocked."), _("Algorithm 15: When a 2 and a 1 are diagonally adjacent and the 2 is blocked on a side distal to the 1, two of the three unblocked cells next to the 2 are also next to the 1. The 2 requires two bulbs and to avoid having two bulbs next to the 1 the cell not touching the 1 must be a bulb."), _("Algorithm 16: When a cell requires one more bulb which can only be placed in one of two places, any cell which if bulbed would block both of these places cannot be a bulb."), _("Algorithm 17: When two 2s are diagonally adjacent and one of them requires only one more bulb, that bulb will be shared with the other 2 and that 2 must have a bulb on its other side."), _("Algorithm 18: When two 2s are diagonally adjacent and one of them requires only one more bulb, that bulb will be shared with the other 2 and that 2 must have a bulb on its other side."), _("Algorithm 19: This algorithm looks for cells which if bulbed would block the only way to light another cell. Any such cells can be blocked.")]

    def make_tooltip(self):
        self.tooltip_text = [
            _('Left-mouse random puzzle, right-mouse next puzzle'),
            _('Give hint, Apply hint'),
            _('Check for errors, correct errors'),
            _('Show solution')
        ]

    def make_help(self):
        self.help_text =_('Place light bulbs so that every white square on the board is lit and all the numbered squares are vertically and horizontally contiguous with exactly their given number of light bulbs. No light bulb can be illuminated by another and there is only one solution to each puzzle. Bulbs are placed using a left mouse click. Cells can be marked as blocked using a right mouse click. Middle mouse undoes a bulb or a block. In the toolbar the jigsaw symbol contains a menu, the PZL Akari symbol starts a new game; the wand requests a hint, a second click on the wand will perform the hint operation; the thumb checks if the puzzle contains any errors and a second click will clear the errors; the sad smiley shows the solution; the entrybox shows the current puzzle number and can be used to select a new puzzle number; the number next to the puzzle number is the puzzle difficulty score. The clock ticks every 5 seconds but will be replaced by the algorithm number if a hint is requested. The program contains 20 solving algorithms numbered 0 - 19, ordered very roughly on their complexity, so that low numbered algorithms are easier to understand/apply than those with high numbers. The font size option in the menu can be used to change the size of the display. The game contains 5 blocks of 100 puzzles with sizes 12x10, 18x12, 20x18, 24x20, 36x20, numbered sequentially from 0 to 499. Within each block of a 100 the puzzles are ordered by their difficulty score, easiest first.')

    def load_menus(self):
        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 Datum(object):
    """
    Data item for a cell

    Types: unknown ie may be illuminated
                   or given a light bulb (bulbed)
           black_unnumbered
           black_numbered: neighbouring cells must contain
                           given number of light bulbs
    Some cells can never contain bulbs (bulbable = False)
    When we light a cell it cannot be given a bulb so bulbable = False

    """

    def __init__(self, value, datum_index):
        self.index = datum_index
        self.bulbs = None
        self.type = None
        self.sources = 0
        self.bulbed = False
        self.bulbable = False
        self.answer = False
        self.guess = None
        self.neighbours = []

        if value == Sgg.UNKNOWN:
            self.type = Sgg.UNKNOWN
            self.bulbs = 0
            self.bulbable = True
        elif value == Sgg.BULB:
            self.type = Sgg.UNKNOWN
            self.bulbs = 0
            self.bulbable = True
            self.answer = True
        elif value == Sgg.UNNUMBERED:
            self.type = Sgg.UNNUMBERED
        elif value in [0, 1, 2, 3, 4]:
            self.type = Sgg.NUMBERED
            self.bulbs = value

    def init(self):
        """
        Set all data back to initial state.
        """

        if self.type is Sgg.UNKNOWN:
            self.bulbs = 0
            self.sources = 0
            self.bulbable = True
            self.bulbed = False
        elif self.type is Sgg.NUMBERED:
            self.sources = 0

    def colour(self):
        """
        For the display we need to know the cell's guess
        """

        ret = 0
        if self.type is not Sgg.UNNUMBERED and \
           self.type is not Sgg.NUMBERED:
            if self.guess is not None:
                ret = self.guess

        return ret

    def __repr__(self):
        """
        Numbered = number
        Bulbed = *
        Lit = o
        Unlit = .
        Unlit and not bulbable = X
        Lit and not bulbable = ~
        """
        if self.type == Sgg.UNKNOWN:
            if self.sources == 0:
                s = '.'
                if not self.bulbable:
                    s = 'X'
            elif self.sources > 0:
                s = 'o'
#                if not self.bulbable:
#                    s = 'x'
            if self.bulbed:
                s = 'O'
        elif self.type == Sgg.UNNUMBERED:
            s = '#'
        else:
            s = str(self.bulbs)
#        if self.answer:
#            s = '*'
        return s

    def pr(self):
        """
        Numbered = number
        Bulbed = *
        Lit = o
        Unlit = .
        Unlit and not bulbable = X
        Lit and not bulbable = ~
        """
        if self.type == Sgg.UNKNOWN:
            s = Sgg.UNKNOWN
            if self.bulbed:
                s = Sgg.BULB
        elif self.type == Sgg.UNNUMBERED:
            s = Sgg.UNNUMBERED
        else:
            s = self.bulbs
        return s

    def set_bulbability(self, value):
        self.bulbable = value

    def is_bulbable(self):
        return self.bulbable

    def add_bulb(self):
        self.bulbed = True
        self.bulbable = False
        self.guess = Sgg.BULB

    def delete_bulb(self):
        self.bulbed = False
        self.bulbable = True

    def add_source(self):
        self.sources += 1
        self.bulbable = False
        self.guess = Sgg.LIT

    def is_bulbed(self):
        return self.bulbed

    def is_satisfied(self):
        ret = False
        if self.type is Sgg.NUMBERED:
            if self.sources >= self.bulbs:
                ret = True
        elif self.type is Sgg.UNKNOWN:
            if self.sources > 0 or self.bulbed:
                ret = True
        elif self.type is Sgg.UNNUMBERED:
            ret = True
        return ret

    def is_wrong(self):
        ret = False
        if self.bulbed and not self.answer:
            ret = True
        if not self.bulbable and self.answer and not self.bulbed:
            ret = True
        return ret

    def guess_is_wrong(self):
        ret = False
        if self.guess is not None:
            if self.guess is Sgg.BULB and \
               not self.answer:
                ret = True
            elif self.guess is Sgg.BLOCK and \
                self.answer:
                ret = True
        return ret

    def get_neighbour(self, direction):
        return self.neighbours[direction]

    def get_all_neighbours(self):
        return self.neighbours

    def get_neighbours(self):
        # get good neighbours
        return [x for x in self.neighbours if x is not None]

    def set_neighbours(self):
        """
        Set the neighbours
             0
           3   1
             2
        """
        self.neighbours = []

        # up
        nbr_index = self.index - Sgg.grid_width
        if nbr_index > -1:
            self.neighbours.append(nbr_index)
        else:
            self.neighbours.append(None)

        # right
        nbr_index = self.index + 1
        if (self.index + 1) % Sgg.grid_width != 0:
            self.neighbours.append(nbr_index)
        else:
            self.neighbours.append(None)

        # down
        nbr_index = self.index + Sgg.grid_width
        if nbr_index < Sgg.cell_count:
            self.neighbours.append(nbr_index)
        else:
            self.neighbours.append(None)

        # left
        nbr_index = self.index - 1
        if self.index % Sgg.grid_width != 0:
            self.neighbours.append(nbr_index)
        else:
            self.neighbours.append(None)

    def get_diagonals(self):
        # special case diagonals for NUMBERED value 3
        ret = []

        # up right
        nbr_index = self.index - Sgg.grid_width + 1
        if nbr_index > -1 and (self.index + 1) % Sgg.grid_width != 0:
            ret.append(nbr_index)
        else:
            ret.append(None)
        # down right
        nbr_index = self.index + Sgg.grid_width + 1
        if nbr_index < Sgg.cell_count and (self.index + 1) % Sgg.grid_width != 0:
            ret.append(nbr_index)
        else:
            ret.append(None)

        # down left
        nbr_index = self.index + Sgg.grid_width - 1
        if nbr_index < Sgg.cell_count and self.index % Sgg.grid_width != 0:
            ret.append(nbr_index)
        else:
            ret.append(None)

        # up left
        nbr_index = self.index - Sgg.grid_width - 1
        if nbr_index > -1 and self.index % Sgg.grid_width != 0:
            ret.append(nbr_index)
        else:
            ret.append(None)

        return ret

    def set_guess(self, guess):
        """
        User can place a block or a bulb.
        If a bulb we have to light rows and columns
        but Model takes care of that.
        """
        self.guess = guess

    def unset_guess(self):
        """
        Let Model take care of everything else.
        """
        self.guess = None


class Model(object):
    """
    """

    def __init__(self, model_data):
        self.alg_count = 20
        self.alg_usage = [0] * self.alg_count
        self.model_data = model_data
        self.cells = []
        self.progress = False
        self.status = True
        self.finished = False
        self.hint = []
        self.user_errors = []
        self.difficulty = None

    def __str__(self):
        out = ''
        item = -1
        for y in range(Sgg.grid_height):
            string = str(y * Sgg.grid_width)
            out += string.rjust(4) + ' '
            for x in range(Sgg.grid_width):
                item += 1
                o = str(self.cells[item])
                out += o
            out += '\n'
        return out

    def load_puzzle(self, data, difficulty):
        self.difficulty = difficulty
        self.cells = []
        for item in range(len(data)):
            self.cells.append(Datum(data[item], item))

    def zero_alg_usage(self):
        for i in range(len(self.alg_usage)):
            self.alg_usage[i] = 0

    def update_alg_usage(self, alg):
        if alg < self.alg_count:
            self.alg_usage[alg] += 1

    def set_neighbours(self):
        for cell in self.cells:
            cell.set_neighbours()

    def is_wrong(self):
        ret = []
        for cell in self.cells:
            if cell.is_wrong():
                ret.append(cell.index)
        for cell in self.cells:
            if cell.type is Sgg.NUMBERED:
                sources = 0
                neighbours = cell.get_neighbours()
                for neighbour in neighbours:
                    n_cell = self.cells[neighbour]
                    if n_cell.is_bulbed():
                        sources += 1
                if cell.sources != sources:
                    ret.append(cell.index)
        if ret:
            self.status = False
        return ret

    def user_is_finished(self):
        ret = True
        for cell in self.cells:
            if cell.answer and cell.guess is not Sgg.BULB:
                ret = False
                break
        return ret

    def is_finished(self):
        ret = []
        for cell in self.cells:
            if cell.type is Sgg.NUMBERED:
                neighbours = cell.get_neighbours()
                bulbs = 0
                for neighbour in neighbours:
                    if self.cells[neighbour].is_bulbed():
                        bulbs += 1
                if cell.bulbs != bulbs:
                    ret.append(cell.index)
            elif cell.type is Sgg.UNKNOWN:
                if not cell.is_bulbed() and cell.sources == 0:
                    ret.append(cell.index)
        if not ret:
            self.finished = True
        return ret

    def set_solution(self):
        for cell in self.cells:
            if cell.answer:
                cell.guess = Sgg.BULB

    def is_consistent(self):
        ret = []
        for cell in self.cells:
            if cell.guess_is_wrong():
                ret.append(cell.index)
        return ret

    def check_errors(self):
        self.user_errors = self.is_consistent()
        if self.user_errors:
            self.status = False

    def gis_consistent(self):
        ret = []
        for cell in self.cells:
            if cell.type is Sgg.NUMBERED:
                neighbours = cell.get_neighbours()
                bulbs = 0
                for neighbour in neighbours:
                    if self.cells[neighbour].is_bulbed():
                        bulbs += 1
                if cell.bulbs != bulbs:
                    ret.append(cell.index)
        return ret

    def open_neighbours(self, cell_index):
        """
        Return list of neighbours that are not bulbed or blocked
        """
        ret = []
        cell = self.cells[cell_index]
        if cell.type is Sgg.NUMBERED and \
           cell.sources < cell.bulbs:
            neighbours = cell.get_neighbours()
            for neighbour in neighbours:
                n = self.cells[neighbour]
                if n.is_bulbable():
                    ret.append(neighbour)
        return ret

    def openings(self, cell_index):
        """
        Return count of neighbours that are not bulbed or blocked
        """
        ret = 0
        cell = self.cells[cell_index]
        neighbours = cell.get_neighbours()
        for neighbour in neighbours:
            n = self.cells[neighbour]
            if n.is_bulbable():
                ret += 1
        return ret

    def get_line(self, cell, direction):
        ret = []
        n = cell
        while True:
            n = self.cells[n].get_neighbour(direction)
            if n is None:
                break
            else:
                nc = self.cells[n]
                if nc.type == Sgg.UNKNOWN:
                    ret.append(n)
                else:
                    break
        return ret

    def get_lineb(self, cell, direction):
        """
        Get line of UNKNOWN cells that are bulbable.
        Stop at first non-UNKNOWN.
        """
        ret = []
        n = cell
        while True:
            n = self.cells[n].get_neighbour(direction)
            if n is None:
                break
            else:
                nc = self.cells[n]
                if nc.type == Sgg.UNKNOWN:
                    if nc.is_bulbable():
                        ret.append(n)
                else:
                    break
        return ret

    def light_lines(self, bulb):
        for direction in range(4):
            line = self.get_line(bulb, direction)
            for cell in line:
                self.cells[cell].add_source()

    def update_numbers(self, bulb):
        neighbours = self.cells[bulb].neighbours
        for neighbour in neighbours:
            if neighbour is not None and \
               self.cells[neighbour].type is Sgg.NUMBERED:
                self.cells[neighbour].add_source()
                if self.cells[neighbour].is_satisfied():
                    blocked = self.cells[neighbour].get_neighbours()
                    for cell_index in blocked:
                        cell = self.cells[cell_index]
                        if cell.is_bulbable():
                            cell.set_bulbability(False)

    def add_bulbs(self, bulbs):
        """
        When we add a bulb we add a bulb to a cell
        and light up its row and column by adding
        to their cells sources.
        We also update the source count for any NUMBERED
        neighbours and if they are consequently completely
        satisfied we set their immediate neighbours unbulbable.
        """
        for bulb in bulbs:
            self.cells[bulb].add_bulb()
            self.light_lines(bulb)
            self.update_numbers(bulb)

    def update_numbersm(self, bulb):
        neighbours = self.cells[bulb].neighbours
        for neighbour in neighbours:
            if neighbour is not None and \
               self.cells[neighbour].type is Sgg.NUMBERED and \
               self.cells[neighbour].bulbs < 4:
                self.cells[neighbour].bulbs += 1

    def add_bulbsm(self, bulbs):
        """
        When we add a bulb we add a bulb to a cell
        and light up its row and column by adding
        to their cells sources.
        We also update the source count for any NUMBERED
        neighbours and if they are consequently completely
        satisfied we set their immediate neighbours unbulbable.
        """
        for bulb in bulbs:
            self.cells[bulb].add_bulb()
            self.light_lines(bulb)
            self.update_numbersm(bulb)


    """
    NB all the algorithms were originally written to return lists
    of bulbs or blocks (which was more efficient) but when the hints
    were added we only needed 1 bulb or block at a time so we added
    nasty early returns from each algorithm (personally prefer only
    one return from any function).
    """

    def alg19(self):
        """
        Find cells which if lit would block the only way to
        light another cell. In this example there are 2 possible
        sources but they are both blocked by a single cell so
        that cell must be blocked.

        Eg
        504 *ooooo1o#oo*oooooooo#o1ooo*2*#o*oo#*
        540 ooo*o1Xo.#oooo*oooo#*oo2*oooooooo1*o
        576 0o*2o*ooooo#o*2oo0ooooo*o1o*ooo#oooo
        612 o*oo#o.o.X0ooooo*o1*oooo#*oooooo0ooo
        648 *o2*oooooooo1o*o3*oooooooo1oo*2oo*oo
        684 o1*ooo#*1ooo*#o1*ooo#*1ooo*#o2*ooo0o

        unfinished are 546, 548, 618, 620, 621
        the only way to light 546 is from 548 or 618
        but a bulb at 620 blocks both of these so
        we can block 620.

        1. find possible sources for a cell
        here the cell is 546 and the sources 548, 618
        2. find possible blockers of these sources
        3. if one blocks all it must be blocked

        """

        ret = set([])
        source_pairs = []
        for cell in self.cells:
            if cell.type is Sgg.UNKNOWN and \
               not cell.is_satisfied():
                sources = []
                if cell.is_bulbable():
                    sources.append(cell.index)
                for direction in range(4):
                    line = self.get_lineb(cell.index, direction)
                    sources.extend(line)
                if sources:
                    # sources is all possible cell indexes
                    #that could light cell
                    t = [cell.index, set(sources)]
                    source_pairs.append(t)

        # now we look to see if any cell is such that all its possible
        # sources are also possible sources for another cell (excluding
        # the cell itself).
        for source_pair in source_pairs:
            cell = source_pair[0]
            sources = source_pair[1]
            for source_pair_1 in source_pairs:
                cell_1 = source_pair_1[0]
                if self.cells[cell_1].is_bulbable():
                    sources_1 = source_pair_1[1]
                    if cell_1 != cell and cell_1 not in sources:
                        if sources.issubset(sources_1) and \
                           not sources_1.issubset(sources):
                            ret.add(cell_1)
                            if True:
                                return list(ret), Sgg.BLOCK
        return list(ret), Sgg.BLOCK

    def alg18(self):
        """
        ..@..
        .a2..
        x2b.
        .*..

        NB upper 2 must have < 4 open neighbours.
        only way to satisfy lower 2 is with either a or b but not both
        but the 2 above needs 1 more bulb so it must go at @

        ..@..
        .a2..
        x2b.
        .*..

        .*..
        x2b.
        .a2..
        ..@..

        ...*..
        ..b2x.
        ..2a.
        ..@..

        ..@..
        ..2a.
        ..b2x.
        ...*..

        diagonal order
        30
        21

        neighbour order
         0
        3 1
         2

        """
        ret = []
        # required bulbs e_bulb
        e_bulb = [2, 0, 0, 2]
        # blocked cells xs
        xs = [3, 3, 1, 1]
        # new_bulb n_bulb
        n_bulb = [0, 2, 2, 0]
        # a and b relative to first 2 a_and_b
        a_and_b = [[0, 1], [1, 2], [2, 3], [0, 3]]

        for cell in self.cells:
            # first 2
            if cell.type is Sgg.NUMBERED and \
               cell.bulbs == 2 and \
               cell.sources == 1:
                # got a 2 needing 1 bulb
                neighbours = cell.get_all_neighbours()
                diagonals = cell.get_diagonals()
                for direction in range(4):
                    if diagonals[direction] is not None:
                        s_2 = self.cells[diagonals[direction]]
                        if s_2.type is Sgg.NUMBERED and \
                           s_2.bulbs == 2 and \
                           s_2.sources < 2 and \
                           self.openings(s_2.index) < 4:
                            # and has an adjacent 2 on a diagonal
                            a = neighbours[a_and_b[direction][0]]
                            b = neighbours[a_and_b[direction][1]]
                            if neighbours[e_bulb[direction]] is not None and \
                               self.cells[neighbours[e_bulb[direction]]].is_bulbed() and \
                               a is not None and \
                               self.cells[a].is_bulbable() and \
                               b is not None and \
                               self.cells[b].is_bulbable():
                                # 2 has adjacent bulb in correct place
                                # and a and b bulbable
                                if neighbours[xs[direction]] is not None and not \
                                   self.cells[neighbours[xs[direction]]].is_bulbable():
                                    # 2 is blocked in correct place
                                    b_neighbours = s_2.get_all_neighbours()
                                    nb = b_neighbours[n_bulb[direction]]
                                    if nb is not None and \
                                       self.cells[nb].is_bulbable():
                                        ret.append(nb)
                                        if True:
                                            return ret, Sgg.NEW_BULBS
        return list(set(ret)), Sgg.NEW_BULBS

    def alg17(self):
        """
        xx..
        .2..
        ..2x
        ..*.

        The lower 2 needs a bulb so the upper 2 will share it
        and the upper 2s other bulb must go to its left.

        xx..
        @2..
        ..2x
        ..*.

        Definitely a bulb at @.

        ..xx
        ..2.
        x2..
        .*..

        xx..
        .2..
        ..2x
        ..*.
        --------------------------------
        ..*.
        ..2x
        .2..
        xx..

        .*..
        x2..
        ..2.
        ..xx


        ..xx
        ..2@
        x2..
        .*..

        xx..
        @2..
        ..2x
        ..*.
        --------------------------------
        ..*.
        ..2x
        @2..
        xx..

        .*..
        x2..
        ..2@
        ..xx


        neighbour order
         0
        3 1
         2

        diagonal order
        30
        21

        """

        ret = []
        o1 = [0, 3]
        bulb1 = [1, 3]
        for cell in self.cells:
            # first 2
            if cell.type is Sgg.NUMBERED and \
               cell.bulbs == 2 and \
               cell.sources == 1:
                # got a 2 needing 1 bulb
                neighbours = cell.get_all_neighbours()
                diagonals = cell.get_diagonals()
                if neighbours[2] is not None and \
                   self.cells[neighbours[2]].is_bulbed():
                    # 2 has adjacent bulb
                    for direction in range(2):
                        d = diagonals[o1[direction]]
                        if d is not None and \
                           self.cells[d].type is Sgg.NUMBERED and \
                           self.cells[d].bulbs == 2 and \
                           self.cells[d].sources == 0:
                            u2 = self.cells[d]
                            # 2 has another 2 (u2) with no bulbs
                            uneighbours = u2.get_all_neighbours()
                            udiagonals = u2.get_diagonals()
                            p = uneighbours[0]
                            if p is not None and \
                               not self.cells[p].is_bulbable():
                                # lower 2 blocked to one side
                                un = udiagonals[o1[direction]]
                                if un is not None and \
                                   not self.cells[un].is_bulbable():
                                    ud = udiagonals[o1[direction]]
                                    if ud is not None and \
                                       not self.cells[ud].is_bulbable():
                                        # upper 2 is blocked on 2 sides
                                        bn = uneighbours[bulb1[direction]]

                                        if bn is not None and \
                                           self.cells[bn].is_bulbable():
                                            ret.append(bn)
                                            if True:
                                                return ret, Sgg.NEW_BULBS

        """
        ..*.
        ..2x
        @2..
        xx..

        .*..
        x2..
        ..2@
        ..xx

        neighbour order
         0
        3 1
         2

        diagonal order
        30
        21

        """
        o2 = [2, 1]
        bulb2 = [3, 1]
        for cell in self.cells:
            # first 2
            if cell.type is Sgg.NUMBERED and \
               cell.bulbs == 2 and \
               cell.sources == 1:
                # got a 2 needing 1 bulb
                neighbours = cell.get_all_neighbours()
                diagonals = cell.get_diagonals()
                if neighbours[0] is not None and \
                   self.cells[neighbours[0]].is_bulbed():
                    # 2 has adjacent bulb
                    for direction in range(2):
                        d = diagonals[o2[direction]]
                        if d is not None and \
                           self.cells[d].type is Sgg.NUMBERED and \
                           self.cells[d].bulbs == 2 and \
                           self.cells[d].sources == 0:
                            u2 = self.cells[d]
                            # 2 has another 2 (u2) with no bulbs
                            uneighbours = u2.get_all_neighbours()
                            udiagonals = u2.get_diagonals()
                            p = uneighbours[2]
                            if p is not None and \
                               not self.cells[p].is_bulbable():
                                # lower 2 blocked to one side
                                un = udiagonals[o2[direction]]
                                if un is not None and \
                                   not self.cells[un].is_bulbable():
                                    ud = udiagonals[o2[direction]]
                                    if ud is not None and \
                                       not self.cells[ud].is_bulbable():
                                        # upper 2 is blocked on 2 sides
                                        bn = uneighbours[bulb2[direction]]

                                        if bn is not None and \
                                           self.cells[bn].is_bulbable():
                                            ret.append(bn)
                                            if True:
                                                return ret, Sgg.NEW_BULBS

        return list(set(ret)), Sgg.NEW_BULBS

    def alg16(self):
        """
         .X.
         X1.
         ..A

        The 1 requires a bulb which can only in 2 places
        so A must be blocked as a bulb there would block
        both possibilities.

          NC
          DA

        N requires 1 more bulb which can only go at C or D.
        A is unbulbed and can be blocked.

         A
        N

          N
           A

         N
        A

        A
         N

        neighbour order
         0
        3 1
         2

        diagonal order
        30
        21

        """
        ret = []
        blocks = [[2, 3], [0, 3], [0, 1], [1, 2]]
        for cell in self.cells:
            if cell.type is Sgg.NUMBERED and \
               cell.bulbs - cell.sources == 1:
                neighbours = cell.get_all_neighbours()
                diagonals = cell.get_diagonals()
                for direction in range(4):
                    if diagonals[direction] is not None and \
                       self.cells[diagonals[direction]].is_bulbable():
                        block = blocks[direction]
                        b0 = neighbours[block[0]]
                        blocked = True
                        if b0 is not None:
                            b0c = self.cells[b0]
                            if b0c.is_bulbable():
                                blocked = False
                                break
                        b1 = neighbours[block[1]]
                        if b1 is not None:
                            b1c = self.cells[b1]
                            if b1c.is_bulbable():
                                blocked = False
                                break
                        if blocked:
                            ret.append(diagonals[direction])
                            ret = list(set(ret))
                            return ret, Sgg.BLOCK
        ret = list(set(ret))
        return ret, Sgg.BLOCK

    def alg15(self):
        """

        .X..
        A2C.
        .D1.
        ....

        Can only use 1 of C and D, but must use A.

        ....
        ..1.
        A2..
        .X..

        .X..
        A2..
        ..1.
        ....

        ..X.
        ..2A
        .1..
        ....

        ....
        .1..
        ..2A
        ..X.

        neighbour order
         0
        3 1
         2

        diagonal order
        30
        21

        get a good 2
        get a blocked neighbour
        get a 1
        """
        blocked = [2, 0, 0, 2]
        bulb = [3, 3, 1, 1]
        ret = []
        for cell in self.cells:
            if cell.type is Sgg.NUMBERED and \
               cell.bulbs == 2 and \
               cell.sources == 0:
                neighbours = cell.get_all_neighbours()
                diagonals = cell.get_diagonals()
                for direction in range(4):
                    b = neighbours[blocked[direction]]
                    if b is not None and \
                       not self.cells[b].is_bulbable():
                        diagonal = diagonals[direction]
                        if diagonal is not None and \
                           self.cells[diagonal].type is Sgg.NUMBERED and \
                           self.cells[diagonal].bulbs == 1:
                            nb = neighbours[bulb[direction]]
                            if nb is not None and \
                               not self.cells[nb].is_bulbed():
                                ret.append(nb)
                                if True:
                                    return ret, Sgg.NEW_BULBS
        return list(set(ret)), Sgg.NEW_BULBS

    def alg14(self):
        """
        Find cells which if lit would block the only way to
        light another cell.

        Eg
        problem unlit cells labelled A,B,C,D and A is blocked:

        *o0ABo#ooo*o2*o0oooo*1oo
        o1XCDo1*o#1o*#*o#o*o1o*o
        o*o12*o#ooooooooo*2oo*#*
        #o...o.1..#*#oo*1o2*ooo2
        .#.1.o...1.o0o#1oo*o##o*

        Cannot put bulb at D because then A
        cannot be lit.
        had problems with this so also required D to be on As diagonals
        """
        ret = []
        cells = []
        for cell in self.cells:
            if cell.type is Sgg.UNKNOWN and not cell.is_satisfied():
                sources = []
                if cell.is_bulbable():
                    sources.append(cell.index)
                for direction in range(4):
                    line = self.get_lineb(cell.index, direction)
                    sources.extend(line)
                if sources:
                    # sources is all possible cell indexes
                    #that could light cell
                    t = [cell.index, set(sources)]
                    cells.append(t)

        for pair0 in cells:
            cell0 = pair0[0]
            if self.cells[cell0].is_bulbable():
                sources0 = pair0[1]
                diagonals = self.cells[cell0].get_diagonals()
                for pair1 in cells:
                    if pair1 != pair0:
                        cell1 = pair1[0]
                        sources1 = pair1[1]
                        if cell1 in diagonals and \
                           sources1.issubset(sources0) and \
                           not sources0.issubset(sources1) and \
                           cell1 not in sources0:
                            ret.append(cell0)
                            if True:
                                return ret, Sgg.BLOCK
        ret = list(set(ret))
        return ret, Sgg.BLOCK

    def alg13(self):
        """
        ...X
        X12.
        ...X

        ..@X
        X12a
        ..@X

        Can't put bulbs (@) in both of these places
        because then we cannot satisfy the 1, so there
        must be a bulb at a.

        .X.
        .1.
        .2.
        XaX

        X...
        a21X
        X...

        XaX
        .2.
        .1.
        .X.

        ...X
        X12a
        ...X

        neighbour order
         0
        3 1
         2

        diagonal order
        30
        21

        get a good 2
        get a blocked neighbour
        get a 1
        neighbour of this 1 must be blocked [0, 1, 2, 3] ie neighbour[direction]
        """
        ns = [2, 3, 0, 1]
        ds = [[1, 2], [2, 3], [0, 3], [0, 1]]
        ret = []
        for cell in self.cells:
            if cell.type is Sgg.NUMBERED and \
               cell.bulbs == 2 and \
               cell.sources == 0:
                neighbours = cell.get_all_neighbours()
                diagonals = cell.get_diagonals()
                for direction in range(4):
                    d0 = diagonals[ds[direction][0]]
                    d1 = diagonals[ds[direction][1]]
                    if d0 is not None and \
                       not self.cells[d0].is_bulbable() and \
                       d1 is not None and \
                       not self.cells[d1].is_bulbable():
                        if neighbours[direction] is not None:
                            n = self.cells[neighbours[direction]]
                            if n.type is Sgg.NUMBERED and \
                               n.bulbs == 1 and \
                               n.sources == 0:
                                neighbours1 = n.get_all_neighbours()
                                if neighbours1[direction] is not None and \
                                   not self.cells[neighbours1[direction]].is_bulbable():
                                    ret.append(neighbours[ns[direction]])
                                    if True:
                                        return list(set(ret)), Sgg.NEW_BULBS
        return list(set(ret)), Sgg.NEW_BULBS

    def alg12(self):
        """
        y...
        .2b.
        .aNz
        ..z.

        y is bulbable, z is not bulbable
        The N requires 1 more bulb which can only go at a or b.
        Xc..
        d2b.
        .aNz
        ..z.
        The 2 will share a or b but also needs to use c or d,
        so X cannot be a bulb and can be blocked.

        ...y
        ..2.
        zN..
        .z..

        .z..
        zN..
        ..2.
        ...y

        ..z.
        ..Nz
        .2..
        y...

        y...
        .2..
        ..Nz
        ..z.

        diagonal order
        30
        21
        neighbour order
         0
        3 1
         2
        """

        revds = [2, 3, 0, 1]
        open_n = [[0, 1], [1, 2], [2, 3], [0, 3]]
        block_n = [[2, 3], [0, 3], [0, 1], [1, 2]]

        ret = []
        for cell in self.cells:
            if cell.type is Sgg.NUMBERED and \
               cell.bulbs == 2 and \
               not cell.is_satisfied():
                # got an unsatisfied 2
                diagonals = cell.get_diagonals()
                for d in range(4):
                    diagonal = diagonals[d]
                    if diagonal is not None:
                        d_cell = self.cells[diagonal]
                        if d_cell.type is Sgg.UNKNOWN and \
                           d_cell.is_bulbable():
                            # with a bulbable diagonal
                            revd_index = diagonals[revds[d]]
                            if revd_index is not None:
                                revd_cell = self.cells[revd_index]
                                if revd_cell.type is Sgg.NUMBERED and \
                                   revd_cell.bulbs - revd_cell.sources == 1:
                                    # and the reverse diagonal requiring a bulb
                                    neighbours = revd_cell.get_all_neighbours()
                                    open_0 = neighbours[open_n[d][0]]
                                    open_1 = neighbours[open_n[d][1]]
                                    blocked_0 = neighbours[block_n[d][0]]
                                    blocked_1 = neighbours[block_n[d][1]]
                                    if open_0 is not None and \
                                       self.cells[open_0].is_bulbable() and \
                                       open_1 is not None and \
                                       self.cells[open_1].is_bulbable() and \
                                       blocked_0 is not None and \
                                       not self.cells[blocked_0].is_bulbable() and \
                                       blocked_1 is not None and \
                                       not self.cells[blocked_1].is_bulbable():
                                        # and required combination of
                                        # open and blocked
                                        ret.append(d_cell.index)
                                        if True:
                                            return ret, Sgg.BLOCK

        return ret, Sgg.BLOCK

    def alg11(self):
        """
        ...
        .2X
        1..

        ... Can't put bulbs (@) here because the 1 is touching 2 bulbs
        @2X
        1@.

        .@. Can put bulbs (@) here or
        @2X
        1..

        .@. here. Either way there must be a bulb above the 2
        .2X
        1@.

        ..1
        X2.
        .@.

        .@.
        X2.
        ..1

        .@.
        .2X
        1..

        1..
        .2X
        .@.

        neighbour order
         0
        3 1
         2

        diagonal order
        30
        21

        get a good 2
        get a blocked neighbour
        get a 1
        """
        ds = [3, 3, 1, 1]
        bs = [2, 0, 0, 2]
        ret = []
        for cell in self.cells:
            if cell.type is Sgg.NUMBERED and \
               cell.bulbs == 2 and \
               cell.sources == 0:
                neighbours = cell.get_all_neighbours()
                diagonals = cell.get_diagonals()
                for direction in range(4):
                    ns = ds[direction]
                    if neighbours[ns] is not None and \
                       not self.cells[neighbours[ns]].is_bulbable():
                        d = diagonals[direction]
                        if d is not None:
                            dc = self.cells[d]
                            if dc.type is Sgg.NUMBERED and \
                               dc.bulbs == 1 and \
                               dc.sources == 0:
                                bn = neighbours[bs[direction]]
                                b = self.cells[bn]
                                if b is not None and b.is_bulbable():
                                    ret.append(b.index)
                                    if True:
                                        return list(set(ret)), Sgg.NEW_BULBS
        return list(set(ret)), Sgg.NEW_BULBS

    def alg10(self):
        """
        If a 2 is not touching any bulbs and is blocked
        on one side we cannot place a bulb on either of the
        two oposite diagonals.
        ...
        .2.
        .X.

        @.@
        .2.
        .X.

        neighbour order
         0
        3 1
         2

        diagonal order
        30
        21

        """
        diags = [[1, 2], [2, 3], [0, 3], [0, 1]]
        ret = []
        for cell in self.cells:
            if cell.type is Sgg.NUMBERED and \
               cell.bulbs == 2 and \
               cell.sources == 0:
                neighbours = cell.get_all_neighbours()
                diagonals = cell.get_diagonals()
                for direction in range(4):
                    if neighbours[direction] is not None and \
                       not self.cells[neighbours[direction]].is_bulbable():
                        dcs = diags[direction]
                        dc0 = diagonals[dcs[0]]
                        dc1 = diagonals[dcs[1]]
                        if dc0 is not None and \
                           self.cells[dc0].is_bulbable():
                            ret.append(dc0)
                        if dc1 is not None and \
                           self.cells[dc1].is_bulbable():
                            ret.append(dc1)
                            if True:
                                ret = list(set(ret))
                                return ret, Sgg.BLOCK
        ret = list(set(ret))
        return ret, Sgg.BLOCK

    def alg9(self):
        """
        ..@.
        ..2X
        .1..


        ..@.
        ..2X
        a1..
        a must be blocked because 2 must be satisfied
        and any solution places a bulb next to the 2 and the 1.
        ..a.
        ..1a
        X2..
        .@..

        .@..
        X2..
        ..1a
        ..a.

        ..@.
        ..2X
        a1..
        .a..

        .a..
        a1..
        ..2X
        ..@.

        diagonal order
        30
        21

        neighbour order
         0
        3 1
         2
        """
        n_blocks = [3, 3, 1, 1]
        a_blocks = [[0, 1], [1, 2], [2, 3], [0, 3]]
        ret = []
        for cell in self.cells:
            if cell.type is Sgg.NUMBERED and \
               cell.bulbs == 2 and \
               cell.sources == 1:
                # got a 2 with one bulb
                neighbours = cell.get_all_neighbours()
                diagonals = cell.get_diagonals()
                for direction in range(4):
                    b = neighbours[n_blocks[direction]]
                    if b is not None:
                        b_cell = self.cells[b]
                        if not b_cell.is_bulbable() and \
                           self.openings(cell.index) == 2:
                            # which is blocked to a side
                            d = diagonals[direction]
                            if d is not None:
                                d_cell = self.cells[d]
                                if d_cell.type is Sgg.NUMBERED and \
                                   d_cell.bulbs == 1 and \
                                   d_cell.sources == 0:
                                    # and has an unsatisfied 1 on a diagonal
                                    b_neighbours = d_cell.get_all_neighbours()
                                    a_bs = a_blocks[direction]
                                    a_0 = b_neighbours[a_bs[0]]
                                    a_1 = b_neighbours[a_bs[1]]
                                    if a_0 is not None and \
                                       self.cells[a_0].is_bulbable():
                                        ret.append(a_0)
                                    if a_1 is not None and \
                                       self.cells[a_1].is_bulbable():
                                        ret.append(a_1)
                                        if True:
                                            ret = list(set(ret))
                                            return ret, Sgg.BLOCK
        ret = list(set(ret))
        return ret, Sgg.BLOCK

    def alg8(self):
        """
        if a NUMBERED cell has value 3 and on an adjacent diagonal
        has a cell requiring 1 more bulb and which can only place it
        next to the 3. Then we can place 2 of the 3s bulbs.
        Here the a requires one more bulb and so bulbs can be placed
        as shown in the first case

        ..x
        ..ax
        @3..
        .@..

        .@..
        @3..
        ..ax
        ..x

        ..@.
        ..3@
        xa..
        .x..

        .x
        xa..
        ..3@
        ..@.

        neighbour order
         0
        3 1
         2

        diagonal order
        30
        21
        """
        bulbs = [[2, 3], [0, 3], [0, 1], [1, 2]]
        ret = []
        for cell in self.cells:
            if cell.type is Sgg.NUMBERED and \
               cell.bulbs == 3 and \
               cell.sources < 3:
                # got an unfinished 3
                diagonals = cell.get_diagonals()
                for direction in range(4):
                    diagonal = diagonals[direction]
                    if diagonal is not None:
                        dc = self.cells[diagonal]
                        if dc.type is Sgg.NUMBERED:
                            if dc.bulbs == 1 and dc.sources == 0:
                                # with a 1 bulb cell on a diagonal
                                neighbours = cell.get_all_neighbours()
                                b = bulbs[direction]
                                b0 = neighbours[b[0]]
                                if b0 is not None and \
                                   self.cells[b0].is_bulbable():
                                    ret.append(b0)
                                b1 = neighbours[b[1]]
                                if b1 is not None and \
                                   self.cells[b1].is_bulbable():
                                    ret.append(b1)
                            if dc.bulbs - dc.sources == 1:
                                # with a cell on a diagonal needing 1 bulb
                                # in 2 openings
                                ab = bulbs[direction]
                                a_neighbours = dc.get_all_neighbours()
                                a_0 = a_neighbours[ab[0]]
                                a_1 = a_neighbours[ab[1]]
                                if a_0 is not None and \
                                   a_1 is not None and \
                                   self.cells[a_0].is_bulbable() and \
                                   self.cells[a_1].is_bulbable():
                                    neighbours = cell.get_all_neighbours()
                                    b = bulbs[direction]
                                    b0 = neighbours[b[0]]
                                    if b0 is not None and \
                                       self.cells[b0].is_bulbable():
                                        ret.append(b0)
                                        b1 = neighbours[b[1]]
                                        if b1 is not None and \
                                           self.cells[b1].is_bulbable():
                                            ret.append(b1)
                                            if True:
                                                return \
                                                    list(set(ret)), \
                                                    Sgg.NEW_BULBS
        return list(set(ret)), Sgg.NEW_BULBS

    def alg7(self):
        """
        if a NUMBERED cell has value 3 and requires 1 more bulb and
        on an adjacent diagonal has a cell requiring 1 more bulb
        only the cells marked @ can satisfy the 3 and this also
        satisfies the other needy cell (in this case a 1).
        So all other cells touching the other cell (the 1 here)
        can be blocked.

        .@1.
        *3@.
        .*..

        .*..
        *3@.
        .@1.

        ..*.
        .@3*
        .1@.


        .1@.
        .@3*
        ..*.

        blocked cells marked x
         x
        x1@.
        .@3*
        ..*.

        neighbour order
         0
        3 1
         2

        diagonal order
        30
        21
        """
        # existing bulbs ebulbs
        ebulbs = [[2, 3], [0, 3], [0, 1], [1, 2]]
        # blockable neighbours
        blocks = [[0, 1], [1, 2], [2, 3], [0, 3]]
        ret = []
        for cell in self.cells:
            if cell.type is Sgg.NUMBERED and \
               cell.bulbs == 3 and \
               cell.sources == 2:
                # got a 3 needing 1 more bulb
                diagonals = cell.get_diagonals()
                neighbours = cell.get_all_neighbours()
                for direction in range(4):
                    ebulb = ebulbs[direction]
                    eb1 = neighbours[ebulb[0]]
                    bulbs_OK = True
                    if eb1 is not None:
                        eb1c = self.cells[eb1]
                        if not eb1c.is_bulbed():
                            bulbs_OK = False
                    else:
                        bulbs_OK = False

                    eb2 = neighbours[ebulb[1]]
                    if eb2 is not None:
                        eb2c = self.cells[eb2]
                        if not eb2c.is_bulbed():
                            bulbs_OK = False
                    else:
                        bulbs_OK = False

                    if bulbs_OK:
                        diagonal = diagonals[direction]
                        if diagonal is not None:
                            dc = self.cells[diagonal]
                            if dc.type is Sgg.NUMBERED and \
                               dc.bulbs - dc.sources == 1:
                                oneighbours = dc.get_all_neighbours()
                                block = blocks[direction]
                                for i in range(2):
                                    b = block[i]
                                    if oneighbours[b] is not None and \
                                       self.cells[oneighbours[b]].is_bulbable():
                                        ret.append(oneighbours[b])
                                        if True:
                                            return list(set(ret)), Sgg.BLOCK
        return list(set(ret)), Sgg.BLOCK

    def alg6(self):
        """
        top 1 has 2 possibilities remaining (a,b)
        both of these satisfy middle 1 as well
        SO middle 1 must also be satisfied by these and cannot use c or d.
           xx
        ..a1x
        .c1b.
        ..d..

        xx
        x1a..
        .b1c.
        ..d..

        ..d..
        .c1b.
        ..a1x
           xx

        ..d..
        .b1c.
        x1a..
        xx

        neighbour order
         0
        3 1
         2

        diagonal order
        30
        21

        """
        # l1d diagonal positions for lower 1 relative to upper
        l1d = [2, 1, 1, 2]
        #u_open neighbours of upper that must be bulbable
        # and be the only bulbable cells for upper 1 (a, b) above
        u_open = [[3, 2], [1, 2], [1, 2], [2, 3]]
        x_closed = [[0, 1], [0, 3], [1, 2], [2, 3]]
        # x_closed are the blocked cells (xs) which are
        # relative to upper or lower 1s
        bulbable = [[3, 2], [1, 2], [3, 0], [1, 0]]
        # bulbable are cd and d in diagrams
        # the bulbable cells can be relative to upper or lower
        ret = []
        for c0 in self.cells:
            if c0.type is Sgg.NUMBERED and \
               not c0.is_satisfied() and \
               c0.bulbs == 1:
                n0 = c0.get_all_neighbours()
                d0 = c0.get_diagonals()
                for direction in range(2):
                    s1 = d0[l1d[direction]]
                    if s1 is not None:
                        c1 = self.cells[s1]
                        if c1.type is Sgg.NUMBERED and \
                           not c1.is_satisfied() and \
                           c1.bulbs == 1:
                            n1 = c1.get_all_neighbours()
                            t0s = u_open[direction]
                            a = n0[t0s[0]]
                            b = n0[t0s[1]]
                            if a is not None and b is not None and \
                               self.cells[a].is_bulbable() and \
                               self.cells[b].is_bulbable():
                                # got diagonal 1s
                                x0s = x_closed[direction]
                                x0 = n0[x0s[0]]
                                x1 = n0[x0s[1]]
                                if x0 is not None and x1 is not None and \
                                   not self.cells[x0].is_bulbable() and \
                                   not self.cells[x1].is_bulbable():
                                    b0s = bulbable[direction]
                                    c = n1[b0s[0]]
                                    d = n1[b0s[1]]
                                    if c is not None and \
                                       self.cells[c].is_bulbable():
                                        ret.append(c)
                                    if d is not None and \
                                       self.cells[d].is_bulbable():
                                        ret.append(d)
                                        if True:
                                            return list(set(ret)), Sgg.BLOCK

                for direction in range(2, 4):
                    s1 = d0[l1d[direction]]
                    if s1 is not None:
                        c1 = self.cells[s1]
                        if c1.type is Sgg.NUMBERED and \
                           not c1.is_satisfied() and \
                           c1.bulbs == 1:
                            t0s = u_open[direction]
                            a = n0[t0s[0]]
                            b = n0[t0s[1]]
                            if a is not None and b is not None and \
                               self.cells[a].is_bulbable() and \
                               self.cells[b].is_bulbable():
                                # got diagonal 1s
                                n1 = c1.get_all_neighbours()
                                x0s = x_closed[direction]
                                x0 = n1[x0s[0]]
                                x1 = n1[x0s[1]]
                                if x0 is not None and x1 is not None and \
                                   not self.cells[x0].is_bulbable() and \
                                   not self.cells[x1].is_bulbable():
                                    b0s = bulbable[direction]
                                    c = n0[b0s[0]]
                                    d = n0[b0s[1]]
                                    if c is not None and \
                                       self.cells[c].is_bulbable():
                                        ret.append(c)
                                    if d is not None and \
                                       self.cells[d].is_bulbable():
                                        ret.append(d)
                                        if True:
                                            return list(set(ret)), Sgg.BLOCK
        return list(set(ret)), Sgg.BLOCK

    def alg5(self):
        """
        if a NUMBERED cell has value 1 and has 2 unbulbable
        adjacent cells arranged diagonally to one another
        then no bulb can occupy its relative diagonal cell.
        eg the cell marked @ is unbulbable because it would
        block both of the possible bulb placements for the 1.
        4 cases:
        @..    .x.    .x.    ..@
        .1x    .1x    x1.    x1.
        .x.    @..    ..@    .x.

        """
        ret = []
        for cell in self.cells:
            if cell.type is Sgg.NUMBERED and \
               cell.bulbs == 1 and \
               not cell.is_satisfied():
                neighbour = cell.neighbours

                if neighbour[1] is not None:
                    n0 = self.cells[neighbour[1]]
                    if neighbour[2] is not None:
                        n1 = self.cells[neighbour[2]]
                        if not n0.is_bulbable() and not n1.is_bulbable():
                            diagonal = cell.get_diagonals()
                            if diagonal[3] is not None:
                                blocked = diagonal[3]
                                if self.cells[blocked].is_bulbable():
                                    ret.append(blocked)
                                    if True:
                                        ret = list(set(ret))
                                        return ret, Sgg.BLOCK

                if neighbour[0] is not None:
                    n0 = self.cells[neighbour[0]]
                    if neighbour[1] is not None:
                        n1 = self.cells[neighbour[1]]
                        if not n0.is_bulbable() and not n1.is_bulbable():
                            diagonal = cell.get_diagonals()
                            if diagonal[2] is not None:
                                blocked = diagonal[2]
                                if self.cells[blocked].is_bulbable():
                                    ret.append(blocked)
                                    if True:
                                        ret = list(set(ret))
                                        return ret, Sgg.BLOCK

                if neighbour[0] is not None:
                    n0 = self.cells[neighbour[0]]
                    if neighbour[3] is not None:
                        n1 = self.cells[neighbour[3]]
                        if not n0.is_bulbable() and not n1.is_bulbable():
                            diagonal = cell.get_diagonals()
                            if diagonal[1] is not None:
                                blocked = diagonal[1]
                                if self.cells[blocked].is_bulbable():
                                    ret.append(blocked)
                                    if True:
                                        ret = list(set(ret))
                                        return ret, Sgg.BLOCK

                if neighbour[3] is not None:
                    n0 = self.cells[neighbour[3]]
                    if neighbour[2] is not None:
                        n1 = self.cells[neighbour[2]]
                        if not n0.is_bulbable() and not n1.is_bulbable():
                            diagonal = cell.get_diagonals()
                            if diagonal[0] is not None:
                                blocked = diagonal[0]
                                if self.cells[blocked].is_bulbable():
                                    ret.append(blocked)
                                    if True:
                                        ret = list(set(ret))
                                        return ret, Sgg.BLOCK
        ret = list(set(ret))
        return ret, Sgg.BLOCK

    def alg4(self):
        """
        if a cell is unlit and can only be lit by one other cell
        (ie cannot be bulbed) return that cell.
        """
        ret = []
        for cell in self.cells:
            if cell.type is Sgg.UNKNOWN and \
               cell.sources == 0 and not cell.is_bulbable():
                # an unlit cell, check its lines
                possible_sources = []
                for direction in range(4):
                    line = self.get_line(cell.index, direction)
                    if line:
                        delete = []
                        for i in line:
                            if self.cells[i].sources or \
                               not self.cells[i].is_bulbable():
                                delete.append(i)
                        line = set(line)
                        line.difference_update(set(delete))
                        if len(line) == 1:
                            tmp = line.pop()
                            possible_sources.append(tmp)
                        elif len(line) > 1:
                            possible_sources = []
                            break

                if len(possible_sources) == 1:
                    bulb = possible_sources.pop()
                    ret.append(bulb)
                    self.progress = True
                    if True:
                        return list(set(ret)), Sgg.NEW_BULBS
                    break
        return list(set(ret)), Sgg.NEW_BULBS

    def alg3(self):
        """
        if a NUMBERED cell has value 3 then no bulb can occupy its
        adjacent diagonals as each would block bulbs along 2 sides.
        """
        ret = []
        for cell in self.cells:
            if cell.type is Sgg.NUMBERED and cell.bulbs == 3:
                blocked = cell.get_diagonals()
                blocked = [x for x in blocked
                           if x is not None and self.cells[x].is_bulbable()]
                if blocked:
                    ret.append(blocked)
        if ret:
            # flatten list
            chain = itertools.chain(*ret)
            ret = list(chain)
            ret = list(set(ret))
            self.progress = True
        return ret, Sgg.BLOCK

    def alg2(self):
        """
        if a cell is unlit and can only be lit by itself
        return that cell.
        """
        ret = []
        for cell in self.cells:
            if cell.is_bulbable():
                # an unlit cell, check its lines
                possible_sources = 0
                for direction in range(4):
                    line = self.get_line(cell.index, direction)
                    if line:
                        delete = []
                        for i in line:
                            if self.cells[i].sources or \
                               not self.cells[i].is_bulbable():
                                delete.append(i)
                        line = set(line)
                        line.difference_update(set(delete))
                        possible_sources += len(line)

                if not possible_sources:
                    ret.append(cell.index)
                    if True:
                        return ret, Sgg.NEW_BULBS
        return ret, Sgg.NEW_BULBS

    def alg1(self):
        """
        if a NUMBERED cell has bulbs value bulbs
        and only has bulbs neighbours that are bulbable
        they are all bulbs
        """
        ret = []
        for cell in self.cells:
            if cell.type is Sgg.NUMBERED and not cell.is_satisfied():
                bulbs = cell.bulbs - cell.sources
                neighbours = cell.get_neighbours()
                delete = []
                if bulbs != 4:
                    for item in neighbours:
                        if not self.cells[item].is_bulbable():
                            delete.append(item)
                    # if remining cells must be bulbs, add them to ret
                    if bulbs == len(neighbours) - len(delete):
                        neighbours = set(neighbours)
                        neighbours.difference_update(set(delete))
                        ret.append(list(neighbours))
                        if True:
                            ret = ret[0]
                            return ret, Sgg.NEW_BULBS
                else:
                    neighbours = set(neighbours)
                    neighbours.difference_update(set(delete))
                    if neighbours:
                        ret.append(list(neighbours))
                        if True:
                            ret = ret[0]
                            return ret, Sgg.NEW_BULBS
        if ret:
            # flatten list
            chain = itertools.chain(*ret)
            ret = list(chain)
            ret = list(set(ret))
        return ret, Sgg.NEW_BULBS

    def alg0(self):
        """
        if a NUMBERED cell has bulbs value 0
        then all its neighbours can be blocked.
        """
        ret = []
        for cell in self.cells:
            if cell.type is Sgg.NUMBERED and cell.bulbs == 0:
                neighbours = cell.get_neighbours()
                for neighbour in neighbours:
                    if self.cells[neighbour].is_bulbable():
                        ret.append(neighbour)
                        if True:
                            ret = list(set(ret))
                            return ret, Sgg.BLOCK
        if ret:
            ret = list(set(ret))
        return ret, Sgg.BLOCK

    def apply_algorithm(self, alg, job):
        res = []
        if alg == 0:
            res, res_type = self.alg0()
        elif alg == 1:
            res, res_type = self.alg1()
        elif alg == 2:
            res, res_type = self.alg2()
        elif alg == 3:
            res, res_type = self.alg3()
        elif alg == 4:
            res, res_type = self.alg4()
        elif alg == 5:
            res, res_type = self.alg5()
        elif alg == 6:
            res, res_type = self.alg6()
        elif alg == 7:
            res, res_type = self.alg7()
        elif alg == 8:
            res, res_type = self.alg8()
        elif alg == 9:
            res, res_type = self.alg9()
        elif alg == 10:
            res, res_type = self.alg10()
        elif alg == 11:
            res, res_type = self.alg11()
        elif alg == 12:
            res, res_type = self.alg12()
        elif alg == 13:
            res, res_type = self.alg13()
        elif alg == 14:
            res, res_type = self.alg14()
        elif alg == 15:
            res, res_type = self.alg15()
        elif alg == 16:
            res, res_type = self.alg16()
        elif alg == 17:
            res, res_type = self.alg17()
        elif alg == 18:
            res, res_type = self.alg18()
        elif alg == 19:
            res, res_type = self.alg19()
        if res:
            self.progress = True
            if job == 1:
                self.update_alg_usage(alg)
                if res_type is Sgg.NEW_BULBS:
                    self.add_bulbs([res[0]])
                elif res_type is Sgg.BLOCK:
                    cell = res[0]
                    if not self.cells[cell].is_bulbable():
                        pass
                    self.cells[cell].set_bulbability(False)
                res = []
            else:
                self.alg_usage[alg] += 1
        return res, res_type

    def test_solver(self):
        algs = list(range(len(self.alg_usage)))
        random.shuffle(algs)
        self.progress = False
        some_progress = False
        alg_index = 0
        while self.status:
            alg = algs[alg_index]
            self.apply_algorithm(alg, 1)
            if self.is_wrong():
                break
            if not self.is_finished():
                break
            if self.progress:
                some_progress = True
                self.progress = False
            alg_index += 1
            if alg_index == len(self.alg_usage):
                if some_progress:
                    alg_index = 0
                    self.progress = False
                    some_progress = False
                else:
                    # tried all algs and no progress
                    break

    def solver(self):
        self.progress = False
        some_progress = False
        alg = 0
        while self.status:
            self.apply_algorithm(alg, 1)
            if self.is_wrong():
                break
            if not self.is_finished():
                break
            if self.progress:
                some_progress = True
                self.progress = False
                alg = 0
            else:
                alg += 1
                if alg == len(self.alg_usage):
                    if some_progress:
                        alg = 0
                        self.progress = False
                        some_progress = False
                    else:
                        break

    def do_hint(self):
        self.hint = []
        self.progress = False
        self.check_errors()
        if not self.status:
            return
        self.prepare_for_algorithms()
        alg_index = -1
        while self.status:
            alg_index += 1
            if alg_index == self.alg_count:
                break
            res, res_type = self.apply_algorithm(alg_index, 2)
            if res:
                self.hint.append(alg_index)
                self.hint.append(res_type)
                self.hint.append(res[0])
                break

    def delete_hint(self):
        self.hint = []

    def user_light_lines(self, cell_index):
        """
        When we add a user bulb we add a bulb to guess
        and light up its row and column by setting their
        cell.guess to LIT
        """
        for direction in range(4):
            line = self.get_line(cell_index, direction)
            for cell in line:
                self.cells[cell].set_guess(Sgg.LIT)

    def user_unlight_lines(self, cell_index):
        """
        When we delete a user bulb we need to unlight
        its row and column if it is the only source.
        How? Must look at all sources!
        """
        unlit = []
        self.cells[cell_index].guess = None
        lit = []
        for cell in self.cells:
            if cell.guess is Sgg.BULB:
                for direction in range(4):
                    line = self.get_line(cell.index, direction)
                    lit.extend(line)
        slit = set(lit)
        for direction in range(4):
            line = self.get_line(cell_index, direction)
            for cell in line:
                if cell not in slit:
                    # ie not lit by another source
                    self.cells[cell].set_guess(None)
                    # nb if a cell was blocked before we lose that info
                    unlit.append(cell)

        return unlit

    def check_lighting(self):
        """
        Return index of all lit cells with no light source.
        """
        unlit = []
        for cell in self.cells:
            if cell.guess is Sgg.LIT:
                lit = False
                for direction in range(4):
                    line = self.get_line(cell.index, direction)
                    for c in line:
                        if self.cells[c].guess is Sgg.BULB:
                            lit = True
                            break
                    if lit:
                        break
                if not lit:
                    unlit.append(cell.index)
        return unlit

    def apply_user_click(self, action, cell_index):
        """
        All user actions are stored in Datum as guess.
        """
        changes = []
        before = self.get_colours()
        cell = self.cells[cell_index]
        if action is Sgg.BULB:
            self.user_light_lines(cell_index)
            cell.set_guess(action)
        elif action is Sgg.BLOCK:
            if cell.guess is Sgg.BULB:
                self.user_unlight_lines(cell_index)
            cell.set_guess(action)
        elif action is Sgg.DELETE:
            if cell.guess is Sgg.BULB:
                self.user_unlight_lines(cell_index)
            cell.set_guess(None)
        after = self.get_colours()
        for item in range(Sgg.cell_count):
            if after[item] != before[item]:
                changes.append(item)
        return changes

    def get_colours(self):
        ret = []
        for cell in self.cells:
            ret.append(cell.colour())
        return ret

    def apply_hint(self):
        """
        All hints are stored in Datum as guess.
        """
        changes = []
        if self.hint:
            before = self.get_colours()
            cell_index = self.hint[2]
            res_type = self.hint[1]
            cell = self.cells[cell_index]
            if res_type is Sgg.NEW_BULBS:
                self.user_light_lines(cell_index)
                cell.set_guess(Sgg.BULB)
            elif res_type is Sgg.BLOCK:
                cell.set_guess(res_type)
            after = self.get_colours()
            for item in range(Sgg.cell_count):
                if after[item] != before[item]:
                    changes.append(item)
        self.delete_hint()
        return changes

    def apply_guesses(self):
        """
        Assuming only correct guesses update cell data
        """
        for cell in self.cells:
            guess = cell.guess
            if guess is not None:
                if guess is Sgg.BULB:
                    self.add_bulbs([cell.index])
                elif guess is Sgg.BLOCK:
                    cell.set_bulbability(False)

    def prepare_for_algorithms(self):
        """
        Hint requested after user has been let loose on the grid.
        Her guesses are stored in cell.guess and have left all
        else untouched. Here we reset all cell data to initial
        values, then delete any user errors, then update cell data
        accordingly, ready for the algorithms to be applied.
        """

        for cell in self.cells:
            cell.init()
        for cell in self.cells:
            if cell.guess_is_wrong():
                cell.unset_guess()
        self.apply_guesses()


class Board(tk.Frame):
    def __init__(self, parent, settings, model):
        self.settings = settings
        self.model = model
        datum_dic = model.cells
        self.grid_widgets = []
        bg = self.settings.green
        tk.Frame.__init__(self, parent, relief='flat',
                          highlightbackground=self.settings.grey,
                          highlightthickness=2,
                          bd=2, bg=bg)

        square_index = 0
        for row in range(Sgg.grid_height):
            for column in range(Sgg.grid_width):
                #cell = datum_dic[column, row]
                cell = datum_dic[square_index]
                output = self.get_output(cell)
                text = output[0]
                fg = output[1]
                bg = output[2]
                square = TextSquare(self, 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
        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_update(self, job):
        """
        given a job get the corresponding image
        0-4 numbers
        5 unknown
        6 unnumbered
        7 bulb
        8 lit
        9 block
        10 bad bulb
        11 bad block
        12 hint
        13 hint off
        return symbol, fg, bg
        """
        numbers = [' ', '1', '2', '3', '4']
        bulb = 'o'
        hint = '?'
        lit = '#ACACD9'
        unlit = '#5D5D5D'
        offwhite = '#E6E6E6'
        red = self.settings.red
        block = '+'
        update = []
        if job > -1 and job < 5:
            update = [numbers[job], 'white', unlit]
        elif job == 5:
            update = [numbers[0], 'white', offwhite]
        elif job == 6:
            update = [numbers[0], unlit, unlit]
        elif job == 7:
            update = [bulb, 'yellow', '#262691']
        elif job == 8:
            update = [numbers[0], lit, lit]
        elif job == 9:
            update = [block, 'black', offwhite]
        elif job == 10:
            update = [bulb, 'yellow', red]
        elif job == 11:
            update = [block, 'white', red]
        elif job == 12:
            update = [hint, 'black', 'orange']
        elif job == 13:
            update = [' ', 'black', offwhite]
        return update

    def get_output(self, cell):
        job = 5
        if cell.type == Sgg.NUMBERED:
            job = cell.bulbs
        elif cell.type == Sgg.UNNUMBERED:
            job = 6
        return self.get_update(job)

    def my_update(self, changes):
        """
        changes is a list of cells that have changed.
        Work out the job number, get the required
        update from get_update, and apply it.
        """
        for cell_index in changes:
            cell = self.model.cells[cell_index]
            widget = self.grid_widgets[cell_index]
            if cell.sources > 0:
                update = self.get_update(8)
            elif cell.guess is Sgg.LIT:
                update = self.get_update(8)
            elif cell.guess is Sgg.BLOCK:
                update = self.get_update(9)
            elif cell.guess is Sgg.BULB:
                update = self.get_update(7)
            else:
                update = self.get_update(5)
            widget.my_update(update)

    def update_square(self, square_index, job):
        """
        Update cell square_index with a known operation (job)
        """
        update = self.get_update(job)
        widget = self.grid_widgets[square_index]
        widget.my_update(update)


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

    def __init__(self, master, settings):
        ViewB.__init__(self, master, settings)
        self.model = None
        self.image_dic = {}
        self.make_image_dic()
        self.image_dic['start'] = [self.images.start_akari, 0, True]
        self.do_layout(self)
        self.entry_box.activate()

    def make_board(self):
        """
        draw the squares
        """

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

    def update_clock_box(self, algorithm):
        value = '  '
        if algorithm is not None:
            value = Sgg.ALGORITHM_NAMES[algorithm]
        self.clock.box.set_text(value)


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

    def __init__(self, root):
        self.settings = Settings()
        self.master = root
        self.flasher = None
        self.model_data = ModelData()
        self.model = None
        self.hint_status = Sgg.GIVE_HINT
        self.status = False
        self.active = False
        self.error_detected = False
        self.puzzle_number = None
        self.exposed = {}
        screen_shot_state = False
        self.screen_shot = ScreenShot(PzlG.app_name,
                                      'dump_',
                                      screen_shot_state)
        self.view = View(root, self.settings)
        puzzle_number = None
        self.start_game(puzzle_number)

    def set_puzzle_number(self, puzzle_number):
        if puzzle_number is None or puzzle_number < 0 or \
                puzzle_number > self.model_data.puzzle_count - 1:
            puzzle_number = randint(0, self.model_data.puzzle_count - 1)
        self.puzzle_number = puzzle_number

    def start_game(self, puzzle_number):
        self.set_puzzle_number(puzzle_number)
        self.view.clock_init()
        self.view.clock_restart()
        self.view.game_over()
        data, difficulty = self.model_data.get_puzzle(self.puzzle_number)
        self.model = Model(self.model_data)
        self.model.load_puzzle(data, difficulty)
        self.model.set_neighbours()
        self.ready_for_hint()
        self.view.thumbs_up()
        self.view.model = self.model
        self.view.make_board()
        self.add_callbacks()
        self.view.game_on()
        self.error_detected = False
        self.view.entry_box.clear()
        self.view.entry_box.write_box(str(self.puzzle_number))
        self.view.update_score_box(self.model.difficulty)
        self.flasher = Flasher(self.view.clock.box.label,
                               self.master, self.settings)
        self.status = False
        self.active = True
        self.hint_status = Sgg.GIVE_HINT

    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.get_puzzle_number)
        self.view.board.add_callback(self.square_event)

    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 == '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 service_toolbar(self, button_index, mouse_index):
        PzlG.logger.logger.info("service_toolbar " +
                                str(button_index) +
                                ' ' +
                                str(mouse_index))

        if mouse_index == 2:
            alg_index = None
            if self.model.hint:
                alg_index = self.model.hint[0]
            self.view.do_tooltip(button_index, alg_index)
        elif mouse_index == 3:
            if button_index == 0:
                self.puzzle_number += 1
                self.start_game(self.puzzle_number)
        elif mouse_index == 1:
            if button_index == 0:
                self.start_game(None)
            elif button_index == 1:
                if not self.model.user_is_finished():
                    self.give_hint()
                    self.screen_shot.snap()
            elif button_index == 2:
                if not self.model.user_is_finished():
                    self.check_errors()
            elif button_index == 3:
                if not self.model.user_is_finished():
                    self.show_solution()

    def square_event(self, cell_index, mouse_index):
        """
        left mouse add bulb
        middle mouse delete
        right mouse add block
        """

        if self.active:
            PzlG.logger.logger.info("square_event " +
                                    str(mouse_index) +
                                    ' ' +
                                    str(cell_index))
            # need to remove any active hint
            if self.model.hint:
                self.view.hint_off()
                hint_cell_index = self.model.hint[2]
                self.view.board.update_square(hint_cell_index, 13)
                self.model.hint = []

            changes = []
            PzlG.logger.logger.info("square_event " +
                                    str(cell_index) +
                                    ' ' +
                                    str(mouse_index))
            datum = self.model.cells[cell_index]
            if mouse_index == 1:
                # player has left clicked on this cell
                # ie wants to add a bulb
                if datum.type is not Sgg.UNNUMBERED and \
                   datum.type is not Sgg.NUMBERED:
                    changes = self.model.apply_user_click(Sgg.BULB,
                                                          cell_index)
            elif mouse_index == 2:
                # player has middle clicked on this cell
                # ie wants to delete
                if datum.type is not Sgg.UNNUMBERED and \
                   datum.type is not Sgg.NUMBERED:
                    changes = self.model.apply_user_click(Sgg.DELETE,
                                                          cell_index)
            elif mouse_index == 3:
                # player has right clicked on this cell
                # ie wants to add a block
                if datum.type is not Sgg.UNNUMBERED and \
                   datum.type is not Sgg.NUMBERED:
                    changes = self.model.apply_user_click(Sgg.BLOCK,
                                                          cell_index)
            if changes:
                # send list of changed cells to board
                self.view.board.my_update(changes)
            self.check_finished()

    def ready_for_hint(self):
        if not self.model.user_is_finished():
            self.hint_status = Sgg.GIVE_HINT
            self.model.delete_hint()
            if self.model.status:
                self.view.hint_off()
                self.view.thumbs_up()
                self.error_detected = False
            self.view.clock.set_show(True)

    def give_hint(self):
        if self.hint_status == Sgg.GIVE_HINT:
            self.model.do_hint()
            if not self.model.status:
                self.view.thumbs_down()
                self.colour_errors()
                self.error_detected = True
            elif self.model.hint:
                self.view.hint_on()
                cell_index = self.model.hint[2]
                self.model.cells[cell_index].set_guess(Sgg.HINT)
                self.view.board.update_square(cell_index, 12)
                self.view.update_clock_box(self.model.hint[0])
                self.view.clock.set_show(False)
            self.hint_status = Sgg.APPLY_HINT
        else:
            self.apply_hint()
            self.ready_for_hint()
            self.check_finished()

    def check_errors(self):
        if self.error_detected:
            # we know there's an error already, so now we correct it
            self.tidy_error()
            self.model.check_errors()
            if not self.model.status:
                PzlG.logger.logger.error("errors_check ")
            self.ready_for_hint()
        else:
            self.model.check_errors()
            if not self.model.status:
                self.view.thumbs_down()
                self.error_detected = True
                self.colour_errors()

    def check_finished(self):
        """
        Check if finished.
        If so, handle buttons, flasher and clock
        """
        if self.model.user_is_finished():
            self.screen_shot.snap()
            self.view.game_over()
            self.view.clock.set_show(True)
            self.view.clock_stop()
            self.flasher.flash()

    def show_solution(self):
        for cell_index in range(Sgg.cell_count):
            cell = self.model.cells[cell_index]
            if cell.answer:
                job = 7
                self.view.board.update_square(cell_index, job)
            elif cell.type is not Sgg.NUMBERED and \
                 cell.type is not Sgg.UNNUMBERED:
                job = 8
                self.view.board.update_square(cell_index, job)
        self.view.game_over()
        self.view.clock_stop()

    def apply_hint(self):
        changes = self.model.apply_hint()
        if changes:
            # send list of changed cells to board
            self.view.board.my_update(changes)

    def colour_errors(self):
        """
        Colour the error squares and note their indexes for uncolouring.
        """
        for cell_index in self.model.user_errors:
            cell = self.model.cells[cell_index]
            if cell.guess is Sgg.BULB:
                job = 10
            else:
                job = 11
            self.view.board.update_square(cell_index, job)

    def tidy_error(self):
        """
        update view and undo errors
        """
        job = 5
        for cell_index in self.model.user_errors:
            cell = self.model.cells[cell_index]
            self.view.board.update_square(cell_index, job)
            cell.unset_guess()

        unlit = self.model.check_lighting()
        for u in unlit:
            self.view.board.update_square(u, job)
            self.model.cells[u].unset_guess()

        self.model.user_errors = []
        self.model.delete_hint()
        self.model.status = True
        self.error_detected = False
        self.view.thumbs_up()

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

    def get_puzzle_number(self, text):
        if text.find('XYZZY') != -1:
            self.view.xyzzy()
            self.view.entry_box.clear()
        else:
            try:
                puzzle_number = int(text)
                self.start_game(puzzle_number)
                self.screen_shot.snap()
            except ValueError:
                self.view.entry_box.clear()


class ModelData(object):
    def __init__(self):
        self.puzzles = '\
 '
        self.puzzles = base64.b64decode(self.puzzles)
        self.puzzles = zlib.decompress(self.puzzles)
        self.make_puzzles()
        self.puzzle_count = len(self.puzzles)

    def make_puzzles(self):
        puzzles = []
        items = self.puzzles.decode('utf8').split(',')
        for item in items:
            puzzles.append(int(item))
        self.puzzles = puzzles
        self.make_puzzles_1()

    def get_puzzle(self, puzzle_index):
        if puzzle_index > -1 and puzzle_index < self.puzzle_count:
            PzlG.logger.logger.info("puzzle_number " +
                                    str(puzzle_index))
            puzzle = self.puzzles[puzzle_index]
            Sgg.grid_width = puzzle[0]
            Sgg.grid_height = puzzle[1]
            difficulty = puzzle[2]
            Sgg.cell_count = Sgg.grid_width * Sgg.grid_height
            return puzzle[3:], difficulty

    def make_puzzles_1(self):
        tmp_puzzles = []
        len_puzzles = len(self.puzzles)
        istart = 0
        while istart < len_puzzles:
            grid_width = self.puzzles[istart]
            grid_height = self.puzzles[istart+1]
            iend = istart + 3 + grid_width * grid_height
            puzzle = self.puzzles[istart:iend]
            tmp_puzzles.append(puzzle)
            istart = iend
        self.puzzles = tmp_puzzles


class ModelDataT(object):
    def __init__(self):
        self.puzzles = '\
eJx9WFmW4yAMvBAfgGPhuf/FJo6RVVWIfk5nAbSVVvoqV+mtnGV8/+6n/b6NchSbK+dc8V/1+2dz3WB9lM/8Vb+fnWiQv9MYrLd6v/ohRMzYCfpUyOC0wXn7qZg/mUr4NOLUxOxn7aFspNPC5zGobrZvfAMRZ2WTXaA6XnEm9J+Uq8kKQxZnxivt3usA49gZdN0GfXy7kdhWFNk2hY8fov6rTbFuZnwLIyO02FMGtDb5Ik/UiDUcoFsT05DaZnDalOYSkAKDF7V2iiFWmuy4XYN0YAkzvq4niCKR7JWmjh5vwjEK4Yn68sZY9khmjVCbCu+DaJvYoPvxbYgEP4tcW6LXmVKxdMe1FkwbDv9aNKANMjh2+6e07+viuEMLs4crUyAavnjKpCfZ8cZjI5qwpgNvpxq0uvrBSngS47S9pxhrPr2e4Chf/a1W61qTlcijDrxP4DAmNoyt88F8w8z+vPigZR3WVKPQtsN7biX6osNOK9r8TKhDb4yDI8GN+5g+WgWnLr9I7ddFEWxwrkOGof2YRx45aA1iHMi2yU/bi9oxSAfECb0QSPZyCkJYCYyiiFFaYwHt5FqBtY/ju8JqZBoi5D7uRX2NM8aDgcZOljuPziHBoCeg1h7veFLzErU+4cyZ7DBuiKbnS/i3Totqwcx4ci3qJlqGvqkF5R92D0Gjc31uxCUGSkc/OkMl7oo51zjc80ly9cmqLdJhVdKphiuW8tUKewJ1A/qa7GH33vHN9rJaqhUukLWSYRV48WTCE15f9iL70K4hOmHUcq7vpqHAmrtBlu3aIfAs66Q7PB924Yn5qHNlLzFxrGM51l2NAu14O0syK1dP57ywVuTINtrjmn280jXqOuAZtRe5sxbsO5//d54OpHrBfogXoTPxNNbktU6H9OiSei21EghydVnr8YpxVkUUGZ1KMf9zbfNZCL2i2Zjl1/gj9569Tvo/ldrGvz+6xxrvjPH6cO4dhMtufe0u7Nvj9QB7QtHxz+x+E5du9VxkPUcwVgOPGrU61pUaZbLEDrQRHxGp/O8SvfmskydPfcfGE1hjXFuNq5Ctsai3C+Skk3L4qi86YsVhX/WCE+tIpPC8y8is/lZf7LiyNrquvoj15zvWhAZxzRPTilz4SatFe/dzK5hC7yjnVj7a6rt4e8WeU4UjT4sxayFunhtaLzDvOEPwhtGIRzzee9VHXtvXO6rmySjavTSatNNih/gbTfRE3ZzPI3m3rjX1p9t/WRsbpg=='

        self.puzzles = base64.b64decode(self.puzzles)
        self.puzzles = zlib.decompress(self.puzzles)
        self.make_puzzles()
        self.puzzle_count = len(self.puzzles)

    def make_puzzles(self):
        puzzles = []
        items = self.puzzles.decode('utf8').split(',')
        for item in items:
            puzzles.append(int(item))
        self.puzzles = puzzles
        self.make_puzzles_1()

    def get_puzzle(self, puzzle_index):
        if puzzle_index > -1 and puzzle_index < self.puzzle_count:
            PzlG.logger.logger.info("puzzle_number " +
                                    str(puzzle_index))
            puzzle = self.puzzles[puzzle_index]
            Sgg.grid_width = puzzle[0]
            Sgg.grid_height = puzzle[1]
            difficulty = puzzle[2]
            Sgg.cell_count = Sgg.grid_width * Sgg.grid_height
            return puzzle[3:], difficulty

    def make_puzzles_1(self):
        tmp_puzzles = []
        len_puzzles = len(self.puzzles)
        istart = 0
        while istart < len_puzzles:
            grid_width = self.puzzles[istart]
            grid_height = self.puzzles[istart+1]
            iend = istart + 3 + grid_width * grid_height
            puzzle = self.puzzles[istart:iend]
            tmp_puzzles.append(puzzle)
            istart = iend
        self.puzzles = tmp_puzzles


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