'''
Construct and return Tenner Grid CSP models.
'''

from cspbase import *
import itertools


def make_var_array(board, n_cols, n_rows):
    for i in range(n_rows):
        for j in range(n_cols):
            cell = board[i][j]
            name = "Variable " + str(i) + " " + str(j)
            domain = list(range(n_cols)) if cell == -1 else [cell]
            board[i][j] = Variable(name, domain)
    return board


def get_binary_cons(csp, pairs):
    doms = [p.domain() for p in pairs]
    tups = []
    for t in itertools.product(*doms):
        if t[0] != t[1]:
            tups.append(t)
    return tups


def tenner_csp_model_1(initial_tenner_board):
    '''Return a CSP object representing a Tenner Grid CSP problem along 
        with an array of variables for the problem. That is return

        tenner_csp, variable_array

        where tenner_csp is a csp representing tenner grid using model_1
        and variable_array is a list of lists

        [ [  ]
          [  ]
          .
          .
          .
          [  ] ]

        such that variable_array[i][j] is the Variable (object) that
        you built to represent the value to be placed in cell i,j of
        the Tenner Grid (only including the first n rows, indexed from 
        (0,0) to (n,9)) where n can be 3 to 7.


        The input board is specified as a pair (n_grid, last_row). 
        The first element in the pair is a list of n length-10 lists.
        Each of the n lists represents a row of the grid. 
        If a -1 is in the list it represents an empty cell. 
        Otherwise if a number between 0--9 is in the list then this represents a 
        pre-set board position. E.g., the board

        ---------------------  
        |6| |1|5|7| | | |3| |
        | |9|7| | |2|1| | | |
        | | | | | |0| | | |1|
        | |9| |0|7| |3|5|4| |
        |6| | |5| |0| | | | |
        ---------------------
        would be represented by the list of lists

        [[6, -1, 1, 5, 7, -1, -1, -1, 3, -1],
        [-1, 9, 7, -1, -1, 2, 1, -1, -1, -1],
        [-1, -1, -1, -1, -1, 0, -1, -1, -1, 1],
        [-1, 9, -1, 0, 7, -1, 3, 5, 4, -1],
        [6, -1, -1, 5, -1, 0, -1, -1, -1,-1]]


        This routine returns model_1 which consists of a variable for
        each cell of the board, with domain equal to {0-9} if the board
        has a 0 at that position, and domain equal {i} if the board has
        a fixed number i at that cell.

        model_1 contains BINARY CONSTRAINTS OF NOT-EQUAL between
        all relevant variables (e.g., all pairs of variables in the
        same row, etc.).
        model_1 also constains n-nary constraints of sum constraints for each 
        column.
    '''
    # IMPLEMENT
    board, sum_row = initial_tenner_board
    n_cols, n_rows = 10, len(board)
    csp = CSP("model_1")
    var_array = make_var_array(board, n_cols, n_rows)
    for i in range(n_rows):
        for pairs in itertools.combinations(var_array[i], 2):
            tups = get_binary_cons(csp, pairs)
            con = Constraint("Constraint", pairs)
            con.add_satisfying_tuples(tups)
            csp.add_constraint(con)
    return csp, var_array


def tenner_csp_model_2(initial_tenner_board):
    '''Return a CSP object representing a Tenner Grid CSP problem along 
        with an array of variables for the problem. That is return

        tenner_csp, variable_array

        where tenner_csp is a csp representing tenner using model_1
        and variable_array is a list of lists

        [ [  ]
          [  ]
          .
          .
          .
          [  ] ]

        such that variable_array[i][j] is the Variable (object) that
        you built to represent the value to be placed in cell i,j of
        the Tenner Grid (only including the first n rows, indexed from 
        (0,0) to (n,9)) where n can be 3 to 7.

        The input board takes the same input format (a list of n length-10 lists
        specifying the board as tenner_csp_model_1.

        The variables of model_2 are the same as for model_1: a variable
        for each cell of the board, with domain equal to {0-9} if the
        board has a -1 at that position, and domain equal {i} if the board
        has a fixed number i at that cell.

        However, model_2 has different constraints. In particular, instead
        of binary non-equals constaints model_2 has a combination of n-nary 
        all-different constraints: all-different constraints for the variables in
        each row, and sum constraints for each column. You may use binary 
        contstraints to encode contiguous cells (including diagonally contiguous 
        cells), however. Each -ary constraint is over more 
        than two variables (some of these variables will have
        a single value in their domain). model_2 should create these
        all-different constraints between the relevant variables.
    '''
    # IMPLEMENT
    board, sum_row = initial_tenner_board
    n_cols, n_rows = 10, len(board)
    csp = CSP("model_2")
    var_array = make_var_array(board, n_cols, n_rows)
    for i in range(n_rows):
        for pairs in itertools.combinations(var_array[i], 2):
            tups = get_binary_cons(csp, pairs)
            con = Constraint("Constraint", pairs)
            con.add_satisfying_tuples(tups)
            csp.add_constraint(con)
    return csp, var_array
