#ifndef TBPIXELSVC_H
#define TBPIXELSVC_H

// Gaudi
#include "GaudiKernel/Service.h"
#include "GaudiKernel/Bootstrap.h"
#include "GaudiKernel/MsgStream.h"

// Local
#include "TbKernel/ITbPixelSvc.h"
#include "TbKernel/ITbGeometrySvc.h"

#include <stdint.h>

// struct PixelConfig;
template <class TYPE>
class SvcFactory;

/** @class TbPixelSvc TbPixelSvc.h
 *
 * Implementation of the Timepix3 pixel configuration service.
 *
 */

class TbPixelSvc : public extends1<Service, ITbPixelSvc> {

 public:
  /// Constructor
  TbPixelSvc(const std::string& name, ISvcLocator* svc);
  /// Destructor
  virtual ~TbPixelSvc();

  virtual StatusCode initialize();

  /// Get the pixel address for a given column and row.
  virtual unsigned int address(const unsigned int col,
                               const unsigned int row) const {
    // Get the double-column and super-pixel.
    const unsigned int dcol = col / 2;
    const unsigned int spix = row / 4;
    // Get the pixel number within the superpixel.
    unsigned int pix = row % 4;
    if (1 == col % 2) pix += 4;
    return (dcol << 9) + (spix << 3) + pix;
  }
  std::pair<unsigned int, unsigned int> posFromAddress( const unsigned int& address ) const {
    std::pair<unsigned int, unsigned int> pos;
    // Decode the pixel address, first get the double column. 
    const unsigned int dcol = (0xFE00 & address) >> 8;
    // Get the super pixel address.
    const unsigned int spix = (0x01F8 & address) >> 1;
    // Get the address of the pixel within the super pixel. 
    const unsigned int pix = (0x0007 & address); 
    // Calculate the row and column numbers.
    pos.first = dcol + pix / 4;
    pos.second = spix + (pix & 0x3);
    return pos;
  }

  /// Return whether the pixel has been masked.
  virtual bool isMasked(const unsigned int address,
      const unsigned int device) const {
    return m_pixelConfiguration[device][address].isMasked ||
           m_pixelConfiguration[device][address].trimDac_isMasked;
  }

  /// Correct the timestamp of a given hit.
  virtual void applyPhaseCorrection(LHCb::TbHit* h) {
    h->setTime(h->time() +
        m_pixelConfiguration[h->device()][h->pixelAddress()].tOffset);
  }

  /// Set the clock phase correction.
  virtual void setPhase(const unsigned int device,
      const unsigned int pll_config, const int amplitude);

  /// Set trim DACs.
  virtual void setTrim(const unsigned int device, const char* data);

  /// Convert time-over-threshold to charge.
  virtual double charge(const unsigned int tot, const unsigned int address,
      const unsigned int device) const {
    const PixelConfig& conf = m_pixelConfiguration[device][address];
    double value=inverseSurrogate(tot, conf.p1, conf.p0, conf.c, conf.t);
    if( isnan(value)  ){ 
      auto pos = posFromAddress( address );
      warning() << "Pixel " << device << "/0x" 
                << std::hex << address << std::dec << " tot = " << tot  
                << " (" << pos.first << ", " << pos.second 
                << ")" << conf.p1 << "  " << conf.p0 << "   " << conf.c << "   " << conf.t << endmsg;
      return 1;
    }
    return value;
  }

 private:
  /// Allow SvcFactory to instantiate the service.
  friend class SvcFactory<TbPixelSvc>;

  struct PixelConfig {
    PixelConfig()
      : isMasked(false),
      trimDac_isMasked(false),
      isDead(false),
      tp_ena(0),
      trim(4),
      tOffset(0),
      p0(0),
      p1(1),
      c(0),
      t(0) {};
    bool isMasked;
    bool trimDac_isMasked;
    bool isDead;
    bool tp_ena;
    uint8_t trim;
    int tOffset;
    /// Parameters of surrogate function
    float p0;
    float p1;
    float c;
    float t;
  };


  void resetClockPhase(const unsigned int device);
  /// Add a constant offset in time to a single super column.
  void addOffset(const int offset, const unsigned int device,
      const unsigned int dcol);

  /// Pixel configuration for each chip
  std::vector<std::vector<PixelConfig> > m_pixelConfiguration;

  void printConfig( const unsigned int& plane, const unsigned int& address){
    auto pos = posFromAddress(address);
    PixelConfig& conf = m_pixelConfiguration[plane][address];
    info() << std::hex << "0x" << address << std::dec << " = ("<<pos.first << ", " << pos.second << ") : " << conf.p0 << " " << conf.p1 << " " << conf.c << "  " << conf.t << endmsg;
  }
  /// Functionality to write out the trim dacs written into the header
  bool m_writeTrimDACs;
  void writeTrimDAC(const unsigned int device);

  /// ignore PLL config
  std::vector<bool> m_protectPhase;
  /// Surrogate function, taken from http://arxiv.org/pdf/1103.2739v3.pdf
  double surrogate(const double& charge, const double& a, const double& b,
      const double& c, const double& t) const {
    double result = 0.;
    const double r = (b + a * t);
    const double d = r * r + 4 * a * c;
    if (d > 0.) {
      const double itcpt = ((t * a - b) + sqrt(d)) / (2 * a);
      if (charge > itcpt) {
        result = a * charge + b - c / (charge - t);
      }
    }
    return result;
  }

  double inverseSurrogate(const uint32_t tot, const double a, const double b,
      const double c, const double t) const {
    double result = 1.;
    const double r = (b + a * t - tot);
    const double d = r * r + 4. * a * c;
    if (d > 0.) {
      result = (a * t + tot - b + sqrt(d)) / (2. * a);
    }
    return result;
  }

  bool readConditions(const std::string& file);

  /// Pointer to geometry service
  mutable ITbGeometrySvc* m_geomSvc;
  /// Access geometry service on-demand
  ITbGeometrySvc* geomSvc() const {
    if (!m_geomSvc) {
      m_geomSvc = Gaudi::svcLocator()->service<ITbGeometrySvc>("TbGeometrySvc");
    }
    return m_geomSvc;
  }
  /// Pointer to message stream
  mutable MsgStream* m_msg;
  /// On-demand access to message stream
  MsgStream& msg() const {
    if (!m_msg) m_msg = new MsgStream(msgSvc(), name());
    return *m_msg;
  }
};

#endif
