#include "rapidPiPoHost.h"

//===================== P I P O = H O S T = U T I L S ========================//

static const unsigned int maxWordLen = 256;

static bool getPiPoInstanceAndAttrName(const char *attrName,
                                       char *instanceName,
                                       char *pipoAttrName)
{
  const char *dot = std::strrchr(attrName, '.');

  if (dot != NULL)
  {
    unsigned int pipoAttrNameLen = dot - attrName;
    std::strcpy(pipoAttrName, dot + 1);

    if (pipoAttrNameLen > maxWordLen)
    {
      pipoAttrNameLen = maxWordLen;
    }

    std::strncpy(instanceName, attrName, pipoAttrNameLen);
    instanceName[pipoAttrNameLen] = '\0';

    return true;
  }

  return false;
}


static void fromPiPoStreamAttributes(PiPoStreamAttributes &src,
                                     pipoStreamAttributes &dst)
{
  unsigned int numCols = src.dims[0];
  unsigned int numLabels = src.numLabels;
  
  if (numLabels > PIPO_MAX_LABELS) {
    numLabels = PIPO_MAX_LABELS;
  }
  
  if (numLabels > numCols) {
    numLabels = numCols;
  }

  dst.hasTimeTags = src.hasTimeTags;

  if (src.rate <= MIN_PIPO_SAMPLERATE) {
    dst.rate = MIN_PIPO_SAMPLERATE;
  } else if (src.rate >= MAX_PIPO_SAMPLERATE) {
    dst.rate = MAX_PIPO_SAMPLERATE;
  } else {
    dst.rate = src.rate;
  }

  dst.rate = src.rate;
  dst.offset = src.offset;
  dst.dims = { src.dims[0], src.dims[1] };

  dst.labels = std::vector<std::string>();

  for (unsigned int i = 0; i < numLabels; ++i)
  {
    dst.labels.push_back(std::string(src.labels[i]));
  }

  dst.hasVarSize = src.hasVarSize;
  dst.domain = src.domain;
  dst.maxFrames = src.maxFrames;
}


static void toPiPoStreamAttributes(pipoStreamAttributes &src,
                                   PiPoStreamAttributes &dst)
{
  const char *labs[src.labels.size()];

  for (unsigned int i = 0; i < src.labels.size(); ++i)
  {
    labs[i] = labels[i].c_str();
  }

  dst = PiPoStreamAttributes(
    src.hasTimeTags,
    src.rate,
    src.offset,
    { src.dims[0], src.dims[1] },
    &labs[0],
    src.hasVarSize,
    src.domain;
    src.maxFrames
  );
}

//========================= H O S T = M E T H O D S ==========================//

PiPoHost::PiPoHost() :
inputStreamAttrs(PIPO_MAX_LABELS),
outputStreamAttrs(PIPO_MAX_LABELS)
{
  PiPoCollection::init();
  this->out = new PiPoOut(this);
  this->graph = nullptr;
}

PiPoHost::~PiPoHost()
{
  if (this->graph != nullptr)
  {
    delete this->graph;
  }

  delete this->out;
}

bool
PiPoHost::setPiPoGraph(std::string name)
{
  if (this->graph != nullptr)
  {
    delete this->graph;
  }

  this->graph = PiPoCollection::create(name);

  if (this->graph != NULL)
  {
    this->graphName = name;
    this->graph->connect((PiPo *)this->out);
    return true;
  }

  this->graph = nullptr;
  this->graphName = "undefined";
  return false;
}

void
PiPoHost::clearPiPoGraph()
{
  if (this->graph != nullptr)
  {
    delete this->graph;
    this->graph = nullptr;
  }
}


void
PiPoHost::onNewFrameOut(double time, std::vector<PiPoValue> &frame)
{
  std::cout << time << std::endl;
  std::cout << "please override this method" << std::endl;
}

std::vector<PiPoValue>
PiPoHost::getLastFrameOut()
{
  return this->out->getLastFrame();
}



