/**
 * \class L1GtfeExtWord
 *
 *
 * Description: L1 Global Trigger - GTFE words in the readout record.
 *
 * Implementation:
 *    <TODO: enter implementation details>
 *
 * \author: Vasile Mihai Ghete - HEPHY Vienna
 *
 *
 */

// this class header
#include "DataFormats/L1GlobalTrigger/interface/L1GtfeExtWord.h"

// system include files
#include <iomanip>
#include <cstdint>

// user include files
#include "FWCore/Utilities/interface/EDMException.h"

#include "FWCore/MessageLogger/interface/MessageLogger.h"
#include "FWCore/MessageLogger/interface/MessageDrop.h"

#include "DataFormats/L1GlobalTrigger/interface/L1GlobalTriggerReadoutSetupFwd.h"
#include "DataFormats/L1GlobalTrigger/interface/L1GlobalTriggerReadoutSetup.h"

// constructors

// empty constructor, all members set to zero;
L1GtfeExtWord::L1GtfeExtWord() : L1GtfeWord(), m_bstSource(0) {
  // empty
}

// all members set to zero, m_bst has bstSizeBytes zero elements
L1GtfeExtWord::L1GtfeExtWord(int bstSizeBytes) : L1GtfeWord(), m_bstSource(0) { m_bst.resize(bstSizeBytes); }

// constructor from unpacked values, m_bst size taken from bstValue
L1GtfeExtWord::L1GtfeExtWord(cms_uint16_t boardIdValue,
                             cms_uint16_t recordLength1Value,
                             cms_uint16_t recordLengthValue,
                             cms_uint16_t bxNrValue,
                             cms_uint32_t setupVersionValue,
                             cms_uint16_t activeBoardsValue,
                             cms_uint16_t altNrBxBoardValue,
                             cms_uint32_t totalTriggerNrValue,  // end of L1GtfeWord
                             const std::vector<cms_uint16_t>& bstValue,
                             cms_uint16_t bstSourceValue)
    : L1GtfeWord(boardIdValue,
                 recordLength1Value,
                 recordLengthValue,
                 bxNrValue,
                 setupVersionValue,
                 activeBoardsValue,
                 altNrBxBoardValue,
                 totalTriggerNrValue),
      m_bst(bstValue),
      m_bstSource(bstSourceValue)

{
  // empty
}

// destructor
L1GtfeExtWord::~L1GtfeExtWord() {
  // empty now
}

// equal operator
bool L1GtfeExtWord::operator==(const L1GtfeExtWord& result) const {
  // base class

  const L1GtfeWord gtfeResult = result;
  const L1GtfeWord gtfeThis = *this;

  if (gtfeThis != gtfeResult) {
    return false;
  }

  //

  for (unsigned int iB = 0; iB < m_bst.size(); ++iB) {
    if (m_bst[iB] != result.m_bst[iB]) {
      return false;
    }
  }

  if (m_bstSource != result.m_bstSource) {
    return false;
  }

  // all members identical
  return true;
}

// unequal operator
bool L1GtfeExtWord::operator!=(const L1GtfeExtWord& result) const { return !(result == *this); }

// methods

const cms_uint64_t L1GtfeExtWord::gpsTime() const {
  cms_uint64_t gpst = 0ULL;

  // return 0 if BST message too small
  int bstSize = m_bst.size();
  if (GpsTimeLastBlock >= bstSize) {
    return gpst;
  }

  for (int iB = GpsTimeFirstBlock; iB <= GpsTimeLastBlock; ++iB) {
    // keep capitalization for similarity with other functions
    const int scaledIB = iB - GpsTimeFirstBlock;
    const int BstShift = BstBitSize * scaledIB;

    gpst = gpst | ((static_cast<cms_uint64_t>(m_bst[iB])) << BstShift);
  }

  return gpst;
}

