#ifndef MpsSensor1BunchTrainDatum_HH
#define MpsSensor1BunchTrainDatum_HH

#include <iostream>
#include <fstream>

#include "UtlPack.hh"


class MpsSensor1BunchTrainDatum {

public:
  MpsSensor1BunchTrainDatum();
  MpsSensor1BunchTrainDatum(unsigned d);

  unsigned char channels() const;
  void          channels(unsigned char c);

  bool channel(unsigned c) const;
  void channel(unsigned c, bool b);

  unsigned group() const;
  void     group(unsigned g);
  unsigned muxAddressRaw() const;
  void     muxAddressRaw(unsigned g);
  unsigned muxAddressGray() const;
  void     muxAddressGray(unsigned g);
  unsigned muxAddress() const;
  void     muxAddress(unsigned g);

  unsigned row() const;
  void     row(unsigned r);
  unsigned rowGray() const;
  void     rowGray(unsigned r);

  unsigned timeStamp() const;
  void     timeStamp(unsigned t);
  unsigned timeStampGray() const;
  void     timeStampGray(unsigned t);

  bool firstRegionDatum() const;
  void firstRegionDatum(bool b);

  MpsSensor1Hit sensorHit(unsigned r) const;

  void datum(unsigned d);

  std::ostream& print(std::ostream &o=std::cout, std::string s="") const;


private:
  unsigned grayToBinary(unsigned g) const;
  unsigned binaryToGray(unsigned u) const;

  static const unsigned _muxAddressConverter[8];

  UtlPack _datum;
};


#ifdef CALICE_DAQ_ICC

#include <cstring>

#include "UtlPrintHex.hh"


MpsSensor1BunchTrainDatum::MpsSensor1BunchTrainDatum() {
}

MpsSensor1BunchTrainDatum::MpsSensor1BunchTrainDatum(unsigned d) : _datum(d) {
}

unsigned char MpsSensor1BunchTrainDatum::channels() const {
  return _datum.bits(16,21);
}

void MpsSensor1BunchTrainDatum::channels(unsigned char c) {
  assert(c<64);
  _datum.bits(16,21,c);
}

bool MpsSensor1BunchTrainDatum::channel(unsigned c) const {
  assert(c<6);
  return !_datum.bit(21-c);
}

void MpsSensor1BunchTrainDatum::channel(unsigned c, bool b) {
  assert(c<6);
  _datum.bit(21-c,!b);
}

unsigned MpsSensor1BunchTrainDatum::group() const {
  if(muxAddress()==0) return 7; // Only if reading "park" MUX address
  return muxAddress()-1;
}

void MpsSensor1BunchTrainDatum::group(unsigned g) {
  assert(g<8);
  if(g==7) muxAddress(0);
  else     muxAddress(g+1);
}

unsigned MpsSensor1BunchTrainDatum::muxAddressRaw() const {
  return _datum.bits(13,15);
}

void MpsSensor1BunchTrainDatum::muxAddressRaw(unsigned g) {
  assert(g<8);
  _datum.bits(13,15,g);
}

unsigned MpsSensor1BunchTrainDatum::muxAddressGray() const {
  return _muxAddressConverter[muxAddressRaw()];
}

void MpsSensor1BunchTrainDatum::muxAddressGray(unsigned g) {
  assert(g<8);
  muxAddressRaw(_muxAddressConverter[g]);
}

unsigned MpsSensor1BunchTrainDatum::muxAddress() const {
  return grayToBinary(muxAddressGray());
}

void MpsSensor1BunchTrainDatum::muxAddress(unsigned g) {
  assert(g<8);
  muxAddressGray(binaryToGray(g));
}

unsigned MpsSensor1BunchTrainDatum::row() const {
  return grayToBinary(_datum.bits(22,30));
}

void MpsSensor1BunchTrainDatum::row(unsigned r) {
  assert(r<168);
  _datum.bits(22,30,binaryToGray(r));
}

unsigned MpsSensor1BunchTrainDatum::rowGray() const {
  return _datum.bits(22,30);
}

void MpsSensor1BunchTrainDatum::rowGray(unsigned r) {
  assert(r<168);
  _datum.bits(22,30,r);
}

unsigned MpsSensor1BunchTrainDatum::timeStampGray() const {
  return _datum.bits(0,12);
}

void MpsSensor1BunchTrainDatum::timeStampGray(unsigned t) {
  assert(t<0x2000);
  _datum.bits(0,12,t);
}

unsigned MpsSensor1BunchTrainDatum::timeStamp() const {
  return grayToBinary(_datum.bits(0,12));
}

void MpsSensor1BunchTrainDatum::timeStamp(unsigned t) {
  assert(t<8192);
  _datum.bits(0,12,binaryToGray(t));
}

bool MpsSensor1BunchTrainDatum::firstRegionDatum() const {
  return _datum.bit(31);
}

void MpsSensor1BunchTrainDatum::firstRegionDatum(bool b) {
  _datum.bit(31,b);
}

void MpsSensor1BunchTrainDatum::datum(unsigned d) {
  _datum.word(d);
}

unsigned MpsSensor1BunchTrainDatum::grayToBinary(unsigned g) const {
  g^=(g>>16);
  g^=(g>> 8);
  g^=(g>> 4);
  g^=(g>> 2);
  g^=(g>> 1);
  return g;
}

unsigned MpsSensor1BunchTrainDatum::binaryToGray(unsigned u) const {
  unsigned g((u>>1)^u);
  return g;
}

std::ostream& MpsSensor1BunchTrainDatum::print(std::ostream &o, std::string s) const {
  o << s << "MpsSensor1BunchTrainDatum::print()" << std::endl;

  o << s << " Datum = " << printHex(_datum.word()) << std::endl;

  o << s << "  Channels = " << printHex(channels()) << ", Channels hit: ";
  if(channels()==63) o << "none" << std::endl;
  else {
    bool first(true);
    for(unsigned c(0);c<6;c++) {
      if(channel(c)) {
	if(first) o << c;
	else o << "," << c;
	first=false;
      }
    }
    o << std::endl;
  }

  o << s << "  MUX address raw = " << muxAddressRaw()
    << ", in graycode = " << muxAddressGray() 
    << ", in binary = " << muxAddress()
    << ", group = " << group() << std::endl;

  o << s << "  Row in graycode = " << rowGray()
    << ", in binary = " << row() << std::endl;

  o << s << "  Timestamp in graycode = " << timeStampGray()
    << ", in binary = " << timeStamp() << std::endl;

  if(firstRegionDatum()) o << s << "  First region datum" << std::endl;

  return o;
}

const unsigned MpsSensor1BunchTrainDatum::_muxAddressConverter[]={
  0,4,2,6,1,5,3,7
};

#endif
#endif
