#ifndef __ANTARESDAQ__TIMESLICE__
#define __ANTARESDAQ__TIMESLICE__

#include <ostream>
#include <iomanip>
#include <vector>
#include <algorithm>

#include <TROOT.h>
#include <TObject.h>

#include "antares-dataformat/DataTypes.hh"
#include "antares-dataformat/Ars.hh"
#include "antares-dataformat/FramePreamble.hh"
#include "antares-dataformat/EventPreamble.hh"


/**
 * Summary of Frame
 */
class Summary_Frame {
protected:
  /** LCM identifier */
  unsigned short lcm_id_;
  /** ARS identifier */
  unsigned char  ars_id_;
  /** data type */
  unsigned char  data_type_;
  /** number of items */
  unsigned short numberOfItems_;

public:
  /**
   * item type definition
   */
  typedef void item_type;

  /**
   * get LCM idendifier
   * \return       LCM identifier
   */
  const unsigned short lcm_id() const { return lcm_id_; }

  /**
   * get ARS idendifier
   * \return       ARS identifier
   */
  const unsigned char ars_id() const { return ars_id_; }

  /**
   * get data type
   * \return       data type
   */
  const unsigned char data_type() const { return data_type_; }

  /**
   * get number of items
   * \return       number of items
   */
  const unsigned short numberOfItems() const { return numberOfItems_; }

  /**
   * Default constructor.
   */
  Summary_Frame() :
    lcm_id_(0),
    ars_id_(0),
    data_type_(0),
    numberOfItems_(0)
  {}

  /**
   * Virtual destructor.
   */
  virtual ~Summary_Frame() {}
  
  /**
   * equal operator.
   *
   * \param  object   summary frame
   * \return          true if equals object; else false
   */
  const bool operator==(const Summary_Frame& object)
  {
    return (lcm_id_    == object.lcm_id_   &&
	    ars_id_    == object.ars_id_   &&
	    data_type_ == object.data_type_);
  }
  
  /**
   * not-equal operator.
   *
   * \param  object   summary frame
   * \return          true if not equals object; else false
   */
  const bool operator!=(const Summary_Frame& object) const
  {
    return (lcm_id_    != object.lcm_id_   ||
	    ars_id_    != object.ars_id_   ||
	    data_type_ != object.data_type_);
  }

  /**
   * less than operator.
   *
   * \param  object   summary frame
   * \return          true if less than object; else false
   */
  const bool operator<(const Summary_Frame& object) const
  {
    if (lcm_id_ == object.lcm_id_)
      if (ars_id_ == object.ars_id_)
	return data_type_ < object.data_type_;
      else
	return ars_id_ < object.ars_id_;
    else
      return lcm_id_ < object.lcm_id_;
  }

  /**
   * Print ASCII.
   *
   * \param  out     output stream
   * \param  object  Summary frame
   * \return         output stream
   */
  friend std::ostream& operator<<(std::ostream& out, const Summary_Frame& object)
  {
    using namespace std;
    
    return out << setw(5) << (int) object.lcm_id_        << ' ' 
	       << setw(2) << (int) object.ars_id_        << ' ' 
	       << setw(2) << (int) object.data_type_     << ' ' 
	       << setw(5) << (int) object.numberOfItems_ << endl;
  }

  /**
   * operator +=
   *
   * \param  object   summar frame
   * \return          this summay frame
   */
  Summary_Frame& operator+=(const Summary_Frame& object)
  {
    if (*this == object) {
      numberOfItems_ += object.numberOfItems_;
    }
    
    return *this;
  }

  /** ROOT class definition */
  ClassDef(Summary_Frame, 2);
};