void L1GtfeExtWord::setGpsTime(const cms_uint64_t gpsTimeValue) {
  // return if BST message too small
  int bstSize = m_bst.size();
  if (GpsTimeLastBlock >= bstSize) {
    edm::LogError("L1GtfeExtWord") << "Error: BST message length " << bstSize
                                   << " smaller than the required GpsTimeLastBlock " << GpsTimeLastBlock
                                   << "\n Cannot set GpsTime" << std::endl;

    return;
  }

  for (int iB = GpsTimeFirstBlock; iB <= GpsTimeLastBlock; ++iB) {
    // keep capitalization for similarity with other functions
    const int scaledIB = iB - GpsTimeFirstBlock;
    const int BstShift = BstBitSize * scaledIB;
    const cms_uint64_t BstMask = 0x0000000000000000ULL | (BstBlockMask << BstShift);

    m_bst[iB] = static_cast<cms_uint16_t>((gpsTimeValue & BstMask) >> BstShift);

    //LogTrace("L1GtfeExtWord")
    //<< "BstShift: value [dec] = " << BstShift << "\n"
    //<< "BstBlockMask: value [hex] = "  << std::hex << BstBlockMask << "\n"
    //<< "BstMask: value [hex] = "<< BstMask << std::dec
    //<< std::endl;

    //LogTrace("L1GtfeExtWord")
    //<< "BST block " << iB << ": value [hex] = " << std::hex << m_bst[iB] << std::dec
    //<< std::endl;
  }
}

const cms_uint16_t L1GtfeExtWord::bstMasterStatus() const {
  cms_uint16_t bms = 0;

  // return 0 if BST message too small
  int bstSize = m_bst.size();
  if (BstMasterStatusLastBlock >= bstSize) {
    return bms;
  }

  for (int iB = BstMasterStatusFirstBlock; iB <= BstMasterStatusLastBlock; ++iB) {
    // keep capitalization for similarity with other functions
    const int scaledIB = iB - BstMasterStatusFirstBlock;
    const int BstShift = BstBitSize * scaledIB;

    bms = bms | (m_bst[iB] << BstShift);
  }

  return bms;
}

const cms_uint32_t L1GtfeExtWord::turnCountNumber() const {
  cms_uint32_t tcn = 0;

  // return 0 if BST message too small
  int bstSize = m_bst.size();
  if (TurnCountNumberLastBlock >= bstSize) {
    return tcn;
  }

  for (int iB = TurnCountNumberFirstBlock; iB <= TurnCountNumberLastBlock; ++iB) {
    // keep capitalization for similarity with other functions
    const int scaledIB = iB - TurnCountNumberFirstBlock;
    const int BstShift = BstBitSize * scaledIB;

    tcn = tcn | ((static_cast<cms_uint32_t>(m_bst[iB])) << BstShift);
  }

  return tcn;
}

const cms_uint32_t L1GtfeExtWord::lhcFillNumber() const {
  cms_uint32_t lhcfn = 0;

  // return 0 if BST message too small
  int bstSize = m_bst.size();
  if (LhcFillNumberLastBlock >= bstSize) {
    return lhcfn;
  }

  for (int iB = LhcFillNumberFirstBlock; iB <= LhcFillNumberLastBlock; ++iB) {
    // keep capitalization for similarity with other functions
    const int scaledIB = iB - LhcFillNumberFirstBlock;
    const int BstShift = BstBitSize * scaledIB;

    lhcfn = lhcfn | ((static_cast<cms_uint32_t>(m_bst[iB])) << BstShift);
  }

  return lhcfn;
}

const cms_uint16_t L1GtfeExtWord::beamMode() const {
  cms_uint16_t bm = 0;

  // return 0 if BST message too small
  int bstSize = m_bst.size();
  if (BeamModeLastBlock >= bstSize) {
    return bm;
  }

  for (int iB = BeamModeFirstBlock; iB <= BeamModeLastBlock; ++iB) {
    // keep capitalization for similarity with other functions
    const int scaledIB = iB - BeamModeFirstBlock;
    const int BstShift = BstBitSize * scaledIB;

    bm = bm | (m_bst[iB] << BstShift);
  }

  return bm;
}

const cms_uint16_t L1GtfeExtWord::particleTypeBeam1() const {
  cms_uint16_t ptb = 0;

  // return 0 if BST message too small
  int bstSize = m_bst.size();
  if (ParticleTypeBeam1LastBlock >= bstSize) {
    return ptb;
  }

  for (int iB = ParticleTypeBeam1FirstBlock; iB <= ParticleTypeBeam1LastBlock; ++iB) {
    // keep capitalization for similarity with other functions
    const int scaledIB = iB - ParticleTypeBeam1FirstBlock;
    const int BstShift = BstBitSize * scaledIB;

    ptb = ptb | (m_bst[iB] << BstShift);
  }

  return ptb;
}

