import logging

from .known import vlr_factory
from .rawvlr import RawVLR

logger = logging.getLogger(__name__)


class RawVLRList:
    def __init__(self, iterable=None):
        if iterable is not None:
            self.vlrs = list(iterable)
        else:
            self.vlrs = []

    def append(self, raw_vlr):
        self.vlrs.append(raw_vlr)

    def __len__(self):
        return len(self.vlrs)

    def total_size_in_bytes(self):
        return sum(v.size_in_bytes() for v in self.vlrs)

    def write_to(self, out_stream):
        for vlr in self.vlrs:
            vlr.write_to(out_stream)

    @classmethod
    def from_list(cls, vlrs):
        raw_vlrs = cls()
        for vlr in vlrs:
            raw = RawVLR()
            raw.header.user_id = vlr.user_id.encode("utf8")
            raw.header.description = vlr.description.encode("utf8")
            raw.header.record_id = vlr.record_id
            raw.record_data = vlr.record_data_bytes()
            raw_vlrs.append(raw)
        return raw_vlrs


class VLRList:
    """ Class responsible for managing the vlrs
    """

    def __init__(self):
        self.vlrs = []

    def append(self, vlr):
        """ append a vlr to the list

        Parameters
        ----------
        vlr: RawVlR | KnownVlr

        Returns
        -------

        """
        self.vlrs.append(vlr)

    def extend(self, vlr_list):
        """ append all elements of the vlr_list into self
        """
        self.vlrs.extend(vlr_list)

    def get_by_id(self, user_id="", record_ids=(None,)):
        """ Function to get vlrs by user_id and/or record_ids

        Parameters
        ----------
        user_id: str, optional
            the user id
        record_ids: Iterable if int, optional
            THe record ids of the vlr(s) you wish to get

        Returns
        -------
        a List of vlrs matching the user_id and records_ids

        """
        if user_id != "" and record_ids != (None,):
            return [
                vlr
                for vlr in self.vlrs
                if vlr.user_id == user_id and vlr.record_id in record_ids
            ]
        else:
            return [
                vlr
                for vlr in self.vlrs
                if vlr.user_id == user_id or vlr.record_id in record_ids
            ]

    def get(self, vlr_type):
        """ Returns the list of vlrs of the requested type

        Parameters
        ----------
        vlr_type: str, the class name of the vlr

        Returns
        -------
        :py:class:`list`
            a List of vlrs matching the user_id and records_ids

        """
        return [v for v in self.vlrs if v.__class__.__name__ == vlr_type]

    def extract(self, vlr_type):
        """ Returns the list of vlrs of the requested type
        The difference with get is that the returned vlrs will be removed from the list

        Parameters
        ----------
        vlr_type: str, the class name of the vlr

        Returns
        -------
        a List of vlrs matching the user_id and records_ids

        """
        kept_vlrs, extracted_vlrs = [], []
        for vlr in self.vlrs:
            if vlr.__class__.__name__ == vlr_type:
                extracted_vlrs.append(vlr)
            else:
                kept_vlrs.append(vlr)
        self.vlrs = kept_vlrs
        return extracted_vlrs

    def pop(self, index):
        return self.vlrs.pop(index)

    def index(self, vlr_type):
        for i, v in enumerate(self.vlrs):
            if v.__class__.__name__ == vlr_type:
                return i
        else:
            raise ValueError("{} is not in the VLR list".format(vlr_type))

    def __iter__(self):
        yield from iter(self.vlrs)

    def __getitem__(self, item):
        return self.vlrs[item]

    def __len__(self):
        return len(self.vlrs)

    def __eq__(self, other):
        if isinstance(other, list):
            return self.vlrs == other

    def __repr__(self):
        return "[{}]".format(", ".join(repr(vlr) for vlr in self.vlrs))

    @classmethod
    def read_from(cls, data_stream, num_to_read):
        """ Reads vlrs and parse them if possible from the stream

        Parameters
        ----------
        data_stream : stream to read from
        num_to_read : number of vlrs to be read

        Returns
        -------
        pylas.vlrs.vlrlist.VLRList
            List of vlrs

        """
        vlrlist = cls()
        for _ in range(num_to_read):
            raw = RawVLR.read_from(data_stream)
            try:
                vlrlist.append(vlr_factory(raw))
            except UnicodeDecodeError:
                logger.error("Failed to decode VLR: {}".format(raw))

        return vlrlist

    @classmethod
    def from_list(cls, vlr_list):
        vlrs = cls()
        vlrs.vlrs = vlr_list
        return vlrs
