import ast
import copy
import csv


class Move:
    moveType = None     # i.e spin, extension, delay etc
    index = None        # stores the index on which the move acts on
    # (in this case the side of the machine or the index of the LED)
    position = None     # which position to goto (angle or retraction since I've reused the variable)
    delay = None        # only used for delay moves
    colour = None       # colour set to LEDs during the move

    # Validation dicts. Used to check weather the data that is given is valid
    _validMoves = {'spin', 'extension', 'delay', 'stop', 'led', 'photo'}
    _validSides = {'b': 0, 'r': 1, 'f': 2, 'l': 3, 0: 0, 1: 1, 2: 2, 3: 3}
    _validAngles = {0, 1, 2, 3}
    _validPositions = {'f', 'e', 'r'}

    def __init__(self, _move_type, _index=None, _position=None, _colour=None, _delay=0.0):
        if _move_type in self._validMoves:
            self.moveType = _move_type
        else:
            raise ValueError("Invalid Move Type")

        if _move_type == 'spin':
            try:
                self.index = self._validSides.get(_index)
            except KeyError:
                raise ValueError("Invalid Side Num")

            if int(_position) in range(4):
                self.position = int(_position)
            else:
                raise ValueError("Invalid position num")

        elif _move_type == 'extension':
            try:
                self.index = self._validSides.get(_index)
            except KeyError:
                raise ValueError("Invalid Side Num")
            if _position in self._validPositions:
                self.position = _position
            else:
                raise ValueError("Position must be either f, e or r")

        elif _move_type == 'delay':
            self.delay = _delay
        elif _move_type == 'stop':  # stops the servos 'pushing' when they are not being activated anymore
            self.index = _index
        elif _move_type == 'led':
            if _index in range(-1, 8):
                self.index = _index  # in this case, index is the index of the LED
            else:
                raise ValueError("Invalid LED Index")
            # todo error checking
            self.colour = _colour

    def execute(self, _machine):
        if self.moveType == 'spin':
            _machine.goto(self.index, self.position)

        elif self.moveType == 'extension':
            if self.position == 'e':
                _machine.extend(self.index)
            elif self.position == 'r':
                _machine.retract(self.index)
            elif self.position == 'f':
                _machine.full_extend(self.index)

        elif self.moveType == 'delay':
            return {'type': 'delay', 'content': self.delay}

        elif self.moveType == 'stop':
            # todo check if this works
            _machine.set_angle(self.index, None)
            _machine.set_angle(self.index + 4, None)

        elif self.moveType == 'led':
            _machine.set_leds(self.index, self.colour)

        elif self.moveType == 'photo':
            _machine.take_photo()
        return {'type': 'none'}


class MoveList:
    moves: list[Move] = []
    moves2 = None
    counter = 1
    machine = None
    waitSeconds = 0
    _running = False

    def __init__(self, _machine, _moves: list[Move] = None, csv_path=None):
        self.machine = _machine
        if _moves is None:
            self.read_from_csv(csv_path)
        else:
            self.moves = _moves

    def read_from_csv(self, csv_path):
        with open(csv_path, newline='') as csvfile:
            csv_reader = csv.DictReader(csvfile, delimiter=',', quotechar='|')
            for row in csv_reader:
                row_d = dict(row)
                # bit of a mess here to ensure all values are cast correctly
                move_type = row_d['move_type']
                if not move_type:
                    raise ValueError("move_type passed empty")
                index = None
                if row_d['index']:
                    index = int(row_d['index'])
                position = None
                if row_d['position']:
                    try:
                        position = int(row_d['position'])
                    except ValueError:
                        position = row_d['position']
                colour = None
                if row_d['colour'] is not None:
                    colour = row_d['colour']
                delay = 0
                if row_d['delay']:
                    delay = float(row_d['delay'])

                self.moves.append(Move(
                    move_type, index, position, colour, delay)
                )

    def add_move(self, _move_type, _index=None, _position=None, _colour=None, _delay=0.0):
        self.moves.append(Move(_move_type, _index, _position, _colour, _delay))

    def send_list_request(self):
        self.machine.execute_move_list(self.moves)

    def execute(self):
        # dynamically executes the moves each time tick() is called
        self.moves2 = copy.copy(self.moves)
        self._running = True

    def tick(self):
        if self._running:
            self.waitSeconds -= .05
            if self.waitSeconds <= 0:
                while True:
                    move_delay = 0
                    try:
                        move_response = self.moves2[0].execute(self.machine)
                    except IndexError:
                        self._running = False
                        self.counter -= 1
                        if self.counter != 0:
                            self.execute()
                        break
                    self.moves2.pop(0)
                    if move_response['type'] == 'delay':
                        move_delay = move_response['value']
                        self.waitSeconds = move_delay
                    elif move_response['type'] == 'camera':
                        # hand down the response to be further handled
                        return move_response