void
PiPoHost::setInputStreamAttributes(pipoStreamAttributes &sa, bool propagate)
{
  toPiPoStreamAttributes(sa, inputStreamAttrs);

  if (propagate)
  {
    this->propagateInputStreamAttributes();
  }
}

pipoStreamAttributes
PiPoHost::getOutputStreamAttributes()
{
  pipoStreamAttributes sa;
  return fromPiPoStreamAttributes(this->outputStreamAttrs, sa);
}

int
PiPoHost::frames(double time, double weight, PiPoValue *values, unsigned int size,
                 unsigned int num)
{
  return this->graph->frames(time, weight, values, size, num);
}



// bool
// setAttr(const std::string &attrName, bool value)
// {

// }

// bool
// setAttr(const std::string &attrName, int value)
// {

// }

bool
setAttr(const std::string &attrName, double value)
{
  PiPo::Attr *attr = this->graph->getAttr(attrName.c_str());
  
  if (attr != NULL) {
    int iAttr = attr->getIndex();
    return this->pipo->setAttr(iAttr, value);
  }

  return false;
}

bool
setAttr(const std::string &attrName, const std::vector<double> &values)
{
  PiPo::Attr *attr = this->pipo->getAttr(attrName.c_str());

  if (attr != NULL) {
    int iAttr = attr->getIndex();
    double vals[values.size()];
    unsigned int i = 0;
    for (auto &value : values) {
      vals[i] = value;
      i++;
    }
    return this->pipo->setAttr(iAttr, &vals[0], values.size());
  }

  return false;
}

bool
setAttr(const std::string &attrName, const std::string &value) // for enums
{
  PiPo::Attr *attr = this->pipo->getAttr(attrName.c_str());
  
  if (attr != NULL) {
    int iAttr = attr->getIndex();
    PiPo::Type type = attr->getType();
  
    if (type == PiPo::Type::Enum) {
      std::vector<const char *> *list = attr->getEnumList();
      
      for (int i = 0; i < list->size(); i++) {
        if (strcmp(list->at(i), value.c_str()) == 0) {
          attr->set(0, i);
          return true;
        }
      }
    }
  }

  return false;
}


void
PiPoHost::propagateInputStreamAttributes()
{
  if (this->graph != nullptr)
  {
    this->graph->streamAttributes(this->inputStreamAttrs.hasTimeTags,
                            this->inputStreamAttrs.rate,
                            this->inputStreamAttrs.offset,
                            this->inputStreamAttrs.dims[0],
                            this->inputStreamAttrs.dims[1],
                            this->inputStreamAttrs.labels,
                            this->inputStreamAttrs.hasVarSize,
                            this->inputStreamAttrs.domain,
                            this->inputStreamAttrs.maxFrames);
  }
}




// void
// PiPoHost::streamAttributesChanged(PiPo *pipo, PiPo::Attr *attr) {
//   this->propagateInputAttributes();
// }

// void
// PiPoHost::signalError(PiPo *pipo, std::string errorMsg) {
//   // todo
// }

// void
// PiPoHost::signalWarning(PiPo *pipo, std::string warningMsg) {
//   // todo
// }

//--------------------- INPUT STREAM ATTRIBUTES SETTERS ----------------------//