class ExtendedSummary_Frame :
  public Summary_Frame
{
protected:
  /** number of items original */
  unsigned short numberOfItemsOrg_;

public:
  /**
   * item type definition
   */
  typedef void item_type;

  /**
   * get number of items original
   * \return       number of items
   */
  const unsigned short numberOfItemsOrg() const { return numberOfItemsOrg_; }

  /**
   * Default constructor.
   */
  ExtendedSummary_Frame() :
    Summary_Frame(),
    numberOfItemsOrg_(0)
  {}

  /**
   * Print ASCII.
   *
   * \param  out     output stream
   * \param  object  Extended Summary frame
   * \return         output stream
   */
  friend std::ostream& operator<<(std::ostream& out, const ExtendedSummary_Frame& object)
  {
    using namespace std;
    
    return out << setw(5) << (int) object.lcm_id_           << ' ' 
	       << setw(2) << (int) object.ars_id_           << ' ' 
	       << setw(2) << (int) object.data_type_        << ' ' 
	       << setw(5) << (int) object.numberOfItems_    << ' '
	       << setw(5) << (int) object.numberOfItemsOrg_ << endl;
  }

  /**
   * operator +=
   *
   * \param  object   extended summary frame
   * \return          this extended summary frame
   */
  ExtendedSummary_Frame& operator+=(const ExtendedSummary_Frame& object)
  {
    if (*this == object) {
      numberOfItems_    += object.numberOfItems_;
      numberOfItemsOrg_ += object.numberOfItemsOrg_;
    }
    return *this;
  }

  /** ROOT class definition */
  ClassDef(ExtendedSummary_Frame, 2);
};


/**
 * Template Frame for ARS data
 */
template<class T> class Frame :
  public DaqFramePreamble,
  public std::vector<T>
{
public:
  /**
   * item type definition
   */
  typedef T item_type;

  /**
   * Default constructor.
   */
  Frame() :
    DaqFramePreamble()
  {}

  /**
   * Print ASCII.
   *
   * \param  out     output stream
   * \param  object  frame
   * \return         output stream
   */
  friend std::ostream& operator<<(std::ostream& out, const Frame& object)
  {
    using namespace std;
    
    out << static_cast<const DaqFramePreamble&>(object);

    if (!object.empty()) {            
      out << ' ' << *(object. begin());
      out << " ... ";
      out << ' ' << *(object.rbegin());
      out << endl;
    }


    return out;
  }

  /**
   * operator +=
   *
   * \param  object   frame
   * \return          this frame
   */
  Frame<T>& operator+=(const Frame<T>& object)
  {
    if ((DaqFramePreamble&) *this == (DaqFramePreamble&) object) {
      for (typename Frame<T>::const_iterator i = object.begin(); i != object.end(); ++i) {
	this->insert(std::lower_bound(Frame<T>::begin(),Frame<T>::end(),*i),*i);
      }
    }
    
    return *this;
  }
		 
  /** ROOT class definition */
  ClassDef(Frame, 2);
};

ClassDefT2(Frame,T);
ClassImpT(Frame,T);


/**
 * Status frame
 */
class Status_Frame :
  public Frame<Status_Item>
{
public:
  /**
   * Default constructor.
   */
  Status_Frame() : Frame<Status_Item>() {}

  /** ROOT class definition */
  ClassDef(Status_Frame, 2);
};


/**
 * RTS frame
 */
class RTS_Frame :
  public Frame<RTS_Item>
{
public:
  /**
   * Default constructor.
   */
  RTS_Frame() : Frame<RTS_Item>() {}

  /** ROOT class definition */
  ClassDef(RTS_Frame, 2);
};


/**
 * CRM frame
 */
class CRM_Frame :
  public Frame<CRM_Item>
{
public:
  /**
   * Default constructor.
   */
  CRM_Frame() : Frame<CRM_Item>() {}

  /** ROOT class definition */
  ClassDef(CRM_Frame, 2);
};


/**
 * SPE frame
 */
class SPE_Frame :
  public Frame<SPE_Item>
{
public:
  /**
   * Default constructor.
   */
  SPE_Frame() : Frame<SPE_Item>() {}

  /** ROOT class definition */
  ClassDef(SPE_Frame, 2);
};


/**
 * AWF frame
 */
class AWF_Frame :
  public Frame<AWF_Item>
{
public:
  /**
   * Default constructor.
   */
  AWF_Frame() : Frame<AWF_Item>() {}

  /** ROOT class definition */
  ClassDef(AWF_Frame, 2);
};


/**
 * DWF frame
 */
