from yarnover.exceptions import KnittingException
from typing import List, Optional, Set

from logging import getLogger

logger = getLogger(__name__)

Stitch = int

Marker = str

class Needle:
    def __init__(self, initial_stitches : List[Stitch|Marker] = list()) -> None:
        self.is_circular = False
        self.items : List[Stitch|Marker] = list()
        if initial_stitches:
            self.items.extend(initial_stitches)

    def __repr__(self):
        return f"<Needle ({len(self.items)})>"
    
    def push(self, stitch):
        self.items.append(stitch)

    def extend(self, items):
        for item in items:
            self.push(item)

    def _pop_next_item(self):
        if self.is_empty:
            raise KnittingException("No items left to pop")
        return self.items.pop()

    def pop(self, count : int = 1) -> List[Stitch|Marker]:
        items = list()
        for _ in range(count):
            item = self._pop_next_item()
            items.append(item)
        return items

    def get_next_stitch(self) -> Stitch:
        for i in range(len(self.items)):
            item = self.peek(i)
            if isinstance(item, Stitch):
                return item
        else:
            raise KnittingException("No stitches left on needle")
            

    def peek(self, position : int = 0):
        return self.items[-(position+1)]

    @property
    def is_empty(self):
        return len(self.items) == 0

    def __iter__(self):
        return reversed(self.items)

    def distance_to_marker(self) -> int:
        """
        Returns the number of stitches left before the next marker.

        :param stitches_left: The number of stitches that must be left before the marker.
        """
        for n, item in enumerate(self):
            if isinstance(item, Marker):
                return n
        else:
            raise KnittingException("No markers left found")

    def distance_to_end(self):
        for _, item in enumerate(self):
            if isinstance(item, Marker):
                raise KnittingException("Marker found before end")
        return len(self.items)

    def distance_to_marker_or_end(self):
        distance = 0
        for item in iter(self):
            if isinstance(item, Marker):
                return distance
            else:
                distance += 1
        return distance
    

    @property
    def stitch_count(self):
        count = 0
        for item in self.items:
            if isinstance(item, Stitch):
                count += 1
        return count

    def measure(self, from_marker, to_marker):
        state = 0
        count = 0
        if from_marker is None:
            state = 1
        for item in self:
            if state == 0 and isinstance(item, Marker):
                assert item != to_marker
                if item == from_marker:
                    state = 1
            elif state == 1:
                if isinstance(item, Stitch):
                    count += 1
                elif isinstance(item, Marker):
                    assert item == to_marker
                    state = 2
        if to_marker is not None:
            assert state == 2
        return count

    def summarize(self):
        summary = [0]
        for item in self:
            if isinstance(item, Stitch):
                summary[-1] += 1
            elif isinstance(item, Marker):
                summary.append(0)
        return summary


class BackNeedle(Needle):
    def __init__(self, needle : Needle) -> None:
        self.is_circular = True
        needle.is_circular = True
        self.items : List[Stitch|Marker] = needle.items
        
    def push(self, stitch):
        self.items.insert(0, stitch)

    def _pop_next_item(self):
        return self.items.pop(0)

    def peek(self, position : int = 0):
        return self.items[position]

    def __iter__(self):
        return iter(self.items)