/*
void
PiPoHost::setInputHasTimeTags(bool hasTimeTags, bool propagate) {
  this->inputStreamAttrs.hasTimeTags = hasTimeTags;

  if (propagate) {
    this->propagateInputAttributes();
  }
}

#define MIN_PIPO_SAMPLERATE (1.0 / 31536000000.0) // once a year
#define MAX_PIPO_SAMPLERATE (96000000000.0)

void
PiPoHost::setInputFrameRate(double rate, bool propagate) {
  if (rate <= MIN_PIPO_SAMPLERATE) {
    this->inputStreamAttrs.rate = MIN_PIPO_SAMPLERATE;
  } else if (rate >= MAX_PIPO_SAMPLERATE) {
    this->inputStreamAttrs.rate = MAX_PIPO_SAMPLERATE;
  } else {
    this->inputStreamAttrs.rate = rate;
  }

  if (propagate) {
    this->propagateInputAttributes();
  }
}

void
PiPoHost::setInputFrameOffset(double offset, bool propagate) {
  this->inputStreamAttrs.offset = offset;

  if (propagate) {
    this->propagateInputAttributes();
  }
}

void
PiPoHost::setInputDims(int width, int height, bool propagate) {
  this->inputStreamAttrs.dims[0] = width;
  this->inputStreamAttrs.dims[1] = height;
  
  if (propagate) {
    this->propagateInputAttributes();
  }
}

void
PiPoHost::setInputLabels(const std::vector<std::string> &labels, bool propagate) {
  const char *labs[labels.size()];

  for (unsigned int i = 0; i < labels.size(); ++i) {
    labs[i] = labels[i].c_str();
  }

  this->inputStreamAttrs.labels = &labs[0];

  if (propagate) {
    this->propagateInputAttributes();
  }
}

void
PiPoHost::setInputHasVarSize(bool hasVarSize, bool propagate) {
  this->inputStreamAttrs.hasVarSize = hasVarSize;

  if (propagate) {
    this->propagateInputAttributes();
  }
}

void
PiPoHost::setInputDomain(double domain, bool propagate) {
  this->inputStreamAttrs.domain = domain;

  if (propagate) {
    this->propagateInputAttributes();
  }
}

void
PiPoHost::setInputMaxFrames(int maxFrames, bool propagate) {
  this->inputStreamAttrs.maxFrames = maxFrames;

  if (propagate) {
    this->propagateInputAttributes();
  }
}

//--------------------- INPUT STREAM ATTRIBUTES GETTERS ----------------------//

bool
PiPoHost::getInputHasTimeTags() {
  return this->inputStreamAttrs.hasTimeTags;
}

double
PiPoHost::getInputFrameRate() {
  return this->inputStreamAttrs.rate;
}

double
PiPoHost::getInputFrameOffset() {
  return this->inputStreamAttrs.offset;
}

void
PiPoHost::getInputDims(int &width, int &height) {
  width = this->inputStreamAttrs.dims[0];
  height = this->inputStreamAttrs.dims[1];
}

void
PiPoHost::getInputLabels(std::vector<std::string> &labels) {
  //for (unsigned int i = 0; i < )
}

bool
PiPoHost::getInputHasVarSize() {
  return this->inputStreamAttrs.hasVarSize;
}

double
PiPoHost::getInputDomain() {
  return this->inputStreamAttrs.domain;
}

int
PiPoHost::getInputMaxFrames() {
  return this->inputStreamAttrs.maxFrames;
}
//*/

//--------------------- OUTPUT STREAM ATTRIBUTES GETTERS ---------------------//

void
PiPoHost::setOutputAttributes(bool hasTimeTags, double rate, double offset,
                              unsigned int width, unsigned int height,
                              const char **labels, bool hasVarSize,
                              double domain, unsigned int maxFrames) {
  if (labels != NULL) {
    int numLabels = width;
    
    if (numLabels > PIPO_MAX_LABELS) {
      numLabels = PIPO_MAX_LABELS;
    }
    
    for (unsigned int i = 0; i < numLabels; i++) {
      try {
        this->outputStreamAttrs.labels[i] = labels[i];        
      } catch(std::exception e) {
        this->outputStreamAttrs.labels[i] = "unnamed";
      }
    }
    
    this->outputStreamAttrs.numLabels = numLabels;
  } else {
    this->outputStreamAttrs.numLabels = 0;
  }
  
  this->outputStreamAttrs.hasTimeTags = hasTimeTags;
  this->outputStreamAttrs.rate = rate;
  this->outputStreamAttrs.offset = offset;
  this->outputStreamAttrs.dims[0] = width;
  this->outputStreamAttrs.dims[1] = height;
  this->outputStreamAttrs.hasVarSize = hasVarSize;
  this->outputStreamAttrs.domain = domain;
  this->outputStreamAttrs.maxFrames = maxFrames; 
}