class DWF_Frame :
  public Frame<DWF_Item>
{
public:
  /**
   * Default constructor.
   */
  DWF_Frame() : Frame<DWF_Item>() {}

  /** ROOT class definition */
  ClassDef(DWF_Frame, 2);
};

/**
 * Template TimeSlice 
 */ 
template<class T>
class TimeSlice :
  public EventPreamble,
  public std::vector<T>
{
public:
  typedef T frame_type;                              //!< item type definition 
  typedef typename frame_type::item_type item_type;  //!< item sub-type definition 

  /**
   * Default constructor.
   */
  TimeSlice() :
    EventPreamble(),
    std::vector<T>()
  {}

  /**
   * Constructor.
   *
   * \param  header  event preamble
   */
  TimeSlice(const EventPreamble& header) :
    EventPreamble(header),
    std::vector<T>()
  {}

  /**
   * Print ASCII.
   *
   * \param  out     output stream
   * \param  object  time slice
   * \return         output stream
   */
  friend std::ostream& operator<<(std::ostream& out, const TimeSlice<T>& object)
  {
    out << static_cast<const EventPreamble&>(object);
    
    for (typename TimeSlice<T>::const_iterator i = object.begin(); i != object.end(); ++i) {
      out << *i;
    }

    return out;
  }

  /**
   * operator +=
   *
   * \param  object   time slice
   * \return          this time slice
   */
  TimeSlice<T>& operator+=(const TimeSlice<T>& object)
  {
    if ((EventPreamble&) *this == (EventPreamble&) object) {

      typename TimeSlice<T>::const_iterator from;
      typename TimeSlice<T>::iterator       to;

      for (from = object.begin(); from != object.end(); ++from) {

	for (to = std::vector<T>::begin(); to != std::vector<T>::end(); ++to) {
	  if (*to == *from) {
	    *to += *from;
	    break;
	  }
	}
	
	if (to == std::vector<T>::end()) {
	  this->push_back(*from);
	}
      }
    }
    
    return *this;
  }

  /** ROOT class definition */
  ClassDef(TimeSlice, 2);
};

ClassDefT2(std::vector,T);
ClassImpT(std::vector,T);

ClassDefT2(TimeSlice,T);
ClassImpT(TimeSlice,T);


/**
 * Status time slices
 */
class Status_TimeSlice :
  public TimeSlice<Status_Frame>
{
public:
  /**
   * Default constructor.
   */
  Status_TimeSlice() : TimeSlice<Status_Frame>() {}

  /** ROOT class definition */
  ClassDef(Status_TimeSlice, 2);
};


/**
 * RTS time slices
 */
class RTS_TimeSlice :
  public TimeSlice<RTS_Frame>
{
public:
  /**
   * Default constructor.
   */
  RTS_TimeSlice() : TimeSlice<RTS_Frame>() {}

  /** ROOT class definition */
  ClassDef(RTS_TimeSlice, 2);
};


/**
 * CRM time slices
 */
class CRM_TimeSlice :
  public TimeSlice<CRM_Frame>
{
public:
  /**
   * Default constructor.
   */
  CRM_TimeSlice() : TimeSlice<CRM_Frame>() {}

  /** ROOT class definition */
  ClassDef(CRM_TimeSlice, 2);
};


/**
 * SPE time slices
 */
class SPE_TimeSlice :
  public TimeSlice<SPE_Frame>
{
public:
  /**
   * Default constructor.
   */
  SPE_TimeSlice() : TimeSlice<SPE_Frame>() {}

  /** ROOT class definition */
  ClassDef(SPE_TimeSlice, 2);
};


/**
 * AWF time slices
 */
class AWF_TimeSlice :
  public TimeSlice<AWF_Frame>
{
public:
  /**
   * Default constructor.
   */
  AWF_TimeSlice() : TimeSlice<AWF_Frame>() {}

  /** ROOT class definition */
  ClassDef(AWF_TimeSlice, 2);
};


/**
 * DWF time slices
 */
class DWF_TimeSlice :
  public TimeSlice<DWF_Frame>
{
public:
  /**
   * Default constructor.
   */
  DWF_TimeSlice() : TimeSlice<DWF_Frame>() {}

  /** ROOT class definition */
  ClassDef(DWF_TimeSlice, 2);
};


