import numpy as np

#from pyics import Model
import random


def decimal_to_base_k(n, k):
    """Converts a given decimal (i.e. base-10 integer) to a list containing the
    base-k equivalant.

    For example, for n=34 and k=3 this function should return [1, 0, 2, 1]."""
    lrg_pow = 1
    while 1:
        if n >= k**lrg_pow:
            lrg_pow += 1
        else:
            break

    num_list = [0] * (lrg_pow)

    while n:
        sub_num = k**lrg_pow
        if n >= sub_num:
            n -= sub_num
            num_list[-(lrg_pow + 1)] += 1
        else:
            lrg_pow -= 1

    return num_list


def base_k_to_decimal(n, k):
    """Converts a base k list to an int"""
    num = 0
    for i in range(0, len(n)):
        num += n[-(i + 1)] * (k**i)
    return int(num)

def randint_neq(x, y, n):
    """Generates a random int between x and (including) y not equal to n"""
    while 1:
        r = random.randint(x, y)
        if r != n:
            return r


class CASim():
    def __init__(self, r=1, k=2, w=50, h=50, rule=184, c_dens = 0.5):
        self.t = 0
        self.rule_set = []
        self.config = None

        self.r = r
        self.k = k
        self.width = w
        self.height = h

        self.rule = rule
        self.c_dens = c_dens

        self.flow = 0

    def setter_rule(self, val):
        """Setter for the rule parameter, clipping its value between 0 and the
        maximum possible rule number."""
        rule_set_size = self.k ** (2 * self.r + 1)
        max_rule_number = self.k ** rule_set_size
        return max(0, min(val, max_rule_number - 1))

    def build_rule_set(self):
        """Sets the rule set for the current rule.
        A rule set is a list with the new state for every old configuration.
        """
        len_list = self.k**(2*self.r + 1)
        rule = decimal_to_base_k(self.rule, self.k)
        self.rule_set = [0] * (len_list - len(rule)) + rule

    def check_rule(self, inp):
        """Returns the new state based on the input states.

        The input state will be an array of 2r+1 items between 0 and k, the
        neighbourhood which the state of the new cell depends on."""
        return self.rule_set[-base_k_to_decimal(inp, self.k) - 1]

    def setup_initial_row(self):
        """Returns an array of length `width' with the initial state for each of
        the cells in the first row in accordance with c_dens. Values should be
        between 0 and k."""
        n_car = round(self.width * self.c_dens)
        state = [0] * self.width
        c_ind = random.sample(range(self.width), int(n_car))
        for i in c_ind:
            state[i] = 1
        return state

    def reset(self):
        """Initializes the configuration of the cells and converts the entered
        rule number to a rule set."""

        self.t = 0
        self.flow = 0
        self.rule_length = self.k**(2*self.r + 1)
        self.config = np.zeros([self.height, self.width])
        self.config[0, :] = self.setup_initial_row()
        self.build_rule_set()

    def draw(self, title, path):
        """Draws the current state of the grid."""
        import matplotlib
        import matplotlib.pyplot as plt

        plt.cla()
        if not plt.gca().yaxis_inverted():
            plt.gca().invert_yaxis()
        plt.imshow(self.config, interpolation='none', vmin=0, vmax=self.k - 1,
                cmap=matplotlib.cm.binary)
        plt.axis('image')
        plt.title(title)
        plt.savefig(path)

    def step(self):
        """Performs a single step of the simulation by advancing time (and thus
        row) and applying the rule to determine the state of the cells."""
        self.t += 1
        if self.t >= self.height:
            return True

        for patch in range(self.width):
            # We want the items r to the left and to the right of this patch,
            # while wrapping around (e.g. index -1 is the last item on the row).
            # Since slices do not support this, we create an array with the
            # indices we want and use that to index our grid.
            indices = [i % self.width
                    for i in range(patch - self.r, patch + self.r + 1)]
            values = self.config[self.t - 1, indices]
            self.config[self.t, patch] = self.check_rule(values)

        self.check_flow()

    def check_flow(self):
        """Checks if a car has passed the right edge"""
        if self.config[self.t - 1][-1] == 1 and self.config[self.t][-1] == 0:
            self.flow += 1