bool
PiPoHost::getOutputHasTimeTags() {
  return this->outputStreamAttrs.hasTimeTags;
}

double
PiPoHost::getOutputFrameRate() {
  return this->outputStreamAttrs.rate;
}

double
PiPoHost::getOutputFrameOffset() {
  return this->outputStreamAttrs.offset;
}

void
PiPoHost::getOutputDims(int &width, int &height) {
  width = this->outputStreamAttrs.dims[0];
  height = this->outputStreamAttrs.dims[1];
}

void
PiPoHost::getOutputLabels(std::vector<std::string> &labels) {
  labels.clear();

  for (unsigned int i = 0; this->outputStreamAttrs.numLabels; ++i) {

    if (this->outputStreamAttrs.labels[i] != NULL) {
      labels.push_back(std::string(this->outputStreamAttrs.labels[i]));
    } else {
      labels.push_back("unnamed");
    }
  }
}

bool
PiPoHost::getOutputHasVarSize() {
  return this->outputStreamAttrs.hasVarSize;
}

double
PiPoHost::getOutputDomain() {
  return this->outputStreamAttrs.domain;
}

int
PiPoHost::getOutputMaxFrames() {
  return this->outputStreamAttrs.maxFrames;
}










//===================== PIPO HOST CLASS IMPLEMENTATION =======================//

rapidPiPoHost::rapidPiPoHost(rapidPiPoOwner *rpo) :
owner(rpo),
inputStreamAttrs(PIPO_MAX_LABELS),
outputStreamAttrs(PIPO_MAX_LABELS) {
  PiPoCollection::init();
  outputter = new rapidPiPoOutputter(this);
  chain = nullptr;
}

rapidPiPoHost::~rapidPiPoHost() {
  if (chain != nullptr) {
    delete chain;
  }
  delete outputter;
}

rapidPiPoOwner *
rapidPiPoHost::getOwner() {
  return owner;
}

void
rapidPiPoHost::onNewFrame(double time, std::vector<PiPoValue> &frame) {
  std::cout << time << std::endl;
  owner->onNewFrame(frame);
}
// void
// rapidPiPoHost::onNewFrame(const std::function<void(std::vector<PiPoValue>,
//                                              rapidPiPoOwner *rpo)> cb) {
//   outputter->setFrameCallback(cb);
// }

// void
// rapidPiPoHost::onNewFrame(std::function<void(std::vector<PiPoValue>)> cb) {
//   outputter->setSimpleFrameCallback(cb);
// }

std::vector<PiPoValue>
rapidPiPoHost::getLastFrame() {
  return outputter->getLastFrame();
}

rapidPiPo *
rapidPiPoHost::setPiPoChain(std::string name) {
  if (chain != nullptr) {
    delete chain;
  }

  chain = new rapidPiPo(name);
  chain->connect((PiPo *)outputter);
  return chain;
}

void
rapidPiPoHost::clearPiPoChain() {
  delete chain;
  chain = nullptr;
}

void
rapidPiPoHost::propagateInputAttributes() {
  if (chain != nullptr) {
    const char *colNameStr[PIPO_MAX_LABELS];

    unsigned int numCols = this->inputStreamAttrs.dims[0];
    unsigned int numLabels = this->inputStreamAttrs.numLabels;
    
    if (numLabels > PIPO_MAX_LABELS) {
      numLabels = PIPO_MAX_LABELS;
    }
    
    if (numLabels > numCols) {
      numLabels = numCols;
    }
    
    std::vector<std::string> labels(numLabels);

    if (numLabels > 0) {
      for (unsigned int i = 0; i < numLabels; i++) {
        colNameStr[i] = this->inputStreamAttrs.labels[i];
      }
      
      for (unsigned int i = numLabels; i < numCols; i++) {
        colNameStr[i] = "unnamed";
      }
      
      for (unsigned int i = 0; i < numCols; ++i) {
        labels[i] = std::string(colNameStr[i]);
      }
    }
    
    chain->streamAttributes(this->inputStreamAttrs.hasTimeTags,
                            this->inputStreamAttrs.rate,
                            this->inputStreamAttrs.offset,
                            this->inputStreamAttrs.dims[0],
                            this->inputStreamAttrs.dims[1],
                            labels,
                            this->inputStreamAttrs.hasVarSize,
                            this->inputStreamAttrs.domain,
                            this->inputStreamAttrs.maxFrames);
  }
}