const cms_uint16_t L1GtfeExtWord::particleTypeBeam2() const {
  cms_uint16_t ptb = 0;

  // return 0 if BST message too small
  int bstSize = m_bst.size();
  if (ParticleTypeBeam2LastBlock >= bstSize) {
    return ptb;
  }

  for (int iB = ParticleTypeBeam2FirstBlock; iB <= ParticleTypeBeam2LastBlock; ++iB) {
    // keep capitalization for similarity with other functions
    const int scaledIB = iB - ParticleTypeBeam2FirstBlock;
    const int BstShift = BstBitSize * scaledIB;

    ptb = ptb | (m_bst[iB] << BstShift);
  }

  return ptb;
}

const cms_uint16_t L1GtfeExtWord::beamMomentum() const {
  cms_uint16_t bm = 0;

  // return 0 if BST message too small
  int bstSize = m_bst.size();
  if (BeamMomentumLastBlock >= bstSize) {
    return bm;
  }

  for (int iB = BeamMomentumFirstBlock; iB <= BeamMomentumLastBlock; ++iB) {
    // keep capitalization for similarity with other functions
    const int scaledIB = iB - BeamMomentumFirstBlock;
    const int BstShift = BstBitSize * scaledIB;

    bm = bm | (m_bst[iB] << BstShift);
  }

  return bm;
}

const cms_uint32_t L1GtfeExtWord::totalIntensityBeam1() const {
  cms_uint32_t tib = 0;

  // return 0 if BST message too small
  int bstSize = m_bst.size();
  if (TotalIntensityBeam1LastBlock >= bstSize) {
    return tib;
  }

  for (int iB = TotalIntensityBeam1FirstBlock; iB <= TotalIntensityBeam1LastBlock; ++iB) {
    // keep capitalization for similarity with other functions
    const int scaledIB = iB - TotalIntensityBeam1FirstBlock;
    const int BstShift = BstBitSize * scaledIB;

    tib = tib | ((static_cast<cms_uint32_t>(m_bst[iB])) << BstShift);
  }

  return tib;
}

const cms_uint32_t L1GtfeExtWord::totalIntensityBeam2() const {
  cms_uint32_t tib = 0;

  // return 0 if BST message too small
  int bstSize = m_bst.size();
  if (TotalIntensityBeam2LastBlock >= bstSize) {
    return tib;
  }

  for (int iB = TotalIntensityBeam2FirstBlock; iB <= TotalIntensityBeam2LastBlock; ++iB) {
    // keep capitalization for similarity with other functions
    const int scaledIB = iB - TotalIntensityBeam2FirstBlock;
    const int BstShift = BstBitSize * scaledIB;

    tib = tib | ((static_cast<cms_uint32_t>(m_bst[iB])) << BstShift);
  }

  return tib;
}

// get/set BST for block iB
const uint16_t L1GtfeExtWord::bst(int iB) const {
  int NumberBstBlocks = m_bst.size();

  if (iB < 0 || iB >= NumberBstBlocks) {
    throw cms::Exception("BstIndexError")
        << "\nError: index for BST array out of range. Allowed range: [0, " << NumberBstBlocks << ") " << std::endl;

  } else {
    return m_bst[iB];
  }
}

void L1GtfeExtWord::setBst(const uint16_t bstVal, const int iB) {
  int NumberBstBlocks = m_bst.size();

  if (iB < 0 || iB >= NumberBstBlocks) {
    throw cms::Exception("BstIndexError")
        << "\nError: index for BST array out of range. Allowed range: [0, " << NumberBstBlocks << ") " << std::endl;

  } else {
    m_bst[iB] = bstVal;
  }
}

