#ifndef RETINA_H
#define RETINA_H

#include <my_tools.h>
#include <logpolarscheme.h>

#include <retinamapfilter.h>
#include <condcellmap.h>

#include <sq_spikingchannel.h>
#include <rd_spikingchannel.h>

struct Retina;

//OK, first let us define a couple of verrry typical retinian objects:


//*************(0) UGLY: we overwrite objects LogPolarScheme, rdSpikingChannel and sqSpikingChannel,

// to create objects whose parameters are defined in retinal degrees rather than in pixels
// (case of the original objects )


struct Retinal_deg_obj
{
	virtual void degToPix(double pixPerDeg)=0;
	virtual void pixToDeg(double pixPerDeg)=0;
};


struct Retinal_LogPolarScheme : public LogPolarScheme, public Retinal_deg_obj
{
  XML_IZE_MACRO;
  double R0_deg, K_deg;
  void degToPix(double pixPerDeg);
  void pixToDeg(double pixPerDeg);
};


struct Retinal_rdSpikingChannel : public rdSpikingChannel, public Retinal_deg_obj
{
  XML_IZE_MACRO;
  Retinal_rdSpikingChannel();
  double diameter_deg, foveaDensity_deg;
  void degToPix(double pixPerDeg);
  void pixToDeg(double pixPerDeg);
};

struct Retinal_sqSpikingChannel : public sqSpikingChannel, public Retinal_deg_obj
{
  XML_IZE_MACRO;
  Retinal_sqSpikingChannel();
  double sizeX_deg, sizeY_deg, uniformDensity_deg;
  void degToPix(double pixPerDeg);
  void pixToDeg(double pixPerDeg);
};




//************* (1) Two versions of Outer Plexiform Layer ***********************

struct OPLLayers: virtual public xmlClass
{
  CImgReader *outputSignal, *receptorSignal, *horizontalSignal;
  double sigmaCenter;
  double sigmaSurround;
  double tauSurround;
  double wSurround;

  Retina* fatherRetina;

  OPLLayers(){}
  virtual ~OPLLayers(){}
  virtual void feedInput(const CImg<double>& image)=0;
  virtual void tempStep()=0;

  virtual void allocateValues(int sx, int sy, double st, bool verbose, double initValue)=0;
  CImg<double> stock_output;
};


struct LinearOPL: public OPLLayers
{
  XML_IZE_MACRO;

  double tauCenter;
  uint nCenter;
  int leakyHeatEq;
  RetinaMapFilter *excitCells;
  RetinaMapFilter *inhibCells;
  double relative_ampOPL, ampOPL;

  //In this new version, the linear Undershoot is added as a sub-field of the linear version.
  struct Undershoot : virtual public RetinaMapFilter, virtual public xmlClass
  {
	  XML_IZE_MACRO;
	  double wAdap;
	  double tauAdap;
	  Undershoot(){}
	  virtual ~Undershoot(){}
  };
  Undershoot* undershoot ;
  	  
  LinearOPL();
  ~LinearOPL();
  void feedInput(const CImg<double>& image);
  void tempStep();

  void allocateValues(int sx, int sy, double st, bool verbose, double initValue);
};


// OBSOLETE.
struct UndershootOPL: public LinearOPL
{
  XML_IZE_MACRO;

  //Three added variables, in comparison to a LinearOPL object:
  double wAdap;
  double tauAdap;
  uint adapType;

  UndershootOPL();

  RetinaMapFilter afterAdap;

  void feedInput(const CImg<double>& image);
  void tempStep();
  void allocateValues(int sx, int sy, double st, bool verbose, double initValue);
};



struct GainControlOPL: public OPLLayers
{
  XML_IZE_MACRO;

  double tauPhoto;
  double ampPhoto;
  double gLeak;
  double ampFeedback;
  double nernstFeedback;

  RetinaMapFilter phototransFilter;
  CondCellMap excitCells;
  RetinaMapFilter controlCond;

  GainControlOPL();
  ~GainControlOPL();
  void feedInput(const CImg<double>& image);
  void tempStep();

  void allocateValues(int sx, int sy, double st, bool verbose, double initValue);
};


//************************* (2) A possible Contrast Gain Control stage *********************

struct FastGainControl: virtual public xmlClass
{
  XML_IZE_MACRO;

  CImgReader *bipolarSignal, *adaptationSignal;
  double ampInputCurrent;
  double sigmaSurround;
  double tauSurround;
  double gLeak;
  double vThresh;
  double ampFeedback;

  Retina* fatherRetina;

  CondCellMap excitCells;
  RetinaMapFilter controlCond;


  FastGainControl();
  ~FastGainControl(){}
  virtual void feedInput(const CImg<double>& image);
  void tempStep();

  virtual void allocateValues(int sx, int sy, double st, bool verbose=true);

  // OPTIONAL FEATURES, CODED IN THIS OBJECT BUT ACCESIBLE ONLY THROUGH THE 'CUSTOM' VERSION

  // (1) Custom transmission from bipolar signal to adaptation conductance :
  double Einh;
  double exponent;
  uint symmetrize ;

  // (2) Optional linear low-pass on the bipolar signal (as mediated by short-range amacrine cells)
  RetinaMapFilter* inhibCells ;
  double wTransient ;
  double tauTransient ;

};

//A more experimental object:
// - decide whether ON/OFF symmetry is wanted or not,
// - can change the inhibitory potential Einh.
// - can change the synaptic transmissions to power laws.
// - can add an intrinsic linear transient to integration in bipolar cells (short range amacrine cells)

struct FastGainControl_custom: public FastGainControl
{
  XML_IZE_MACRO;
  FastGainControl_custom() : FastGainControl() {}
  ~FastGainControl_custom(){}}
;



//*************************** (3) Parvo and Magno ganglion cells layers, with or without spike generation


struct GanglionLayer:	virtual public xmlClass
{
  XML_IZE_MACRO;

  double sign;
  double vThresh;
  double valThresh;
  double ampInputCurrent;
  double tauTransient;
  double wTransient;
  double sigmaPool;

  RetinaMapFilter supplementaryTransient;
  RetinaMapFilter inputCurrent;
  BaseSpikingChannel* spikingChannel;

  Retina* fatherRetina;

  GanglionLayer();
  virtual ~GanglionLayer();
  void feedInput(const CImg<double>& image);
  void tempStep();

  virtual void allocateValues(int sx, int sy, double st, bool verbose=true);
};



//***********************  OK! Now here is class "Retina" itself :

struct Retina: virtual public xmlClass
{

  XML_IZE_MACRO;

  double step , input_luminosity_range, pixels_per_degree ;
  int sizeX, sizeY;

  Retinal_LogPolarScheme* radScheme;
  OPLLayers* outerPlexiformLayer;
  FastGainControl* contrastGainControl;
  vector<GanglionLayer*> ganglionLayers;

  CImgReader input;
  CImg<double> stockInput;

  Retina(double stepp=1);
  ~Retina();

  void feedInput(const CImg<double>& image);
  void tempStep();
  void save_output();

  void degToPix();
  int get_minimumSize();
  void adjustSizes(int imX, int imY);

  //initValue is the luminance of the uniform screen which is supposed
  // to have been `watched' by the retina for a long time, prior to simulation 
  // if initValue = HUGE_VAL, initValue is automatically taken as input_luminance_range / 2 .
  
  void allocateValues(bool verbose=true , double initValue=HUGE_VAL );
  
  bool adjusted, done;

protected:
  int adjustChannelSize( int* chanSize, int imSize);
};



#endif





