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

# This file is part of ROBOTGAME
#
# ROBOTGAME is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# ROBOTGAME is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# ROBOTGAME.  If not, see <http://www.gnu.org/licenses/>.
#
# ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '
#
#                         colourboxes.py
#                     --------------------
#       date created : Wed Aug 8 2012
#          copyright : (C) 2012 Niels G. W. Serup
#      maintained by : Niels G. W. Serup <ns@metanohi.name>

"""
Colour boxes.
"""

from __future__ import print_function
import random
import itertools

def generate_colour_boxes(nwells, nboxes):
    """
    Generate colour boxes that can be used to make all wells white. None of the
    generated colour boxes are white.

    Arguments:
    nwells -- number of wells
    nboxes -- maximum number of boxes needed to make all wells white.

    Return [[(r, g, b)]]
      where r : 0|1,  g : 0|1,  b : 0|1
    """
    
    nbits = nwells * 3
    data = [[0 for _ in range(nboxes)] for _ in range(nbits)]

    def insert_1(x):
        t = random.randrange(0, nboxes)
        x0, x1 = _get_oxs(x)
        for y in range(t, nboxes) + range(0, t):
            if data[x][y] == 0 and not (data[x0][y] == 1 and data[x1][y] == 1):
                data[x][y] = 1
                break
        else:
            raise Exception("Cannot maintain no 111s invariant.")

    def insert_two_1(x):
        t = random.randrange(0, nboxes)
        x0, x1 = _get_oxs(x)
        if len(list(filter(lambda y: data[x][y] == 0 and not (data[x0][y] == 1 and data[x1][y] == 1),
                           range(nboxes)))) < 2:
            return
        for _ in range(2):
            for y in range(t, nboxes) + range(0, t):
                if data[x][y] == 0 and not (data[x0][y] == 1 and data[x1][y] == 1):
                    data[x][y] = 1
                    break

    for x in range(len(data)):
        insert_1(x)
    for x in range(len(data)):
        for _ in range(random.randrange(0, (nboxes + 1) / 2)):
            insert_two_1(x)

    boxes = []
    for y in range(nboxes):
        box = []
        boxes.append(box)
        for x in range(0, nbits, 3):
            r = data[x][y]
            g = data[x + 1][y]
            b = data[x + 2][y]
            box.append((r, g, b))
    return boxes

def _get_oxs(x):
    return (x + 1, x + 2) if x % 3 == 0 \
        else (x - 1, x + 1) if x % 3 == 1 \
        else (x - 2, x - 1)

def generate_random_box(nwells, min_nonblacks=0):
    """
    Generate a box that triggers nwells wells, with random colors except white
    (111).

    Arguments:
    min_nonblacks -- minimum number of well colours in a box required not to be
    black.
    """
    def gen_wc():
        wc = [random.choice((0, 1)) for i in range(3)]
        if all(b == 1 for b in wc):
            wc[random.randrange(3)] = 0
        return wc
    def gen_wc_nonblack():
        wc = gen_wc()
        if all(b == 0 for b in wc):
            wc[random.randrange(3)] = 1
        return wc
    colours = [tuple(gen_wc()) for _ in range(nwells)]
    nonblack = lambda t: any(n == 1 for n in t)
    missing_nonblacks = min_nonblacks - len(list(filter(nonblack, colours)))
    i = 0
    while missing_nonblacks > 0:
        if not nonblack(colours[i]):
            colours[i] = gen_wc_nonblack()
            missing_nonblacks -= 1
        i += 1
    return colours

def get_colours(boxes):
    colours = []
    for i in range(len(boxes[0])):
        r, g, b = boxes[0][i]
        for j in range(1, len(boxes)):
            r1, g1, b1 = boxes[j][i]
            r ^= r1
            g ^= g1
            b ^= b1
        colours.append((r, g, b))
    return colours

def makes_all_wells_white(boxes):
    """
    Determine if the boxes make all wells white when XOR'ed together.
    """
    return all(c == 1 for c in itertools.chain(*get_colours(boxes)))