class TuringMachine:
    def __init__(self,
                 nb_of_states=config.NB_OF_STATES,
                 nb_of_symbols=config.NB_OF_SYMBOLS,
                 init_state=0,
                 init_tape_length=config.TOTAL_ENCODING_LENGTH):

        self.nb_of_states = nb_of_states
        self.nb_of_symbols = nb_of_symbols

        self.init_state = init_state
        self.current_state = init_state
        self.tape = Tape(nb_of_symbols, init_tape_length)

        # [old_state][symbon_read] => ( symbol_to_write, movement, new_state )
        #self.action_table = [[ ( 0, '', 0 ) ] * nb_of_symbols ] * nb_of_states # Problem = works by reference
        self.action_table = []
        for old_state in range(nb_of_states):
            self.action_table.append([])
            for symbol_read in range(nb_of_symbols):
                self.action_table[old_state].append((0, '', 0))

    def __del__(self):

        del self.nb_of_states
        del self.nb_of_symbols
        del self.init_state
        del self.current_state
        del self.action_table
        del self.tape

    def next(self):

        symbol_read = self.tape.read()
        try:
            (symbol_to_write, movement,
             new_state) = self.action_table[self.current_state][symbol_read]
        except IndexError:
            print self.current_state, symbol_read
            self.debugConfiguration()
            self.debugState()

        # Write the new symbol
        self.tape.write(symbol_to_write)

        # Move the tape cursor
        if movement == 'l':
            self.tape.moveLeft()
        elif movement == 'r':
            self.tape.moveRight()

        # Set the new state
        self.current_state = new_state

    def reset(self):

        self.current_state = self.init_state
        self.tape.reset()

    def linearize(self):  # Used for testing purposes

        for old_state in range(self.nb_of_states):
            for symbol_read in range(self.nb_of_symbols):

                symbol_to_write = (symbol_read + 1) % self.nb_of_symbols
                movement = 'r'
                new_state = (old_state + 1) % self.nb_of_states

                action = (symbol_to_write, movement, new_state)
                self.action_table[old_state][symbol_read] = action

        self.tape.linearize()

        return self

    def randomize(self):

        for old_state in range(self.nb_of_states):
            for symbol_read in range(self.nb_of_symbols):
                self.randomizeRow(old_state, symbol_read)

        return self

    def randomizeRow(self, old_state, symbol_read):

        action = self.createRandomAction()
        self.action_table[old_state][symbol_read] = action

        return action

    def createRandomAction(self):

        symbol_to_write = random.randint(0, self.nb_of_symbols - 1)
        movement = random.choice(['l', 'r'])
        new_state = random.randint(0, self.nb_of_states - 1)
        action = (symbol_to_write, movement, new_state)

        return action

    def mutate(self):
        old_state = random.randint(0, self.nb_of_states - 1)
        symbol_read = random.randint(0, self.nb_of_symbols - 1)
        old_action = self.action_table[old_state][symbol_read]
        new_action = self.randomizeRow(old_state, symbol_read)

        #print "Mutation: ", (old_state, symbol_read), "=>", old_action, " is now ", new_action
        return self

    def addState(self):

        self.nb_of_states += 1
        new_state = []
        for i in range(self.nb_of_symbols):
            action = self.createRandomAction()
            new_state.append(action)
        self.action_table.append(new_state)

        return self

    def removeState(self):  # Remove the last one

        if self.nb_of_states == 1:  # Do nothing
            return self

        state_to_remove = self.nb_of_states - 1
        del self.action_table[state_to_remove]
        self.nb_of_states -= 1

        if self.current_state == state_to_remove:
            self.current_state -= 1

        # Reindex the action table
        for old_state in range(self.nb_of_states):
            for symbol_read in range(self.nb_of_symbols):
                (symbol_to_write, movement,
                 new_state) = self.action_table[old_state][symbol_read]
                if new_state == state_to_remove:
                    new_state -= 1
                self.action_table[old_state][symbol_read] = (symbol_to_write,
                                                             movement,
                                                             new_state)

        return self

    def mate1(self, turing_machine):

        if self.nb_of_states != self.nb_of_states:
            raise ValueError('Mate: different number of states')
        if self.nb_of_symbols != self.nb_of_symbols:
            raise ValueError('Mate: different number of symbols')

        cut_state = random.randint(0, self.nb_of_states - 1)
        print "Cut state: ", cut_state

        action_table_1 = self.action_table[:
                                           cut_state] + turing_machine.action_table[
                                               cut_state:]
        action_table_2 = turing_machine.action_table[:
                                                     cut_state] + self.action_table[
                                                         cut_state:]

        self.action_table = action_table_1
        turing_machine.action_table = action_table_2

        return self, turing_machine

    def mate(self, turing_machine):

        # Copy the largest action table of the two parents
        if len(self.action_table) > len(turing_machine.action_table):
            child = copy.deepcopy(self)
            parent_2 = turing_machine
        else:
            child = copy.deepcopy(turing_machine)
            parent_2 = self

        for old_state in range(parent_2.nb_of_states):
            for symbol_read in range(parent_2.nb_of_symbols):
                if random.random() > config.MATE_PB:
                    new_action = parent_2.action_table[old_state][symbol_read]
                    child.action_table[old_state][symbol_read] = new_action

        child.reset()
        return child

    def debugConfiguration(self):

        print "Init state: ", self.init_state
        print "Number of states: ", len(self.action_table)
        print "Number of symbols: ", len(self.action_table[0])
        print "Action table: "
        for old_state, action_state in enumerate(self.action_table):
            for symbol_read, action in enumerate(action_state):
                print(old_state, symbol_read), "=>", action
        #self.tape.debugConfiguration()

    def debugState(self):

        symbol_read = self.tape.read()
        action = self.action_table[self.current_state][symbol_read]

        print "Current action:", (self.current_state,
                                  symbol_read), "=>", action