/**
 * Summary time slices
 */
class Summary_TimeSlice :
  public TimeSlice<Summary_Frame>
{
public:
  /**
   * Default constructor.
   */
  Summary_TimeSlice() : TimeSlice<Summary_Frame>() {}

  /**
   * Constructor.
   *
   * param  object   time slice
   */
  template<class T>
  Summary_TimeSlice(const TimeSlice<T>& object) : 
    TimeSlice<Summary_Frame>((const EventPreamble&) object)
  {
    for (typename TimeSlice<T>::const_iterator i = object.begin(); i != object.end(); ++i) {
      this->push_back(Summary_Frame(i->LCM_ID,
				    i->ARS_ID,
				    i->dataType,
				    i->nbItems));
    }
  }
  
  /**
   * operator +=
   *
   * \param  object   time slice
   * \return          this summary time slice
   */
  template<class T>
  Summary_TimeSlice& operator+=(const TimeSlice<T>& object)
  {
    if ((EventPreamble&) *this == (EventPreamble&) object) {
      for (typename TimeSlice<T>::const_iterator i = object.begin(); i != object.end(); ++i) {
	this->push_back(Summary_Frame(i->LCM_ID,
				      i->ARS_ID,
				      i->dataType,
				      i->nbItems));
      }
    }
    
    return *this;
  }

  /** ROOT class definition */
  ClassDef(Summary_TimeSlice, 2);
};
 

/**
 * ExtendedSummary time slices
 */
class ExtendedSummary_TimeSlice :
  public TimeSlice<ExtendedSummary_Frame>
{
public:
  /**
   * Default constructor.
   */
  ExtendedSummary_TimeSlice() : TimeSlice<ExtendedSummary_Frame>() {}

  /**
   * Constructor.
   *
   * param  object   time slice
   */
  template<class T>
  ExtendedSummary_TimeSlice(const TimeSlice<T>& object) : 
    TimeSlice<ExtendedSummary_Frame>((EventPreamble&) object)
  {
    for (typename TimeSlice<T>::const_iterator i = object.begin(); i != object.end(); ++i) {
      this->push_back(ExtendedSummary_Frame(i->LCM_ID,
					    i->ARS_ID,
					    i->dataType,
					    i->nbItems,
					    0));
    }
  }

  /** ROOT class definition */
  ClassDef(ExtendedSummary_TimeSlice, 2);
};

 
/**
 * equal operator for summary frame and DAQ frame preamble
 *
 * \param  first      summary frame
 * \param  second     DAQ frame preamble
 * \return            true if first equals second; else false
 */
inline const bool operator==(const Summary_Frame& first, const DaqFramePreamble& second)
{
  return (first.data_type() == second.dataType &&
          first.lcm_id()    == second.LCM_ID   &&
          first.ars_id()    == second.ARS_ID);
}

/**
 * equal operator for DAQ frame preamble and summary frame
 *
 * \param  first      DAQ frame preamble
 * \param  second     summary frame
 * \return            true if first equals second; else false
 */
inline const bool operator==(const DaqFramePreamble& first, const Summary_Frame& second)
{
  return (first.dataType == second.data_type() &&
          first.LCM_ID   == second.lcm_id()    &&
          first.ARS_ID   == second.ars_id());
}
 
/**
 * not-equal operator for summary frame and DAQ frame preamble
 *
 * \param  first      summary frame
 * \param  second     DAQ frame preamble
 * \return            true if first not equals second; else false
 */
inline const bool operator!=(const Summary_Frame& first, const DaqFramePreamble& second)
{
  return (first.data_type() != second.dataType ||
          first.lcm_id()    != second.LCM_ID   ||
          first.ars_id()    != second.ARS_ID);
}

/**
 * not-equal operator for DAQ frame preamble and summary frame
 *
 * \param  first      DAQ frame preamble
 * \param  second     summary frame
 * \return            true if first not equals second; else false
 */
inline const bool operator!=(const DaqFramePreamble& first, const Summary_Frame& second)
{
  return (first.dataType != second.data_type() ||
          first.LCM_ID   != second.lcm_id()    ||
          first.ARS_ID   != second.ars_id());
}

#endif