// set the BST block for index iB from a 64-bits word
void L1GtfeExtWord::setBst(const cms_uint64_t& word64, const int iB) {
  // keep capitalization for similarity with other functions //FIXME check it again
  const int scaledIB = iB % (sizeof(word64) * 8 / BstBitSize);
  const int BstShift = BstBitSize * scaledIB;
  const cms_uint64_t BstMask = 0x0000000000000000ULL | (BstBlockMask << BstShift);

  m_bst[iB] = static_cast<cms_uint16_t>((word64 & BstMask) >> BstShift);
}

// set the BST block in a 64-bits word, having the index iWord
// in the GTFE raw record
void L1GtfeExtWord::setBstWord64(cms_uint64_t& word64, int iB, int iWord) {
  // keep capitalization for similarity with other functions
  const int scaledIB = iB % (sizeof(word64) * 8 / BstBitSize);
  const int BstShift = BstBitSize * scaledIB;
  const int BstWord = iB / (sizeof(word64) * 8 / BstBitSize) + BstFirstWord;

  if (iWord == BstWord) {
    word64 = word64 | (static_cast<cms_uint64_t>(m_bst[iB]) << BstShift);
  }
}

// set the hex message indicating the source of BST message from a 64-bits word
void L1GtfeExtWord::setBstSource(const cms_uint64_t& word64) {
  m_bstSource = (word64 & BstSourceMask) >> BstSourceShift;
}

// set hex message indicating the source of BST message in a 64-bits word,
// having the index iWord in the GTFE raw record
void L1GtfeExtWord::setBstSourceWord64(cms_uint64_t& word64, const int iWord) {
  // BST always in the last word of GTFE extended - size must be correct!
  int gtfeSize = this->getSize();

  int BstSourceWord = gtfeSize / 8 - 1;  // counting starts at 0

  if (iWord == BstSourceWord) {
    word64 = word64 | (static_cast<cms_uint64_t>(m_bstSource) << BstSourceShift);
  }
}

// get the size of the GTFE block in GT EVM record (in multiple of 8 bits)
const unsigned int L1GtfeExtWord::getSize() const {
  L1GtfeWord gtfeWord;
  unsigned int gtfeSize = gtfeWord.getSize();

  unsigned int gtfeExtSize;

  // 2 bytes to write if real BST message or simulated BST message
  unsigned int bytesBstWriter = 2;

  // size of BST block, using rounded 64-bit words (8 bytes per 64-bit word)

  unsigned int bstSize = m_bst.size();

  if ((bstSize + bytesBstWriter) % 8 == 0) {
    gtfeExtSize = gtfeSize + bstSize + bytesBstWriter;
  } else {
    gtfeExtSize = gtfeSize + bstSize + bytesBstWriter + (8 - (bstSize + bytesBstWriter) % 8);
  }

  return gtfeExtSize;
}

// resize the BST vector to get the right size of the block
void L1GtfeExtWord::resize(int bstSizeBytes) { m_bst.resize(bstSizeBytes); }

// reset the content of a L1GtfeExtWord
void L1GtfeExtWord::reset() {
  L1GtfeWord::reset();
  m_bst.clear();
}

// pretty print the content of a L1GtfeWord
void L1GtfeExtWord::print(std::ostream& myCout) const {
  myCout << "\n L1GtfeExtWord::print \n" << std::endl;

  unsigned int sizeW64 = 64;
  unsigned int dataBlocksPerLine = sizeW64 / 8;  // 8x8 bits per line

  L1GtfeWord::print(myCout);

  unsigned int numberBstBlocks = m_bst.size();

  myCout << "\n  BST ";

  if (numberBstBlocks == 0) {
    myCout << "\n  BST source [hex]: " << std::hex << std::setw(4) << std::setfill('0') << m_bstSource
           << std::setfill(' ') << std::dec << std::endl;

    return;
  }

  for (unsigned int iB = 0; iB < numberBstBlocks; iB += dataBlocksPerLine) {
    myCout << "\n" << std::hex << " hex: ";

    for (unsigned int jB = iB; jB < dataBlocksPerLine + iB; ++jB) {
      if (jB >= numberBstBlocks) {
        break;
      }

      myCout << std::setw(2) << std::setfill('0') << m_bst[jB] << "   " << std::setfill(' ');
    }

    myCout << "\n" << std::dec << " dec: ";

    for (unsigned int jB = iB; jB < dataBlocksPerLine + iB; ++jB) {
      if (jB >= numberBstBlocks) {
        break;
      }

      myCout << std::setw(3) << std::setfill('0') << m_bst[jB] << "  " << std::setfill(' ');
    }

    myCout << std::endl;
  }

  myCout << "\n  BST source [hex]: " << std::hex << std::setw(4) << std::setfill('0') << m_bstSource
         << std::setfill(' ') << std::dec << std::endl;
}

