"""
Object classes for recording and plotting time-series data.

This module defines a set of DataRecorder object types for recording
time-series data, a set of Trace object types for
specifying ways of generating 1D-vs-time traces from recorded data,
and a TraceGroup object that will plot a set of traces on stacked,
aligned axes.
"""

import os
import bisect
from itertools import izip

from numpy import asarray
import ImageDraw

import param
from param import normalize_path

from topo.base.simulation import EventProcessor
from topo.plotting.bitmap import RGBBitmap, MontageBitmap, TITLE_FONT
from topo.misc.util import Struct



class DataRecorder(EventProcessor):
    """
    Record time-series data from a simulation.

    A DataRecorder instance stores a set of named time-series
    variables, consisting of a sequence of recorded data items of any
    type, along with the times at which they were recorded.

    DataRecorder is an abstract class for which different
    implementations may exist for different means of storing recorded
    data.  For example, the subclass InMemoryRecorder stores all the
    data in memory.

    A DataRecorder instance can operate either as an event processor, or in a
    stand-alone mode.  Both usage modes can be used on the same
    instance in the same simulation.

    STAND-ALONE USAGE:

    A DataRecorder instance is used as follows:

      - Method .add_variable adds a named time series variable.
      - Method .record_data records a new data item and timestamp.
      - Method .get_data gets a time-delimited sequence of data from a variable

    EVENTPROCESSOR USAGE:

    A DataRecorder can also be connected to a simulation as an event
    processor, forming a kind of virtual recording equipment.  An
    output port from any event processor in a simulation can be
    connected to a DataRecorder; the recorder will automaticall create
    a variable with the same name as the connection, and record any
    incoming data on that variable with the time it was received.  For
    example::

      topo.sim['Recorder'] =  InMemoryRecorder()
      topo.sim.connect('V1','Recorder',name='V1 Activity')

    This script snippet will create a new DataRecorder and
    automatically record all activity sent from the sheet 'V1'.
    """

    __abstract = True


    def __init__(self,**params):
        super(DataRecorder,self).__init__(**params)
        self._trace_groups = {}


    def _src_connect(self,conn):
        raise NotImplementedError


    def _dest_connect(self,conn):
        super(DataRecorder,self)._dest_connect(conn)
        self.add_variable(conn.name)


    def input_event(self,conn,data):
        self.record_data(conn.name,self.simulation.time(),data)


    def add_variable(self,name):
        """
        Create a new time-series variable with the given name.
        """
        raise NotImplementedError


    def record_data(self,varname,time,data):
        """
        Record the given data item with the given timestamp in the
        named timeseries.
        """
        raise NotImplementedError


    def get_data(self,varname,times=(None,None),fill_range=False):
        """
        Get the named timeseries between the given times
        (inclusive). If fill_range is true, the returned data will
        have timepoints exactly at the start and end of
        the given timerange.  The data values at these timepoints will
        be those of the next-earlier datapoint in the series.

        (NOTE: fill_range can fail to create a beginning timepoint if
        the start of the time range is earlier than the first recorded datapoint.]
        """
        raise NotImplementedError


    def get_times(self,var):
        """
        Get all the timestamps for a given variable.
        """
        raise NotImplementedError


    def get_time_indices(self,varname,start_time,end_time):
        """
        For the named variable, get the start and end indices suitable
        for slicing the data to include all times t::

          start_time <= t <= end_time.

        A start_ or end_time of None is interpreted to mean the
        earliest or latest available time, respectively.
        """

        times = self.get_times(varname)
        if start_time is None:
            start = 0
        else:
            start = bisect.bisect_left(times,start_time)
            if start >= len(times):
                start = len(times)-1
            elif times[start] > start_time:
                start -= 1

        if end_time is None:
            end = None
        else:
            end = bisect.bisect_right(times,end_time)

        return start,end




class InMemoryRecorder(DataRecorder):
    """
    A data recorder that stores all recorded data in memory.
    """