#!/usr/bin/env python
"""
================================LICENSE======================================
Copyright (c) 2015 Chirag Mello & Mario Tambos

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================LICENSE======================================


Implementation of the augmented spatial pooler according to:

Thornton, John, and Andrew Srbic. "Spatial pooling for greyscale images."
International Journal of Machine Learning and Cybernetics 4, no. 3 (2013):
207-216.

A region is represented by a 4-dimensional numpy array **columns**, such that:

columns[i, j, k, l] == v in [0, 1) => the column mapped to thecoordinates
                                     [i, j] has a *potential* synapse to
                                     coordinates [k, l] with permanence v

Another way of thinking about it is that **columns** is a 2-dimensional array
whose elements are columns, and each column is in turn a 2-dimensional array
of synapses.
"""
from __future__ import division, print_function

from collections import defaultdict
import cPickle as pickle
from datetime import datetime
from functools import partial
from pprint import pprint

import numpy as np
import numexpr as ne

from utils import read_input, iter_columns, iter_synapses
from common import (update_inhibition_area, calculate_min_activity,
                    inhibit_columns, initialise_synapses, test_for_convergence)
from utils import RingBuffer


def calculate_overlap(input_vector, columns, min_overlap, connect_threshold,
                      boost):
    """
    Implements the calculateOverlap function from the paper (p. 3).
    :param input_vector: a single input_vector from the images set.
    :param columns: the 4-dimensional array of HTM columns: a 4-dimensional
                    array of shape *shape* that represents the HTM columns and
                    their synapses; each element columns[a, b, c, d] contains
                    the permanence value of the synapse connecting the column
                    with coordinates (a, b) to input with coordinates (c, d).
    :param min_overlap: the BSP's minOverlap parameter (p. 3). Type: float.
    :param connect_threshold: the BSP's connectThreshold parameter (p. 3).
                              Type: float.
    :param boost: the BSP's boost matrix (pp. 3, 4).
                  It is a matrix of shape (columns.shape[0], columns.shape[1])
    :param overlap_sum: the BSP's overlapSum matrix (p. 4). This parameter is
                        modified and returned. It is a dictionary with tuples
                        (y, x) of HTM column coordinates as keys and deque
                        (a queue implementation) instances as values. The queue
                        for key [a, b] stores a 1 each time the overlap of the
                        column [a, b] was above the minOverlap threshold during
                        the last 1000 iterations.
    :return: a tuple (overlap, overlap_sum). *overlap* is an array of shape
             (columns.shape[0], columns.shape[0]); each element overlap[a, b]
             contains the overlap of the column [a, b] with the input_vector.
             *overlap_sum* is the parameter of the same name; the queue in
             overlap_sum[a, b] will have a 1 pushed into it if the overlap for
             the column [a, b] was above the min_overlap threshold this
             iteration.
    """
    # Initialize the overlap array.
    overlap = np.zeros(columns.shape[:2])
    # for each column ...
    for y, x, syn_matrix in iter_columns(columns):  # @UnusedVariable
        c = (y, x)
        # calculate the overlap as the sum of pixel's values in the
        # input_vector assigned to *connected* synapses and, ...
        # (numexpr.evaluate optimizes and carries out the operations defined in
        # its string argument, it is used here because numpy has some
        # problems when comparing NaNs with numbers)
        active_synapses = ne.evaluate('syn_matrix >= connect_threshold')
        overlap[c] = input_vector[active_synapses].sum()
        # if the overlap is not enough, ...
        if overlap[c] < min_overlap:
            # reset it, but, ...
            overlap[c] = 0
        # if the overlap is enough, ...
        else:
            # then boost it.
            overlap[c] *= boost[c]

    return overlap


def learn_synapse_connections(columns, active, input_vector, p_inc,
                              p_dec, activity, min_activity, boost, b_inc,
                              p_mult, connect_threshold, distances, b_max):
    """
    Calculates the minActivity matrix from the paper (p. 6).
    :param columns: the 4-dimensional array of HTM columns: a 4-dimensional
                    array of shape *shape* that represents the HTM columns and
                    their synapses; each element columns[a, b, c, d] contains
                    the permanence value of the synapse connecting the column
                    with coordinates (a, b) to input with coordinates (c, d).
    :param active: array of shape (columns.shape[0], columns.shape[0]);
                   each element active[a, b] is True if the column [a, b]
                   is active this iteration and False otherwise.
    :param input_vector: the input to be learned. A 2-dimensional array of