// void
// rapidPiPoHost::streamAttributesChanged(PiPo *pipo, PiPo::Attr *attr) {
//   this->propagateInputAttributes();
// }

// void
// rapidPiPoHost::signalError(PiPo *pipo, std::string errorMsg) {
//   // todo
// }

// void
// rapidPiPoHost::signalWarning(PiPo *pipo, std::string warningMsg) {
//   // todo
// }

//--------------------- INPUT STREAM ATTRIBUTES SETTERS ----------------------//

void
rapidPiPoHost::setInputHasTimeTags(bool hasTimeTags, bool propagate) {
  this->inputStreamAttrs.hasTimeTags = hasTimeTags;

  if (propagate) {
    this->propagateInputAttributes();
  }
}

#define MIN_PIPO_SAMPLERATE (1.0 / 31536000000.0) /* once a year */
#define MAX_PIPO_SAMPLERATE (96000000000.0)

void
rapidPiPoHost::setInputFrameRate(double rate, bool propagate) {
  if (rate <= MIN_PIPO_SAMPLERATE) {
    this->inputStreamAttrs.rate = MIN_PIPO_SAMPLERATE;
  } else if (rate >= MAX_PIPO_SAMPLERATE) {
    this->inputStreamAttrs.rate = MAX_PIPO_SAMPLERATE;
  } else {
    this->inputStreamAttrs.rate = rate;
  }

  if (propagate) {
    this->propagateInputAttributes();
  }
}

void
rapidPiPoHost::setInputFrameOffset(double offset, bool propagate) {
  this->inputStreamAttrs.offset = offset;

  if (propagate) {
    this->propagateInputAttributes();
  }
}

void
rapidPiPoHost::setInputDims(int width, int height, bool propagate) {
  this->inputStreamAttrs.dims[0] = width;
  this->inputStreamAttrs.dims[1] = height;
  
  if (propagate) {
    this->propagateInputAttributes();
  }
}

void
rapidPiPoHost::setInputLabels(const std::vector<std::string> &labels, bool propagate) {
  const char *labs[labels.size()];

  for (unsigned int i = 0; i < labels.size(); ++i) {
    labs[i] = labels[i].c_str();
  }

  this->inputStreamAttrs.labels = &labs[0];

  if (propagate) {
    this->propagateInputAttributes();
  }
}

void
rapidPiPoHost::setInputHasVarSize(bool hasVarSize, bool propagate) {
  this->inputStreamAttrs.hasVarSize = hasVarSize;

  if (propagate) {
    this->propagateInputAttributes();
  }
}

void
rapidPiPoHost::setInputDomain(double domain, bool propagate) {
  this->inputStreamAttrs.domain = domain;

  if (propagate) {
    this->propagateInputAttributes();
  }
}

void
rapidPiPoHost::setInputMaxFrames(int maxFrames, bool propagate) {
  this->inputStreamAttrs.maxFrames = maxFrames;

  if (propagate) {
    this->propagateInputAttributes();
  }
}

//--------------------- INPUT STREAM ATTRIBUTES GETTERS ----------------------//

bool
rapidPiPoHost::getInputHasTimeTags() {
  return this->inputStreamAttrs.hasTimeTags;
}

double
rapidPiPoHost::getInputFrameRate() {
  return this->inputStreamAttrs.rate;
}

