#ifndef _RAPID_PIPO_HOST_H_
#define _RAPID_PIPO_HOST_H_

#include "PiPo.h"
#include "PiPoHost.h"
#include "PiPoCollection.h"

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

#define PIPO_OUT_RING_SIZE 2

struct pipoStreamAttributes {
  pipoStreamAttributes() :
  hasTimeTags(false),
  rate(MIN_PIPO_SAMPLERATE),
  offset(0),
  labels({ "" }),
  hasVarSize(false),
  domain(0),
  maxFrames(256)
  {
      dims[0] = 1;
      dims[1] = 1;
  }

  bool hasTimeTags;
  double rate;
  double offset;
  unsigned int dims[2]; // width, height (by pipo convention)
  std::vector<std::string> labels;
  bool hasVarSize;
  double domain;
  unsigned int maxFrames;
};


//class PiPoObserver;
class PiPoOut;

//================================ H O S T ===================================//

class PiPoHost : public PiPo::Parent {
    friend class PiPoOut;
    
private:
    PiPo *graph;
    std::string graphName;
    PiPoOut *out;
    // PiPoObserver *obs;
    PiPoStreamAttributes inputStreamAttrs;
    PiPoStreamAttributes outputStreamAttrs;
    
public:
    // PiPoHost(PiPoObserver *obs);
    PiPoHost();
    ~PiPoHost();
    
    // PiPoObserver *getObserver();

    virtual bool setPiPoGraph(std::string name);
    virtual void clearPiPoGraph();

    // override this method when inheriting !!!
    virtual void onNewFrameOut(double time, std::vector<PiPoValue> &frame);
    virtual std::vector<PiPoValue> getLastFrameOut();
    
    virtual int setInputStreamAttributes(pipoStreamAttributes sa, bool propagate = true);
    virtual pipoStreamAttributes &getOutputStreamAttributes();

    virtual int frames(double time, double weight, PiPoValue *values, unsigned int size,
                       unsigned int num);

    // virtual bool setAttr(const std::string &attrName, bool value);
    // virtual bool setAttr(const std::string &attrName, int value);
    virtual bool setAttr(const std::string &attrName, double value);
    virtual bool setAttr(const std::string &attrName, const std::vector<double> &values);
    virtual bool setAttr(const std::string &attrName, const std::string &value); // for enums

    // virtual const std::vector<std::string>& getAttrNames();
    // virtual bool isBoolAttr(const std::string &attrName);
    // virtual bool isEnumAttr(const std::string &attrName);
    // virtual bool isIntAttr(const std::string &attrName);
    // virtual bool isIntArrayAttr(const std::string &attrName);
    // virtual bool isFloatAttr(const std::string &attrName);
    // virtual bool isFloatArrayAttr(const std::string &attrName);
    // virtual bool isStringAttr(const std::string &attrName);

    // virtual bool getBoolAttr(const std::string &attrName);
    // virtual int getIntAttr(const std::string &attrName);
    // virtual float getFloatAttr(const std::string &attrName);



    // int streamAttributes(bool hasTimeTags, double rate, double offset,
    //                      unsigned int width, unsigned int height,
    //                      const std::vector<std::string> &labels,
    //                      bool hasVarSize, double domain, unsigned int maxFrames,
    //                      bool propagate = true);

    // void propagateInputAttributes();
    

    // void streamAttributesChanged(PiPo *pipo, PiPo::Attr *attr);
    // void signalError(PiPo *pipo, std::string errorMsg);
    // void signalWarning(PiPo *pipo, std::string warningMsg);
    
    /*
    void setInputHasTimeTags(bool hasTimeTags, bool propagate = true);
    void setInputFrameRate(double rate, bool propagate = true);
    void setInputFrameOffset(double offset, bool propagate = true);
    void setInputDims(int width, int height, bool propagate = true);
    void setInputLabels(const std::vector<std::string> &labels, bool propagate = true);
    void setInputHasVarSize(bool hasVarSize, bool propagate = true);
    void setInputDomain(double domain, bool propagate = true);
    void setInputMaxFrames(int maxFrames, bool propagate = true);
    
    bool getInputHasTimeTags();
    double getInputFrameRate();
    double getInputFrameOffset();
    void getInputDims(int &width, int &height);
    void getInputLabels(std::vector<std::string> &labels);
    bool getInputHasVarSize();
    double getInputDomain();
    int getInputMaxFrames();
    
    bool getOutputHasTimeTags();
    double getOutputFrameRate();
    double getOutputFrameOffset();
    void getOutputDims(int &width, int &height);
    void getOutputLabels(std::vector<std::string> &labels);
    bool getOutputHasVarSize();
    double getOutputDomain();
    int getOutputMaxFrames();

    // void setPiPoParam(PiPoParam *param);
    //*/
private:
    void propagateInputStreamAttributes();
    void setOutputAttributes(bool hasTimeTags, double rate, double offset,
                             unsigned int width, unsigned int height,
                             const char **labels, bool hasVarSize,
                             double domain, unsigned int maxFrames);
    
};

