''' Base Filter class and associated functionality. '''

from abc import ABCMeta, abstractmethod
import logging

from pliers.stimuli import AudioStim, VideoStim
from pliers.transformers import Transformer
from pliers.utils import listify


class Filter(Transformer, metaclass=ABCMeta):
    ''' Base class for Filters.'''

    def _transform(self, stim, *args, **kwargs):
        new_stim = self._filter(stim, *args, **kwargs)
        if not isinstance(new_stim, self._input_type) and \
           not isinstance(listify(new_stim)[0], stim.__class__):
            raise ValueError("Filter must return a Stim of the same type as "
                             "its input.")
        return new_stim

    @abstractmethod
    def _filter(self, stim):
        pass


class TemporalTrimmingFilter(Filter):
    ''' Temporally trims the contents of the audio stimulus using the provided
    start and end points.

    Args:
        start (float): New start point for the trimmed video in seconds.
        end (float): New end point for the trimmed video in seconds.
        frames (bool): If True, treat the provided start and end values as
            frame indices, otherwise, treat them as time in terms of seconds.
            If True, start and end must both be integers.
        validation (str): String specifying how OOB errors (end > duration)
            should be handled. Must be one of:
                - 'strict': Raise an exception on an OOB error
                - 'warn': Issue a warning for all OOB errors and use the
                maximum bounds
    '''

    _log_attributes = ('start', 'end', 'frames', 'validation')
    _input_type = ()
    _optional_input_type = (AudioStim, VideoStim)

    def __init__(self, start=0, end=None, frames=False, validation='warn'):
        self.start = start
        self.end = end
        self.frames = frames
        self.validation = validation
        super().__init__()

    def _filter(self, stim):
        rate = 'fps' if isinstance(stim, VideoStim) else 'sampling_rate'
        start = self.start / getattr(stim, rate) if self.frames else self.start
        end = self.end / getattr(stim, rate) if self.frames else self.end
        if end and end > stim.duration:
            if self.validation == 'warn':
                logging.warning("Attempted to trim beyond the duration of the"
                             "clip, instead trimming to the end of the clip")
                end = stim.duration
            else:
                raise ValueError("Invalid end argument passed: Attempted to"
                                 "trim beyond the duration of the clip")
        subclip = stim.clip.subclip(start, end)
        return type(stim)(onset=stim.onset, filename=stim.filename, clip=subclip)
