# -*- coding: utf-8 -*-
'''
This module defines :class:`Segment`, a container for data sharing a common
time basis.

:class:`Segment` derives from :class:`Container`,
from :module:`neo.core.container`.
'''

# needed for python 3 compatibility
from __future__ import absolute_import, division, print_function

from datetime import datetime

import numpy as np

from neo.core.container import Container


class Segment(Container):
    '''
    A container for data sharing a common time basis.

    A :class:`Segment` is a heterogeneous container for discrete or continous
    data sharing a common clock (time basis) but not necessary the same
    sampling rate, start or end time.

    *Usage*::
        >>> from neo.core import Segment, SpikeTrain, AnalogSignal
        >>> from quantities import Hz, s
        >>>
        >>> seg = Segment(index=5)
        >>>
        >>> train0 = SpikeTrain(times=[.01, 3.3, 9.3], units='sec', t_stop=10)
        >>> seg.spiketrains.append(train0)
        >>>
        >>> train1 = SpikeTrain(times=[100.01, 103.3, 109.3], units='sec',
        ...                     t_stop=110)
        >>> seg.spiketrains.append(train1)
        >>>
        >>> sig0 = AnalogSignal(signal=[.01, 3.3, 9.3], units='uV',
        ...                     sampling_rate=1*Hz)
        >>> seg.analogsignals.append(sig0)
        >>>
        >>> sig1 = AnalogSignal(signal=[100.01, 103.3, 109.3], units='nA',
        ...                     sampling_period=.1*s)
        >>> seg.analogsignals.append(sig1)

    *Required attributes/properties*:
        None

    *Recommended attributes/properties*:
        :name: (str) A label for the dataset.
        :description: (str) Text description.
        :file_origin: (str) Filesystem path or URL of the original data file.
        :file_datetime: (datetime) The creation date and time of the original
            data file.
        :rec_datetime: (datetime) The date and time of the original recording
        :index: (int) You can use this to define a temporal ordering of
            your Segment. For instance you could use this for trial numbers.

    Note: Any other additional arguments are assumed to be user-specific
    metadata and stored in :attr:`annotations`.

    *Properties available on this object*:
        :all_data: (list) A list of all child objects in the :class:`Segment`.

    *Container of*:
        :class:`Epoch`
        :class:`Event`
        :class:`AnalogSignal`
        :class:`IrregularlySampledSignal`
        :class:`SpikeTrain`

    '''

    _data_child_objects = ('AnalogSignal',
                           'Epoch', 'Event',
                           'IrregularlySampledSignal', 'SpikeTrain')
    _single_parent_objects = ('Block',)
    _recommended_attrs = ((('file_datetime', datetime),
                           ('rec_datetime', datetime),
                           ('index', int)) +
                          Container._recommended_attrs)
    _repr_pretty_containers = ('analogsignals',)

    def __init__(self, name=None, description=None, file_origin=None,
                 file_datetime=None, rec_datetime=None, index=None,
                 **annotations):
        '''
        Initialize a new :class:`Segment` instance.
        '''
        super(Segment, self).__init__(name=name, description=description,
                                      file_origin=file_origin, **annotations)

        self.file_datetime = file_datetime
        self.rec_datetime = rec_datetime
        self.index = index

    # t_start attribute is handled as a property so type checking can be done
    @property
    def t_start(self):
        '''
        Time when first signal begins.
        '''
        t_starts = [sig.t_start for sig in self.analogsignals + self.spiketrains + self.irregularlysampledsignals]
        t_starts += [e.times[0] for e in self.epochs + self.events if len(e.times) > 0]

        # t_start is not defined if no children are present
        if len(t_starts)==0:
            return None

        t_start = min(t_starts)
        return t_start

    # t_stop attribute is handled as a property so type checking can be done
    @property
    def t_stop(self):
        '''
        Time when last signal ends.
        '''
        t_stops = [sig.t_stop for sig in self.analogsignals +  self.spiketrains + self.irregularlysampledsignals]
        t_stops += [e.times[-1] for e in self.epochs + self.events if len(e.times) > 0]

        # t_stop is not defined if no children are present
        if len(t_stops)==0:
            return None

        t_stop = max(t_stops)
        return t_stop

    def take_spiketrains_by_unit(self, unit_list=None):
        '''
        Return :class:`SpikeTrains` in the :class:`Segment` that are also in a
        :class:`Unit` in the :attr:`unit_list` provided.
        '''
        if unit_list is None:
            return []
        spiketrain_list = []
        for spiketrain in self.spiketrains:
            if spiketrain.unit in unit_list:
                spiketrain_list.append(spiketrain)
        return spiketrain_list

    # def take_analogsignal_by_unit(self, unit_list=None):
    #     '''
    #     Return :class:`AnalogSignal` objects in the :class:`Segment` that are
    #     have the same :attr:`channel_index` as any of the :class:`Unit: objects
    #     in the :attr:`unit_list` provided.
    #     '''
    #     if unit_list is None:
    #         return []
    #     channel_indexes = []
    #     for unit in unit_list:
    #         if unit.channel_indexes is not None:
    #             channel_indexes.extend(unit.channel_indexes)
    #     return self.take_analogsignal_by_channelindex(channel_indexes)
    #
    # def take_analogsignal_by_channelindex(self, channel_indexes=None):
    #     '''
    #     Return :class:`AnalogSignal` objects in the :class:`Segment` that have
    #     a :attr:`channel_index` that is in the :attr:`channel_indexes`
    #     provided.
    #     '''
    #     if channel_indexes is None:
    #         return []
    #     anasig_list = []
    #     for anasig in self.analogsignals:
    #         if anasig.channel_index in channel_indexes:
    #             anasig_list.append(anasig)
    #     return anasig_list

    def take_slice_of_analogsignalarray_by_unit(self, unit_list=None):
        '''
        Return slices of the :class:`AnalogSignal` objects in the
        :class:`Segment` that correspond to a :attr:`channel_index`  of any of
        the :class:`Unit` objects in the :attr:`unit_list` provided.
        '''
        if unit_list is None:
            return []
        indexes = []
        for unit in unit_list:
            if unit.get_channel_indexes() is not None:
                indexes.extend(unit.get_channel_indexes())

        return self.take_slice_of_analogsignalarray_by_channelindex(indexes)

    def take_slice_of_analogsignalarray_by_channelindex(self,
                                                        channel_indexes=None):
        '''
        Return slices of the :class:`AnalogSignalArrays` in the
        :class:`Segment` that correspond to the :attr:`channel_indexes`
        provided.
        '''
        if channel_indexes is None:
            return []

        sliced_sigarrays = []
        for sigarr in self.analogsignals:
            if sigarr.get_channel_index() is not None:
                ind = np.in1d(sigarr.get_channel_index(), channel_indexes)
                sliced_sigarrays.append(sigarr[:, ind])

        return sliced_sigarrays

    def construct_subsegment_by_unit(self, unit_list=None):
        '''
        Return a new :class:`Segment that contains the :class:`AnalogSignal`,
        :class:`AnalogSignal`, and :class:`SpikeTrain`
        objects common to both the current :class:`Segment` and any
        :class:`Unit` in the :attr:`unit_list` provided.

        *Example*::

            >>> from neo.core import (Segment, Block, Unit, SpikeTrain,
            ...                       ChannelIndex)
            >>>
            >>> blk = Block()
            >>> chx = ChannelIndex(name='group0')
            >>> blk.channel_indexes = [chx]
            >>>
            >>> for ind in range(5):
            ...         unit = Unit(name='Unit #%s' % ind, channel_index=ind)
            ...         chx.units.append(unit)
            ...
            >>>
            >>> for ind in range(3):
            ...     seg = Segment(name='Simulation #%s' % ind)
            ...     blk.segments.append(seg)
            ...     for unit in chx.units:
            ...         train = SpikeTrain([1, 2, 3], units='ms', t_start=0.,
            ...                            t_stop=10)
            ...         train.unit = unit
            ...         unit.spiketrains.append(train)
            ...         seg.spiketrains.append(train)
            ...
            >>>
            >>> seg0 = blk.segments[-1]
            >>> seg1 = seg0.construct_subsegment_by_unit(chx.units[:2])
            >>> len(seg0.spiketrains)
            5
            >>> len(seg1.spiketrains)
            2

        '''
        seg = Segment()
        seg.spiketrains = self.take_spiketrains_by_unit(unit_list)
        seg.analogsignals = \
            self.take_slice_of_analogsignalarray_by_unit(unit_list)
        #TODO copy others attributes
        return seg