double
rapidPiPoHost::getInputFrameOffset() {
  return this->inputStreamAttrs.offset;
}

void
rapidPiPoHost::getInputDims(int &width, int &height) {
  width = this->inputStreamAttrs.dims[0];
  height = this->inputStreamAttrs.dims[1];
}

void
rapidPiPoHost::getInputLabels(std::vector<std::string> &labels) {
  //for (unsigned int i = 0; i < )
}

bool
rapidPiPoHost::getInputHasVarSize() {
  return this->inputStreamAttrs.hasVarSize;
}

double
rapidPiPoHost::getInputDomain() {
  return this->inputStreamAttrs.domain;
}

int
rapidPiPoHost::getInputMaxFrames() {
  return this->inputStreamAttrs.maxFrames;
}

//--------------------- OUTPUT STREAM ATTRIBUTES GETTERS ---------------------//

void
rapidPiPoHost::setOutputAttributes(bool hasTimeTags, double rate, double offset,
                                   unsigned int width, unsigned int height,
                                   const char **labels, bool hasVarSize,
                                   double domain, unsigned int maxFrames) {
  if (labels != NULL) {
    int numLabels = width;
    
    if (numLabels > PIPO_MAX_LABELS) {
      numLabels = PIPO_MAX_LABELS;
    }
    
    for (unsigned int i = 0; i < numLabels; i++) {
      try {
        this->outputStreamAttrs.labels[i] = labels[i];        
      } catch(std::exception e) {
        this->outputStreamAttrs.labels[i] = "unnamed";
      }
    }
    
    this->outputStreamAttrs.numLabels = numLabels;
  } else {
    this->outputStreamAttrs.numLabels = 0;
  }
  
  this->outputStreamAttrs.hasTimeTags = hasTimeTags;
  this->outputStreamAttrs.rate = rate;
  this->outputStreamAttrs.offset = offset;
  this->outputStreamAttrs.dims[0] = width;
  this->outputStreamAttrs.dims[1] = height;
  this->outputStreamAttrs.hasVarSize = hasVarSize;
  this->outputStreamAttrs.domain = domain;
  this->outputStreamAttrs.maxFrames = maxFrames; 
}

bool
rapidPiPoHost::getOutputHasTimeTags() {
  return this->outputStreamAttrs.hasTimeTags;
}

double
rapidPiPoHost::getOutputFrameRate() {
  return this->outputStreamAttrs.rate;
}

double
rapidPiPoHost::getOutputFrameOffset() {
  return this->outputStreamAttrs.offset;
}

void
rapidPiPoHost::getOutputDims(int &width, int &height) {
  width = this->outputStreamAttrs.dims[0];
  height = this->outputStreamAttrs.dims[1];
}

void
rapidPiPoHost::getOutputLabels(std::vector<std::string> &labels) {
  labels.clear();

  for (unsigned int i = 0; this->outputStreamAttrs.numLabels; ++i) {

    if (this->outputStreamAttrs.labels[i] != NULL) {
      labels.push_back(std::string(this->outputStreamAttrs.labels[i]));
    } else {
      labels.push_back("unnamed");
    }
  }
}

bool
rapidPiPoHost::getOutputHasVarSize() {
  return this->outputStreamAttrs.hasVarSize;
}

double
rapidPiPoHost::getOutputDomain() {
  return this->outputStreamAttrs.domain;
}

int
rapidPiPoHost::getOutputMaxFrames() {
  return this->outputStreamAttrs.maxFrames;
}

// void
// rapidPiPoHost::setRapidPiPoParam(rapidPiPoParam *param) {
//   char instanceName[maxWordLen];
//   char pipoAttrName[maxWordLen];

//   if (getPiPoInstanceAndAttrName(param->getName(), instanceName, pipoAttrName)) {
//     // todo (or not todo)
//   }
// }

// void
// rapidPiPoHost::getPiPoParams(std::vector<rapidPiPoParam> &params) {

// }