//================================= PiPoOut ==================================//

 class PiPoOut : public PiPo {
private:
    PiPoHost *host;
    std::atomic<int> writeIndex, readIndex;
    std::vector<std::vector<PiPoValue>> ringBuffer;
    //std::function<void(std::vector<PiPoValue>, PiPoObserver *rpo)> frameCallback;
    std::function<void(std::vector<PiPoValue>)> simpleFrameCallback;
    
public:
    PiPoOut(PiPoHost *host) :
    PiPo((PiPo::Parent *)host) {
        this->host = host;
        writeIndex = 0;
        readIndex = 0;
        ringBuffer.resize(PIPO_OUT_RING_SIZE);
    }
    
    ~PiPoOut() {}
    
    int streamAttributes(bool hasTimeTags,
                         double rate, double offset,
                         unsigned int width, unsigned int height,
                         const char **labels, bool hasVarSize,
                         double domain, unsigned int maxFrames) {
        
        this->host->setOutputAttributes(hasTimeTags, rate, offset, width, height,
                                        labels, hasVarSize, domain, maxFrames);
        
        for (int i = 0; i < PIPO_OUT_RING_SIZE; ++i) {
            ringBuffer[i].resize(width * height);
        }
        
        return 0;
    }
    
    int frames(double time, double weight, float *values,
               unsigned int size, unsigned int num) {
        
        if (num > 0) {
            for (int i = 0; i < num; ++i) {
                
                for (int j = 0; j < size; ++j) {
                    ringBuffer[writeIndex][j] = values[i * size + j];
                }
                
                // atomic swap ?
                writeIndex = 1 - writeIndex;
                readIndex = 1 - writeIndex;
                
                this->host->onNewFrameOut(time, ringBuffer[readIndex]);
                
                if (writeIndex + 1 == PIPO_OUT_RING_SIZE) {
                    writeIndex = 0;
                } else {
                    writeIndex++;
                }
            }
        }
        
        return 0;
    }
    
    //void setFrameCallback(std::function<void(std::vector<PiPoValue>,
    //                                         PiPoObserver *obs)> f) {
    //    frameCallback = f;
    //}
    
    void setSimpleFrameCallback(std::function<void(std::vector<PiPoValue>)> f) {
        simpleFrameCallback = f;
    }
    
    std::vector<PiPoValue> getLastFrame() {
        std::vector<PiPoValue> f;
        
        if (readIndex > -1) {
            f = ringBuffer[readIndex];
        }
        
        return f;
    }
};

//================================ PARAMETER =================================//

// can we avoid using such a class ?
/*
class rapidPiPoParam {
  std::string name;
  std::string pipoName;
  std::string paramName;
  std::vector<std::string> values;

public:
  rapidPiPoParam() :
  name(""), pipoName(""), paramName("") {}

  rapidPiPoParam(std::string name, std::string pipoName, std::string paramName,
                 std::vector<std::string> const &values) {
    this->name = name;
    this->pipoName = pipoName;
    this->paramName = paramName;
    this->values = std::vector<std::string>(values.size());
    for (int i = 0; i < values.size(); ++i) {
      this->values[i] = values[i];
    }
  }

  const char *getName() {
    return name.c_str();
  }

  const char *getParamName() {
    return paramName.c_str();
  }

  const char *getPiPoName() {
    return pipoName.c_str();
  }

  int getNumValues() {
    return values.size();
  }

  bool isValueInt(int i) {
    // todo
    return false;
  }

  bool isValueFloat(int i) {
    // todo
    return false;
  } 

  bool isValueNum(int i) {
    // todo;
    return false;
  }

  float getValueFloat(int i) {
    // todo
    return 0.;
  }

  int getValueInt(int i) {
    // todo
    return 0;
  }

  const char *getValueString() {
    return values[0].c_str();
    //return values[i].c_str();
  }

  std::string getValuesAsString() {
    std::string res = values[0];
    for (int i = 1; i < values.size(); ++i) {
      res += " " + values[i];
    }
    return res;
  }
};
//*/

#endif /* _RAPID_PIPO_HOST_H_ */