void L1GtfeExtWord::unpack(const unsigned char* gtfePtr) {
  LogDebug("L1GtfeExtWord") << "\nUnpacking GTFE block.\n" << std::endl;

  L1GtfeWord::unpack(gtfePtr);

  // TODO make BlockSize protected & use friends instead of creating L1GtfeWord?
  L1GtfeWord gtfeWord;
  const unsigned char* gtfeExtPtr = gtfePtr + gtfeWord.getSize();

  const cms_uint64_t* payload = reinterpret_cast<cms_uint64_t const*>(gtfeExtPtr);

  int BlockSizeExt = this->getSize() / 8;
  int NumberBstBlocks = m_bst.size();

  if (edm::isDebugEnabled()) {
    for (int iWord = BstFirstWord; iWord < BlockSizeExt; ++iWord) {
      int jWord = iWord - BstFirstWord;
      LogTrace("L1GtfeExtWord") << std::setw(4) << iWord << "  " << std::hex << std::setfill('0') << std::setw(16)
                                << payload[jWord] << std::dec << std::setfill(' ') << std::endl;
    }
  }

  int blocksPerWord = sizeof(cms_uint64_t) * 8 / BstBitSize;

  for (int iB = 0; iB < NumberBstBlocks; ++iB) {
    // keep capitalization for similarity with other functions
    int BstWord = iB / blocksPerWord;

    setBst(payload[BstWord], iB);
  }
}

// static class members

// block description in the raw GT record

// index of first word for BST blocks
const int L1GtfeExtWord::BstFirstWord = 2;

// size in bits for a BST block
const int L1GtfeExtWord::BstBitSize = 8;

// BST block mask, correlated with the number of bits of a block
// 8 bit = 0xFF
const cms_uint64_t L1GtfeExtWord::BstBlockMask = 0xFFULL;

// BST blocks: conversion to defined quantities (LHC-BOB-ES-0001)

const int L1GtfeExtWord::GpsTimeFirstBlock = 0;
const int L1GtfeExtWord::GpsTimeLastBlock = 7;

const int L1GtfeExtWord::BstMasterStatusFirstBlock = 17;
const int L1GtfeExtWord::BstMasterStatusLastBlock = 17;

const int L1GtfeExtWord::TurnCountNumberFirstBlock = 18;
const int L1GtfeExtWord::TurnCountNumberLastBlock = 21;

const int L1GtfeExtWord::LhcFillNumberFirstBlock = 22;
const int L1GtfeExtWord::LhcFillNumberLastBlock = 25;

const int L1GtfeExtWord::BeamModeFirstBlock = 26;
const int L1GtfeExtWord::BeamModeLastBlock = 27;

const int L1GtfeExtWord::ParticleTypeBeam1FirstBlock = 28;
const int L1GtfeExtWord::ParticleTypeBeam1LastBlock = 28;

const int L1GtfeExtWord::ParticleTypeBeam2FirstBlock = 29;
const int L1GtfeExtWord::ParticleTypeBeam2LastBlock = 29;

const int L1GtfeExtWord::BeamMomentumFirstBlock = 30;
const int L1GtfeExtWord::BeamMomentumLastBlock = 31;

const int L1GtfeExtWord::TotalIntensityBeam1FirstBlock = 32;
const int L1GtfeExtWord::TotalIntensityBeam1LastBlock = 35;

const int L1GtfeExtWord::TotalIntensityBeam2FirstBlock = 36;
const int L1GtfeExtWord::TotalIntensityBeam2LastBlock = 39;

// BST
const cms_uint64_t L1GtfeExtWord::BstSourceMask = 0xFFFF000000000000ULL;
const int L1GtfeExtWord::BstSourceShift = 48;
