/* Adaptive_Thresholding_plugin.cpp
 * Detect fiber
 * 2013-06-17 : by Zhi Zhou
 */

#include "v3d_message.h"
#include <vector>
#include "Adaptive_Thresholding_plugin.h"
#include <QtGui>
#include <fstream>
#include <iostream>
#include <math.h>
#include <stdlib.h>
#include <vector>
#include "stackutil.h"
#include <Eigen/Dense>
#include <boost/lexical_cast.hpp>

/*#define WANT_STREAM       // include iostream and iomanipulators
#include "/home/zhiz/work/v3d_external/v3d_main/jba/newmat11/newmatap.h"
#include "/home/zhiz/work/v3d_external/v3d_main/jba/newmat11/newmatio.h"
#ifdef use_namespace
using namespace RBD_LIBRARIES;
#endif
*/
using namespace Eigen;
using namespace std;
Q_EXPORT_PLUGIN2(Adaptive_Thresholding, adpThresholding);

void processImage(V3DPluginCallback2 &callback, QWidget *parent);
void processImage2(V3DPluginCallback2 &callback, QWidget *parent);
bool processImage(const V3DPluginArgList & input, V3DPluginArgList & output);
void processImage3(V3DPluginCallback2 &callback, QWidget *parent);
void processImage4(V3DPluginCallback2 &callback, QWidget *parent);
void processImage5(V3DPluginCallback2 &callback, QWidget *parent);

template <class T> void AdpThresholding(T* data1d,
                     V3DLONG *in_sz,
                     unsigned int Wx,
                     unsigned int Wy,
                     unsigned int Wz,
             unsigned int c,
             unsigned int p,
             unsigned int d,
                     T* &outimg,
		     T* gsdtdata1d);

template <class T> void AdpThresholding_adpwindow(T* data1d,
                    V3DLONG *in_sz,
                    unsigned int c,
                    T* &outimg,T* gsdtdata1d);

int swapthree(float& dummya, float& dummyb, float& dummyc);

template <class T> void AdpThresholding_adpwindow_v2(T* data1d,
                    V3DLONG *in_sz,
                    unsigned int c,
                    T* &outimg,T* gsdtdata1d,T* gsdtsoma,
                                                  int x,
                                                  int y);

int swapthree(float& dummya, float& dummyb, float& dummyc);

template <class T> void soma_detection(T* data1d,
                    V3DLONG *in_sz,
                    unsigned int c,
                    int x,
                    int y,
                    V3DLONG somasize,
                    T* &outimg);

QStringList adpThresholding::menulist() const
{
	return QStringList()
        <<tr("Enhancement_fixedWindow")
        <<tr("Enhancement_adpWindow")
        <<tr("HighlightSoma")
        <<tr("BatchAPP2")
        <<tr("Individual_score")
		<<tr("about");
}

QStringList adpThresholding::funclist() const
{
	return QStringList()
		<<tr("AdpTheresholding")
		<<tr("help");
}

void adpThresholding::domenu(const QString &menu_name, V3DPluginCallback2 &callback, QWidget *parent)
{
    if (menu_name == tr("Enhancement_fixedWindow"))
	{
		processImage(callback,parent);
	}
    else if(menu_name == tr("Enhancement_adpWindow"))
    {
       processImage2(callback,parent);

    }
    else if(menu_name == tr("HighlightSoma"))
    {
       processImage3(callback,parent);

    } else if(menu_name == tr("BatchAPP2"))
    {
       processImage4(callback,parent);

    } else if(menu_name == tr("Individual_score"))
    {
       processImage5(callback,parent);

    }
        else
	{
        (menu_name == tr("help"));
        v3d_msg(tr("Detect fiber. "
                   "Developed by Zhi Zhou, 2013-06-17"));
	}
}

bool adpThresholding::dofunc(const QString & func_name, const V3DPluginArgList & input, V3DPluginArgList & output, V3DPluginCallback2 & callback,  QWidget * parent)
{
	vector<char*> infiles, inparas, outfiles;
	if(input.size() >= 1) infiles = *((vector<char*> *)input.at(0).p);
	if(input.size() >= 2) inparas = *((vector<char*> *)input.at(1).p);
	if(output.size() >= 1) outfiles = *((vector<char*> *)output.at(0).p);

	if (func_name == tr("AdpTheresholding"))
	{
	}
	else if (func_name == tr("help"))
	{
	}
	else return false;
}

void processImage(V3DPluginCallback2 &callback, QWidget *parent)
{
    v3dhandle curwin = callback.currentImageWindow();
	if (!curwin)
	{
        QMessageBox::information(0, "", "You don't have any image open in the main window.");
		return;
	}

    Image4DSimple* p4DImage = callback.getImage(curwin);

	if (!p4DImage)
	{
		QMessageBox::information(0, "", "The image pointer is invalid. Ensure your data is valid and try again!");
		return;
	}

    unsigned char* data1d = p4DImage->getRawData();
    //V3DLONG totalpxls = p4DImage->getTotalBytes();
    V3DLONG pagesz = p4DImage->getTotalUnitNumberPerChannel();

    V3DLONG N = p4DImage->getXDim();
    V3DLONG M = p4DImage->getYDim();
    V3DLONG P = p4DImage->getZDim();
    V3DLONG sc = p4DImage->getCDim();

	//input
	bool ok1, ok2, ok3, ok4, ok5;
	unsigned int Wx=1, Wy=1, Wz=1, c=1,p=0,d=0;

	Wx = QInputDialog::getInteger(parent, "Window X ",
								  "Enter window size (# voxels) along x:",
								  1, 1, N, 1, &ok1);

	if(ok1)
	{
		Wy = QInputDialog::getInteger(parent, "Window Y",
									  "Enter window size  (# voxels) along y:",
									  1, 1, M, 1, &ok2);
	}
	else
		return;

	if(ok2)
	{
		Wz = QInputDialog::getInteger(parent, "Window Z",
									  "Enter window size  (# voxels) along z:",
									  1, 1, P, 1, &ok3);
	}
	else
		return;

	if(ok3)
	{
		c = QInputDialog::getInteger(parent, "Channel",
									 "Enter channel NO (starts from 1):",
									 1, 1, sc, 1, &ok4);
	}
	else
		return;

	if(QMessageBox::Yes == QMessageBox::question (0, "", QString("Include plane feature?"), QMessageBox::Yes, QMessageBox::No))    p = 1;
	if(QMessageBox::Yes == QMessageBox::question (0, "", QString("Include dot feature?"), QMessageBox::Yes, QMessageBox::No))    d = 1;

     // filter
     V3DLONG in_sz[4];
     in_sz[0] = N; in_sz[1] = M; in_sz[2] = P; in_sz[3] = sc;

    ImagePixelType pixeltype = p4DImage->getDatatype();
    saveImage("temp.tif", (unsigned char *)data1d, in_sz, pixeltype);

    //invoke gsdt function
    V3DPluginArgItem arg;
    V3DPluginArgList input;
    V3DPluginArgList output;

    arg.type = "random";std::vector<char*> args1; args1.push_back("temp.tif"); arg.p = (void *) & args1; input<< arg;
    arg.type = "random";std::vector<char*> args;args.push_back("150");args.push_back("1");args.push_back("0");args.push_back("1"); arg.p = (void *) & args; input << arg;
    arg.type = "random";std::vector<char*> args2; args2.push_back("gsdtImage.tiff"); arg.p = (void *) & args2; output<< arg;

    QString full_plugin_name = "gsdt";
    QString func_name = "gsdt";

    callback.callPluginFunc(full_plugin_name,func_name, input,output);

    unsigned char * gsdtdata1d = 0;
    int datatype;
    V3DLONG * in_zz = 0;

    char * outimg_file = ((vector<char*> *)(output.at(0).p))->at(0);
    loadImage(outimg_file, gsdtdata1d, in_zz, datatype,0);
    remove( "gsdtImage.tiff");
    remove("temp.tif");

    void* outimg = 0;
    switch (pixeltype)
    {
        case V3D_UINT8: AdpThresholding(data1d, in_sz, Wx, Wy, Wz,c,p,d,(unsigned char* &)outimg,gsdtdata1d); break;
        case V3D_UINT16: AdpThresholding((unsigned short int *)data1d, in_sz, Wx, Wy, Wz,c, p,d,(unsigned short int* &)outimg,(unsigned short int *)gsdtdata1d); break;
        case V3D_FLOAT32: AdpThresholding((float *)data1d, in_sz, Wx, Wy, Wz,c, p,d,(float* &)outimg,(float *)gsdtdata1d);break;
        default: v3d_msg("Invalid data type. Do nothing."); return;
    }

     // display
     Image4DSimple * new4DImage = new Image4DSimple();
     new4DImage->setData((unsigned char *)outimg, V3DLONG(N), V3DLONG(M), V3DLONG(P), 1, pixeltype);
     v3dhandle newwin = callback.newImageWindow();
     callback.setImage(newwin, new4DImage);
     callback.setImageName(newwin, "3D enhancement result");
     callback.updateImageWindow(newwin);

}

template <class T> void AdpThresholding(T* data1d,
                     V3DLONG *in_sz,
                     unsigned int Wx,
                     unsigned int Wy,
                     unsigned int Wz,
                     unsigned int c,
		     unsigned int p,
		     unsigned int d,
                     T* &outimg,
		     T* gsdtdatald)
{

	     V3DLONG N = in_sz[0];
	     V3DLONG M = in_sz[1];
	     V3DLONG P = in_sz[2];
	     V3DLONG sc = in_sz[3];
	     V3DLONG pagesz = N*M*P;
         T *pImage = new T [pagesz];
		if (!pImage)
		{
			printf("Fail to allocate memory.\n");
			return;
		 }
		 else
		 {
            for(V3DLONG i=0; i<pagesz; i++)
				pImage[i] = 0;
		  }

          T maxfl = 0;
		 //outimg = pImage;

          for(V3DLONG iz = 0; iz < P; iz++)
          {
              printf("\r Enhancement : %d %% completed ", int(float(iz)/P*100)); fflush(stdout);
              V3DLONG offsetk = iz*M*N;
              for(V3DLONG iy = 0; iy < M; iy++)
              {
                  V3DLONG offsetj = iy*N;
                  for(V3DLONG ix = 0; ix < N; ix++)
                  {
                    if(gsdtdatald[offsetk+offsetj+ix]< 80)
					{
                        V3DLONG xb = ix-Wx; if(xb<0) xb = 0;
                        V3DLONG xe = ix+Wx; if(xe>=N-1) xe = N-1;
                        V3DLONG yb = iy-Wy; if(yb<0) yb = 0;
                        V3DLONG ye = iy+Wy; if(ye>=M-1) ye = M-1;
                        V3DLONG zb = iz-Wz; if(zb<0) zb = 0;
                        V3DLONG ze = iz+Wz; if(ze>=P-1) ze = P-1;

                       // printf("window size is %d\n",Wx);
                        //Seletive approach
                        float  fxx = data1d[offsetk+offsetj+xe]+ data1d[offsetk+offsetj+xb]- 2*data1d[offsetk+offsetj+ix];
                        float fyy = data1d[offsetk+(ye)*N+ix]+data1d[offsetk+(yb)*N+ix]-2*data1d[offsetk+offsetj+ix];
                        float fzz = data1d[(ze)*M*N+offsetj+ix]+data1d[(zb)*M*N+offsetj+ix]- 2*data1d[offsetk+offsetj+ix];

                        float fxy = 0.25*(data1d[offsetk+(ye)*N+xe]+data1d[offsetk+(yb)*N+xb]-data1d[offsetk+(ye)*N+xb]-data1d[offsetk+(yb)*N+xe]);
                        float fxz = 0.25*(data1d[(ze)*M*N+offsetj+xe]+data1d[(zb)*M*N+offsetj+xb]-data1d[(ze)*M*N+offsetj+xb]-data1d[(zb)*M*N+offsetj+xe]);
                        float fyz = 0.25*(data1d[(ze)*M*N+(ye)*N+ix]+data1d[(zb)*M*N+(yb)*N+ix]-data1d[(ze)*M*N+(yb)*N+ix]-data1d[(zb)*M*N+(ye)*N+ix]);

	 				 	Matrix3f A;
						A << fxx,fxy,fxz,fxy,fyy,fyz,fxz,fyz,fzz;
	  					SelfAdjointEigenSolver<Matrix3f> eigensolver(A, false);
	       				//	if (eigensolver.info() != Success) abort();
						/*SymmetricMatrix Cov_Matrix(3);
						Cov_Matrix.Row(1) << fxx;
						Cov_Matrix.Row(2) << fxy << fyy;
						Cov_Matrix.Row(3) << fxz << fyz <<fzz;

						DiagonalMatrix DD;
						//cout << "Matrix" << endl;
						//cout << Cov_Matrix << endl <<endl;

						EigenValues(Cov_Matrix,DD);
						//cout << "EigenValues" << endl;
						//cout <<  DD << endl <<endl;*/

                        float a1 = eigensolver.eigenvalues()(0);
		   				float a2 = eigensolver.eigenvalues()(1);
						float a3 = eigensolver.eigenvalues()(2);
						swapthree(a1, a2, a3);
					//mak	printf("%f,%f,%f,%f,%f,%f,%f,%f,%f\n\n\n\n",fxx,fyy,fzz,fxy,fxz,fyz,a1,a2,a3);
						float output1 = 0;
						float output2 = 0;
						float output3 = 0;
						if(a1<0)
						{

							if (p ==1) output3 = abs(a1) - abs(a2);
							if(a2 < 0)
							{
								//pImage[offsetk+offsetj+ix] = abs(a2)*(abs(a2)-abs(a3))/abs(a1);
                                output1 =  abs(a2)*(abs(a2)-abs(a3))/abs(a1);
								//T output = abs(a1)/abs(a2);

								//printf("%f %f %f %d\n", a1,a2,a3,output1);
								//if(ix ==96 && iy == 37 && iz == 6)
								if(a3 < 0 && d ==1) output2 = (abs(a3),2)/abs(a1);

							}
						}

                        T dataval = sqrt(pow(output1,2)+pow(output2,2)+pow(output3,2));
                        pImage[offsetk+offsetj+ix] = dataval;
                        if(maxfl<dataval) maxfl = dataval;

                    }
;
				}

			}

		}

        T *pImage2 = new T [pagesz];
       if (!pImage2)
       {
           printf("Fail to allocate memory.\n");
           return;
        }
        else
        {
           for(V3DLONG i=0; i<pagesz; i++)
               pImage2[i] = 0;
         }
       for(V3DLONG iz = 0; iz < P; iz++)
        {
            V3DLONG offsetk = iz*M*N;
            for(V3DLONG iy = 0; iy < M; iy++)
            {
                V3DLONG offsetj = iy*N;
                for(V3DLONG ix = 0; ix < N; ix++)
                {
                    if(gsdtdatald[offsetk+offsetj+ix]< 80)
                    {

                        T dataval2 = 255*pImage[offsetk+offsetj+ix]/maxfl;
                        pImage2[offsetk+offsetj+ix] = dataval2;
                    }
                   else
                    pImage2[offsetk+offsetj+ix] = data1d[offsetk+offsetj+ix];
                }

            }

        }
        outimg = pImage2;

}

void processImage2(V3DPluginCallback2 &callback, QWidget *parent)
{
    v3dhandle curwin = callback.currentImageWindow();
    if (!curwin)
    {
        QMessageBox::information(0, "", "You don't have any image open in the main window.");
        return;
    }

    Image4DSimple* p4DImage = callback.getImage(curwin);

    if (!p4DImage)
    {
        QMessageBox::information(0, "", "The image pointer is invalid. Ensure your data is valid and try again!");
        return;
    }

    unsigned char* data1d = p4DImage->getRawData();
    //V3DLONG totalpxls = p4DImage->getTotalBytes();
    V3DLONG pagesz = p4DImage->getTotalUnitNumberPerChannel();

    V3DLONG N = p4DImage->getXDim();
    V3DLONG M = p4DImage->getYDim();
    V3DLONG P = p4DImage->getZDim();
    V3DLONG sc = p4DImage->getCDim();

    V3DLONG in_sz[4];
    in_sz[0] = N; in_sz[1] = M; in_sz[2] = P; in_sz[3] = sc;

    ImagePixelType pixeltype = p4DImage->getDatatype();

    AdaptiveWindowDialog dialog(callback, parent);
    if (!dialog.image)
        return;

    if (dialog.exec()!=QDialog::Accepted)
        return;

    dialog.update();

    Image4DSimple* subject = dialog.image;
    if (!subject)
        return;
    ROIList pRoiList = dialog.pRoiList;

    int c = dialog.ch+1;
    int th_idx = dialog.th_idx;
    double th = dialog.thresh;

   if(th_idx ==0)
    {
        V3DLONG offsetc = (c-1)*pagesz;
        for(V3DLONG iz = 0; iz < P; iz++)
        {
            double PixelSum = 0;
            V3DLONG offsetk = iz*M*N;
            for(V3DLONG iy = 0; iy < M; iy++)
            {
                V3DLONG offsetj = iy*N;
                for(V3DLONG ix = 0; ix < N; ix++)
                {

                double PixelVaule = data1d[offsetc + offsetk + offsetj + ix];
               // filter
                PixelSum = PixelSum + PixelVaule;

                }
            }
            th = th + PixelSum/(M*N*P);
        }

    }

    // filter

    saveImage("temp.v3draw", (unsigned char *)data1d, in_sz, pixeltype);
    //invoke gsdt function
    V3DPluginArgItem arg;
    V3DPluginArgList input;
    V3DPluginArgList output;

    arg.type = "random";std::vector<char*> args1;
   // std:: string inputName(callback.getImageName(curwin).toStdString());char* inputName2 =  new char[inputName.length() + 1]; strcpy(inputName2, inputName.c_str());
    args1.push_back("temp.v3draw"); arg.p = (void *) & args1; input<< arg;
    arg.type = "random";std::vector<char*> args;
    char channel = '0' + (c-1);
    string threshold = boost::lexical_cast<string>(th); char* threshold2 =  new char[threshold.length() + 1]; strcpy(threshold2, threshold.c_str());
    args.push_back(threshold2);args.push_back("1");args.push_back(&channel);args.push_back("1"); arg.p = (void *) & args; input << arg;
    arg.type = "random";std::vector<char*> args2;args2.push_back("gsdtImage.v3draw"); arg.p = (void *) & args2; output<< arg;

    QString full_plugin_name = "gsdt";
    QString func_name = "gsdt";

    callback.callPluginFunc(full_plugin_name,func_name, input,output);

    unsigned char * gsdtdata1d = 0;
    int datatype;
    V3DLONG * in_zz = 0;

    char * outimg_file = ((vector<char*> *)(output.at(0).p))->at(0);
    loadImage(outimg_file, gsdtdata1d, in_zz, datatype,0);
    remove("temp.v3draw");
    remove("gsdtImage.v3draw");

    void* outimg = 0;
    switch (pixeltype)
    {
    case V3D_UINT8: AdpThresholding_adpwindow(data1d, in_sz, c,(unsigned char* &)outimg, gsdtdata1d); break;
    case V3D_UINT16: AdpThresholding_adpwindow((unsigned short int *)data1d, in_sz, c, (unsigned short int* &)outimg,(unsigned short int *)gsdtdata1d); break;
    case V3D_FLOAT32: AdpThresholding_adpwindow((float *)data1d, in_sz, c, (float* &)outimg,(float *)gsdtdata1d);break;
    default: v3d_msg("Invalid data type. Do nothing."); return;
    }

    // display
    Image4DSimple * new4DImage = new Image4DSimple();
    new4DImage->setData((unsigned char *)outimg, N, M, P, 1, pixeltype);
    //new4DImage->setData((unsigned char *)gsdtdata1d, 400, 400, P, 1, pixeltype);
    v3dhandle newwin = callback.newImageWindow();
    callback.setImage(newwin, new4DImage);
    callback.setImageName(newwin, "3D adaptive enhancement result");
    callback.updateImageWindow(newwin);

    return;
}

template <class T> void AdpThresholding_adpwindow(T* data1d,
                                                  V3DLONG *in_sz,
                                                  unsigned int c,
                                                  T* &outimg,
                                                  T* gsdtdatald)
{

    V3DLONG N = in_sz[0];
    V3DLONG M = in_sz[1];
    V3DLONG P = in_sz[2];
    V3DLONG sc = in_sz[3];
    V3DLONG pagesz = N*M*P;
    T *pImage = new T [pagesz];
   if (!pImage)
   {
       printf("Fail to allocate memory.\n");
       return;
    }
    else
    {
       for(V3DLONG i=0; i<pagesz; i++)
           pImage[i] = 0;
     }

     T maxfl = 0;
    //outimg = pImage;
     int Wx = 2;
     int Wy =2;
     int Wz = 4;
     int p=0;
     int d = 0;
     for(V3DLONG iz = 0; iz < P; iz++)
     {
         printf("\r Enhancement : %d %% completed ", int(float(iz)/P*100)); fflush(stdout);
         V3DLONG offsetk = iz*M*N;
         for(V3DLONG iy = 0; iy < M; iy++)
         {
             V3DLONG offsetj = iy*N;
             for(V3DLONG ix = 0; ix < N; ix++)
             {
               if(gsdtdatald[offsetk+offsetj+ix]< 80)
               {
                   V3DLONG xb = ix-Wx; if(xb<0) xb = 0;
                   V3DLONG xe = ix+Wx; if(xe>=N-1) xe = N-1;
                   V3DLONG yb = iy-Wy; if(yb<0) yb = 0;
                   V3DLONG ye = iy+Wy; if(ye>=M-1) ye = M-1;
                   V3DLONG zb = iz-Wz; if(zb<0) zb = 0;
                   V3DLONG ze = iz+Wz; if(ze>=P-1) ze = P-1;

                  // printf("window size is %d\n",Wx);
                   //Seletive approach
                   float  fxx = data1d[offsetk+offsetj+xe]+ data1d[offsetk+offsetj+xb]- 2*data1d[offsetk+offsetj+ix];
                   float fyy = data1d[offsetk+(ye)*N+ix]+data1d[offsetk+(yb)*N+ix]-2*data1d[offsetk+offsetj+ix];
                   float fzz = data1d[(ze)*M*N+offsetj+ix]+data1d[(zb)*M*N+offsetj+ix]- 2*data1d[offsetk+offsetj+ix];

                   float fxy = 0.25*(data1d[offsetk+(ye)*N+xe]+data1d[offsetk+(yb)*N+xb]-data1d[offsetk+(ye)*N+xb]-data1d[offsetk+(yb)*N+xe]);
                   float fxz = 0.25*(data1d[(ze)*M*N+offsetj+xe]+data1d[(zb)*M*N+offsetj+xb]-data1d[(ze)*M*N+offsetj+xb]-data1d[(zb)*M*N+offsetj+xe]);
                   float fyz = 0.25*(data1d[(ze)*M*N+(ye)*N+ix]+data1d[(zb)*M*N+(yb)*N+ix]-data1d[(ze)*M*N+(yb)*N+ix]-data1d[(zb)*M*N+(ye)*N+ix]);

                   Matrix3f A;
                   A << fxx,fxy,fxz,fxy,fyy,fyz,fxz,fyz,fzz;
                   SelfAdjointEigenSolver<Matrix3f> eigensolver(A, false);
                   //	if (eigensolver.info() != Success) abort();
                   /*SymmetricMatrix Cov_Matrix(3);
                   Cov_Matrix.Row(1) << fxx;
                   Cov_Matrix.Row(2) << fxy << fyy;
                   Cov_Matrix.Row(3) << fxz << fyz <<fzz;

                   DiagonalMatrix DD;
                   //cout << "Matrix" << endl;
                   //cout << Cov_Matrix << endl <<endl;

                   EigenValues(Cov_Matrix,DD);
                   //cout << "EigenValues" << endl;
                   //cout <<  DD << endl <<endl;*/

                   float a1 = eigensolver.eigenvalues()(0);
                   float a2 = eigensolver.eigenvalues()(1);
                   float a3 = eigensolver.eigenvalues()(2);
                   swapthree(a1, a2, a3);
               //mak	printf("%f,%f,%f,%f,%f,%f,%f,%f,%f\n\n\n\n",fxx,fyy,fzz,fxy,fxz,fyz,a1,a2,a3);
                   float output1 = 0;
                   float output2 = 0;
                   float output3 = 0;
                   if(a1<0)
                   {

                       if (p ==1) output3 = abs(a1) - abs(a2);
                       if(a2 < 0)
                       {
                           //pImage[offsetk+offsetj+ix] = abs(a2)*(abs(a2)-abs(a3))/abs(a1);
                           output1 =  abs(a2)*(abs(a2)-abs(a3))/abs(a1);
                           //T output = abs(a1)/abs(a2);

                           //printf("%f %f %f %d\n", a1,a2,a3,output1);
                           //if(ix ==96 && iy == 37 && iz == 6)
                           if(a3 < 0 && d ==1) output2 = (abs(a3),2)/abs(a1);

                       }
                   }

                   T dataval = sqrt(pow(output1,2)+pow(output2,2)+pow(output3,2));
                   pImage[offsetk+offsetj+ix] = dataval;
                   if(maxfl<dataval) maxfl = dataval;

               }
;
           }

       }

   }

       T *pImage2 = new T [pagesz];
       if (!pImage2)
       {
           printf("Fail to allocate memory.\n");
           return;
        }
        else
        {
           for(V3DLONG i=0; i<pagesz; i++)
               pImage2[i] = 0;
         }
       for(V3DLONG iz = 0; iz < P; iz++)
        {
            V3DLONG offsetk = iz*M*N;
            for(V3DLONG iy = 0; iy < M; iy++)
            {
                V3DLONG offsetj = iy*N;
                for(V3DLONG ix = 0; ix < N; ix++)
                {

                            T dataval2 = 255*pImage[offsetk+offsetj+ix]/maxfl;
                            pImage2[offsetk+offsetj+ix] = dataval2;

                }

            }

        }
        outimg = pImage2;
        return;

}
int swapthree(float& dummya, float& dummyb, float& dummyc)
{

    if ( (abs(dummya) >= abs(dummyb)) && (abs(dummyb) >= abs(dummyc)) )
    {
        return 1;
    }

    else if ( (abs(dummya) >= abs(dummyc)) && (abs(dummyc) >= abs(dummyb)) )

    {
        float temp = dummyb;
        dummyb = dummyc;
        dummyc = temp;

        return 1;
    }

    else if ( (abs(dummyb) >= abs(dummya)) && (abs(dummya) >= abs(dummyc)) )
    {
        float temp = dummya;
        dummya = dummyb;
        dummyb = temp;

        return 1;
    }

    else if ( (abs(dummyb) >= abs(dummyc)) && (abs(dummyc) >= abs(dummya)) )
    {
        float temp = dummya;
        dummya = dummyb;
        dummyb = dummyc;
        dummyc = temp;

        return 1;
    }

    else if ( (abs(dummyc) >= abs(dummya)) && (abs(dummya) >= abs(dummyb)) )
    {
        float temp = dummya;
        dummya = dummyc;
        dummyc = dummyb;
        dummyb = temp;

        return 1;
    }

    else if ( (abs(dummyc) >= abs(dummyb)) && (abs(dummyb) >= abs(dummya)) )
    {
        float temp = dummyc;
        dummyc = dummya;
        dummya = temp;

        return 1;
    }

    else
       {
           return 0;
       }

}

template <class T> void soma_detection(T* data1d,
                                       V3DLONG *in_sz,
                                       unsigned int c,
                                       int x,
                                       int y,
                                       V3DLONG somasize,
                                       T* &outimg)

{

         V3DLONG N = in_sz[0];
         V3DLONG M = in_sz[1];
         V3DLONG P = in_sz[2];
         V3DLONG pagesz = N*M*P;
         V3DLONG offsetc = (c-1)*pagesz;

         T *pImage = new T [somasize];
        if (!pImage)
        {
            printf("Fail to allocate memory.\n");
            return;
         }
         else
         {
            int i = 0;
            for(V3DLONG iz = 0; iz < P; iz++)
            {
                V3DLONG offsetk = iz*M*N;
                for(V3DLONG iy = y-200; iy < y+200; iy++)
                {
                    V3DLONG offsetj = iy*N;
                    for(V3DLONG ix = x-200; ix < x+200; ix++)
                    {

                        T PixelValue = data1d[offsetc+offsetk + offsetj + ix];
                        pImage[i] = PixelValue;
                        i++;
                    }
                }
            }
        }

        outimg = pImage;
       //if(pImage) {delete [] pImage; pImage = 0;}
}

void processImage3(V3DPluginCallback2 &callback, QWidget *parent)
{
    v3dhandle curwin = callback.currentImageWindow();
    if (!curwin)
    {
        QMessageBox::information(0, "", "You don't have any image open in the main window.");
        return;
    }

    Image4DSimple* p4DImage = callback.getImage(curwin);

    if (!p4DImage)
    {
        QMessageBox::information(0, "", "The image pointer is invalid. Ensure your data is valid and try again!");
        return;
    }

    unsigned char* data1d = p4DImage->getRawData();
    //V3DLONG totalpxls = p4DImage->getTotalBytes();
    V3DLONG pagesz = p4DImage->getTotalUnitNumberPerChannel();

    V3DLONG N = p4DImage->getXDim();
    V3DLONG M = p4DImage->getYDim();
    V3DLONG P = p4DImage->getZDim();
    V3DLONG sc = p4DImage->getCDim();

    V3DLONG in_sz[4];
    in_sz[0] = N; in_sz[1] = M; in_sz[2] = P; in_sz[3] = sc;

    ImagePixelType pixeltype = p4DImage->getDatatype();
    int c = 1;
    unsigned char * gsdtdata1d = 0;
    int datatype;
    V3DLONG * in_zz = 0;
    loadImage("/local1/data/Staci/L2-3_PyramidalNeuron_2/Cropped_results/Crop1/adaptive_enhancement_result.v3draw", gsdtdata1d, in_zz, datatype,0);

    //find local gsdt for soma area

    V3DLONG offsetc = (c-1)*pagesz;
    int soma_x,soma_y;//2901;//2173;
    int soma_z = 0;//2178;//1926;
    LandmarkList soma_center = callback.getLandmark(curwin);
    LocationSimple tmpLocation(0,0,0);
    tmpLocation = soma_center.at(0);
    tmpLocation.getCoord(soma_x,soma_y,soma_z);

    printf("Soma is at (%d %d %d)\n\n",soma_x,soma_y,soma_z);

    V3DLONG i = 0;
    double th_soma = 0;

    for(V3DLONG iz = 0; iz < P; iz++)
    {
        double PixelSum = 0;
        V3DLONG offsetk = iz*M*N;
        for(V3DLONG iy = soma_y-200; iy <  soma_y+200; iy++)
        {
            V3DLONG offsetj = iy*N;
            for(V3DLONG ix = soma_x-200; ix < soma_x+200; ix++)
            {

             double PixelVaule = data1d[offsetc + offsetk + offsetj + ix];
             PixelSum = PixelSum + PixelVaule;
             i++;
            }
        }
        th_soma = th_soma + PixelSum/(400*400*P);
    }

  //  printf("threshold is %d %d %d\n\n",th_soma,PixelSum,i);

    void* somaarea = 0;
     switch (pixeltype)
     {
     case V3D_UINT8: soma_detection(data1d, in_sz, c,soma_x,soma_y,i,(unsigned char* &)somaarea); break;
     case V3D_UINT16: soma_detection((unsigned short int *)data1d, in_sz, c, soma_x,soma_y,i,(unsigned short int* &)somaarea); break;
     case V3D_FLOAT32: soma_detection((float *)data1d, in_sz, c, soma_x,soma_y,i,(float* &)somaarea);break;
     default: v3d_msg("Invalid data type. Do nothing."); return;
     }
     V3DLONG soma_sz[4];
     soma_sz[0] = 400; soma_sz[1] = 400; soma_sz[2] = P; soma_sz[3] = sc;
     saveImage("temp.v3draw", (unsigned char *)somaarea, soma_sz, pixeltype);
     V3DPluginArgItem arg;
     V3DPluginArgList input;
     V3DPluginArgList output;

     arg.type = "random";std::vector<char*> args1;
     args1.push_back("temp.v3draw"); arg.p = (void *) & args1; input<< arg;
     arg.type = "random";std::vector<char*> args;
     char channel = '0' + (c-1);
     string threshold = boost::lexical_cast<string>(th_soma); char* threshold2 =  new char[threshold.length() + 1]; strcpy(threshold2, threshold.c_str());
     args.push_back(threshold2);args.push_back("1");args.push_back(&channel);args.push_back("1"); arg.p = (void *) & args; input << arg;
     arg.type = "random";std::vector<char*> args2;args2.push_back("gsdtImage.v3draw"); arg.p = (void *) & args2; output<< arg;

     QString full_plugin_name = "gsdt";
     QString func_name = "gsdt";

     callback.callPluginFunc(full_plugin_name,func_name, input,output);

     unsigned char * gsdtsoma = 0;
    // int datatype;
    // V3DLONG * in_zz = 0;

     char * outimg_file = ((vector<char*> *)(output.at(0).p))->at(0);
     loadImage(outimg_file, gsdtsoma, in_zz, datatype,0);
     remove("temp.v3draw");
     remove("gsdtImage.v3draw");

    void* outimg = 0;
    switch (pixeltype)
    {
    case V3D_UINT8: AdpThresholding_adpwindow_v2(data1d, in_sz, c,(unsigned char* &)outimg, gsdtdata1d,gsdtsoma,soma_x,soma_y); break;
    case V3D_UINT16: AdpThresholding_adpwindow_v2((unsigned short int *)data1d, in_sz, c, (unsigned short int* &)outimg,(unsigned short int *)gsdtdata1d,(unsigned short int *)gsdtsoma,soma_x,soma_y); break;
    case V3D_FLOAT32: AdpThresholding_adpwindow_v2((float *)data1d, in_sz, c, (float* &)outimg,(float *)gsdtdata1d,(float *)gsdtsoma,soma_x,soma_y);break;
    default: v3d_msg("Invalid data type. Do nothing."); return;
    }

    // display
    Image4DSimple * new4DImage = new Image4DSimple();
    new4DImage->setData((unsigned char *)outimg, N, M, P, 1, pixeltype);
   // new4DImage->setData((unsigned char *)gsdtdata1d, N, M, P, 1, pixeltype);
    v3dhandle newwin = callback.newImageWindow();
    callback.setImage(newwin, new4DImage);
    callback.setImageName(newwin, "3D adaptive enhancement result");
    callback.updateImageWindow(newwin);

    return;
}

template <class T> void AdpThresholding_adpwindow_v2(T* data1d,
                                                  V3DLONG *in_sz,
                                                  unsigned int c,
                                                  T* &outimg,
                                                  T* gsdtdatald,T* gsdtsoma,
                                                  int x,
                                                  int y)
{

         V3DLONG N = in_sz[0];
         V3DLONG M = in_sz[1];
         V3DLONG P = in_sz[2];
         V3DLONG sc = in_sz[3];
         V3DLONG pagesz = N*M*P;
         V3DLONG offsetc = (c-1)*pagesz;

         int Th_gsdt = 150;

        T *pSoma = new T [pagesz];
        if (!pSoma)
       {
           printf("Fail to allocate memory.\n");
           return;
        }
        else
        {
           for(V3DLONG i=0; i<pagesz; i++)
               pSoma[i] = 0;
         }
       int i = 0;
        for(V3DLONG iz = 0; iz < P; iz++)
        {
            V3DLONG offsetk = iz*M*N;
            for(V3DLONG iy = y-200; iy <  y+200; iy++)
            {
                V3DLONG offsetj = iy*N;
                for(V3DLONG ix = x-200; ix < x+200; ix++)
                {
                    pSoma[offsetk + offsetj + ix] = gsdtsoma[i];
                    i++;
                }
            }
        }

       T *pImage2 = new T [pagesz];
       if (!pImage2)
       {
           printf("Fail to allocate memory.\n");
           return;
        }
        else
        {
           for(V3DLONG i=0; i<pagesz; i++)
               pImage2[i] = 0;
         }
           for(V3DLONG iz = 0; iz < P; iz++)
            {
                printf("zhi zhou is %d %d %d %d\n",iz, P, M , N);
                V3DLONG offsetk = iz*M*N;
                for(V3DLONG iy = 0; iy < M; iy++)
                {
                    V3DLONG offsetj = iy*N;
                    for(V3DLONG ix = 0; ix < N; ix++)
                    {
                            T SomaValue = pSoma[offsetc+offsetk + offsetj + ix];

                            if(SomaValue < Th_gsdt)
                            {
                                T dataval2 = gsdtdatald[offsetk+offsetj+ix];
                                pImage2[offsetk+offsetj+ix] = dataval2;
                            }else
                                pImage2[offsetk+offsetj+ix] =  data1d[offsetk+offsetj+ix];
                    }

                }

            }

        outimg = pImage2;
        return;

}

void processImage4(V3DPluginCallback2 &callback, QWidget *parent)
{
    v3dhandle curwin = callback.currentImageWindow();
    if (!curwin)
    {
        QMessageBox::information(0, "", "You don't have any image open in the main window.");
        return;
    }

    Image4DSimple* p4DImage = callback.getImage(curwin);

    if (!p4DImage)
    {
        QMessageBox::information(0, "", "The image pointer is invalid. Ensure your data is valid and try again!");
        return;
    }

    //invoke app2 function
    V3DPluginArgItem arg;
    V3DPluginArgList input;
    V3DPluginArgList output;

    for(int th = 88; th<120;th = th+5)
    {

        arg.type = "random";std::vector<char*> args1;
        std:: string inputName(callback.getImageName(curwin).toStdString());char* inputName2 =  new char[inputName.length() + 1]; strcpy(inputName2, inputName.c_str());
        args1.push_back(inputName2); arg.p = (void *) & args1; input<< arg;
        arg.type = "random";std::vector<char*> args;
        char channel = '0';
        string threshold = boost::lexical_cast<string>(th); char* threshold2 =  new char[threshold.length() + 1]; strcpy(threshold2, threshold.c_str());
        args.push_back("NULL");args.push_back(&channel);args.push_back(threshold2);arg.p = (void *) & args; input << arg;
        arg.type = "random";std::vector<char*> args2;args2.push_back("NULL"); arg.p = (void *) & args2; output<< arg;

        QString full_plugin_name = "Neuron2";
        QString func_name = "app2";

        callback.callPluginFunc(full_plugin_name,func_name, input,output);
    }

    return;
}

void processImage5(V3DPluginCallback2 &callback, QWidget *parent)
{
    v3dhandle curwin = callback.currentImageWindow();
    if (!curwin)
    {
        QMessageBox::information(0, "", "You don't have any image open in the main window.");
        return;
    }

    Image4DSimple* p4DImage = callback.getImage(curwin);

    if (!p4DImage)
    {
        QMessageBox::information(0, "", "The image pointer is invalid. Ensure your data is valid and try again!");
        return;
    }

    unsigned char* data1d = p4DImage->getRawData();
    //V3DLONG totalpxls = p4DImage->getTotalBytes();
    V3DLONG pagesz = p4DImage->getTotalUnitNumberPerChannel();

    V3DLONG N = p4DImage->getXDim();
    V3DLONG M = p4DImage->getYDim();
    V3DLONG P = p4DImage->getZDim();
    V3DLONG sc = p4DImage->getCDim();

    int tmpx,tmpy,tmpz;
    LandmarkList listLandmarks = callback.getLandmark(curwin);
    LocationSimple tmpLocation(0,0,0);
    int marknum = listLandmarks.count();
    for (int i=0;i<marknum;i++)
    {
        tmpLocation = listLandmarks.at(i);
        tmpLocation.getCoord(tmpx,tmpy,tmpz);
        V3DLONG ix = tmpx-1;
        V3DLONG iy = tmpy-1;
        V3DLONG iz = tmpz-1;
        V3DLONG offsetk = iz*M*N;
        V3DLONG offsetj = iy*N;

        for(int ws = 1; ws <15; ws++)
        {

                V3DLONG xb = ix-ws; if(xb<0) xb = 0;
                V3DLONG xe = ix+ws; if(xe>=N-1) xe = N-1;
                V3DLONG yb = iy-ws; if(yb<0) yb = 0;
                V3DLONG ye = iy+ws; if(ye>=M-1) ye = M-1;
                V3DLONG zb = iz-ws; if(zb<0) zb = 0;
                V3DLONG ze = iz+ws; if(ze>=P-1) ze = P-1;

                 // printf("window size is %d\n",Wx);
                  //Seletive approach
                  float  fxx = data1d[offsetk+offsetj+xe]+ data1d[offsetk+offsetj+xb]- 2*data1d[offsetk+offsetj+ix];
                  float fyy = data1d[offsetk+(ye)*N+ix]+data1d[offsetk+(yb)*N+ix]-2*data1d[offsetk+offsetj+ix];
                  float fzz = data1d[(ze)*M*N+offsetj+ix]+data1d[(zb)*M*N+offsetj+ix]- 2*data1d[offsetk+offsetj+ix];

                  float fxy = 0.25*(data1d[offsetk+(ye)*N+xe]+data1d[offsetk+(yb)*N+xb]-data1d[offsetk+(ye)*N+xb]-data1d[offsetk+(yb)*N+xe]);
                  float fxz = 0.25*(data1d[(ze)*M*N+offsetj+xe]+data1d[(zb)*M*N+offsetj+xb]-data1d[(ze)*M*N+offsetj+xb]-data1d[(zb)*M*N+offsetj+xe]);
                  float fyz = 0.25*(data1d[(ze)*M*N+(ye)*N+ix]+data1d[(zb)*M*N+(yb)*N+ix]-data1d[(ze)*M*N+(yb)*N+ix]-data1d[(zb)*M*N+(ye)*N+ix]);

                  Matrix3f A;
                  A << fxx,fxy,fxz,fxy,fyy,fyz,fxz,fyz,fzz;
                  SelfAdjointEigenSolver<Matrix3f> eigensolver(A, false);

                  float a1 = eigensolver.eigenvalues()(0);
                  float a2 = eigensolver.eigenvalues()(1);
                  float a3 = eigensolver.eigenvalues()(2);
                  swapthree(a1, a2, a3);
                  float output1 =  pow((abs(a2)-abs(a3)),3)/abs(a1);
                  printf("%f,%f,%f,%f,%d\n",a1,a2,a3,output1,ws);
         }
         printf("\n\n\n");
      }
}


// Copyright 2007-2021 Jan Niklas Hasse <jhasse@bixense.com>
// For conditions of distribution and use, see copyright notice in LICENSE.txt

#include "main.hpp"

#include "jngl.hpp"
#include "paths.hpp"
#include "spriteimpl.hpp"

#include <boost/math/special_functions/round.hpp>
#include <boost/qvm/map_vec_mat.hpp>
#include <boost/qvm/mat_operations.hpp>
#include <boost/qvm/mat_operations3.hpp>
#include <boost/qvm/vec.hpp>
#include <fstream>
#include <sstream>

#ifdef _WIN32
#include <windows.h>
#endif

#if defined(_WIN32) || (!defined(ANDROID) && __has_include(<filesystem>))
#include <filesystem>
#define HAVE_FILESYSTEM
#endif

#ifdef JNGL_UWP
#include <boost/algorithm/string.hpp>
#endif

#ifdef __APPLE__
#include <TargetConditionals.h>
#endif

#ifdef ANDROID
#include "android/fopen.hpp"
#endif

namespace jngl {

std::string pathPrefix;
optional<std::string> configPath;
std::vector<std::string> args;
float bgRed = 1.0f, bgGreen = 1.0f, bgBlue = 1.0f; // Background Colors
std::stack<jngl::Mat3> modelviewStack;
std::unique_ptr<ShaderProgram> simpleShaderProgram;
int simpleModelviewUniform;
int simpleColorUniform;

void clearBackgroundColor() {
	glClearColor(bgRed, bgGreen, bgBlue, 1);
}

#if defined(GL_DEBUG_OUTPUT) && !defined(NDEBUG)
#ifdef _WIN32
void __stdcall
#else
void
#endif
debugCallback(GLenum /*source*/, GLenum /*type*/, GLuint /*id*/, GLenum severity,
              GLsizei /*length*/, const GLchar* message, const void* /*userParam*/) {
	if (severity == GL_DEBUG_SEVERITY_HIGH) {
		jngl::debugLn(std::string("\x1b[1;31m") + message + "\x1b[0m");
	} else if (severity != GL_DEBUG_SEVERITY_NOTIFICATION) {
		jngl::debugLn(message);
	}
}
#endif

bool Init(const int width, const int height, const int canvasWidth, const int canvasHeight) {
#if defined(GL_DEBUG_OUTPUT) && !defined(NDEBUG)
#ifdef EPOXY_PUBLIC
	if (epoxy_gl_version() >= 43 || epoxy_has_gl_extension("GL_KHR_debug")) {
#endif
		glEnable(GL_DEBUG_OUTPUT);
		glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
		glDebugMessageCallback(reinterpret_cast<GLDEBUGPROC>(debugCallback), nullptr); // NOLINT
#ifdef EPOXY_PUBLIC
	}
#endif
#endif

	updateProjection(width, height, width, height);

	Shader vertexShader(R"(#version 300 es
		in mediump vec2 position;
		uniform highp mat3 modelview;
		uniform mediump mat4 projection;

		void main() {
			vec3 tmp = modelview * vec3(position, 1);
			gl_Position = projection * vec4(tmp.x, tmp.y, 0, 1);
		})", Shader::Type::VERTEX, R"(#version 100
		attribute mediump vec2 position;
		uniform highp mat3 modelview;
		uniform mediump mat4 projection;

		void main() {
			vec3 tmp = modelview * vec3(position, 1);
			gl_Position = projection * vec4(tmp.x, tmp.y, 0, 1);
		})"
	);
	Shader fragmentShader(R"(#version 300 es
		uniform lowp vec4 color;
		out lowp vec4 outColor;

		void main() {
			outColor = color;
		})", Shader::Type::FRAGMENT, R"(#version 100
		uniform lowp vec4 color;

		void main() {
			gl_FragColor = color;
		})"
	);
	simpleShaderProgram = std::make_unique<ShaderProgram>(vertexShader, fragmentShader);
	simpleModelviewUniform = simpleShaderProgram->getUniformLocation("modelview");
	simpleColorUniform = simpleShaderProgram->getUniformLocation("color");

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glViewport(0, 0, width, height);

	if (canvasWidth != width || canvasHeight != height) { // Letterboxing?
		glClearColor(0, 0, 0, 1); // black boxes
		glClear(GL_COLOR_BUFFER_BIT);

		glEnable(GL_SCISSOR_TEST);
		assert(canvasWidth <= width);
		assert(canvasHeight <= height);
		glScissor((width - canvasWidth) / 2, (height - canvasHeight) / 2, canvasWidth,
		          canvasHeight);
	}

	reset();
	modelviewStack = {};

	clearBackgroundColor();
	glClear(GL_COLOR_BUFFER_BIT);

	glFlush();
	setVerticalSync(true);
	return true;
}

void updateProjection(int windowWidth, int windowHeight, int originalWindowWidth,
                      int originalWindowHeight) {
	debug("Updating projection matrix to ");
	debug(windowWidth);
	debug("x");
	debug(windowHeight);
	debug(" (original size: ");
	debug(originalWindowWidth);
	debug("x");
	debug(originalWindowHeight);
	debugLn(")");
	const auto l = static_cast<float>(-windowWidth) / 2.f;
	const auto r = static_cast<float>(windowWidth) / 2.f;
	const auto b = static_cast<float>(windowHeight) / 2.f;
	const auto t = static_cast<float>(-windowHeight) / 2.f;
	opengl::projection = { float(windowWidth) / float(originalWindowWidth) * 2.f / (r - l),
		                   0.f,
		                   0.f,
		                   -(r + l) / (r - l),
		                   0.f,
		                   float(windowHeight) / float(originalWindowHeight) * 2.f / (t - b),
		                   0.f,
		                   -(t + b) / (t - b),
		                   0.f,
		                   0.f,
		                   -1.f,
		                   0.f,
		                   0.f,
		                   0.f,
		                   0.f,
		                   1.f };
}

WindowPointer pWindow;
bool antiAliasingEnabled = true;
bool vsyncEnabled = false;

void showWindow(const std::string& title, const int width, const int height, bool fullscreen,
                const std::pair<int, int> minAspectRatio,
                const std::pair<int, int> maxAspectRatio) {
	debug("jngl::showWindow(\""); debug(title); debug("\", "); debug(width); debug(", ");
	debug(height); debug(", "); debug(fullscreen); debug(");\n");
	bool isMouseVisible = pWindow ? pWindow->getMouseVisible() : true;
	hideWindow();
	if (width == 0) {
		throw std::runtime_error("Width Is 0");
	}
	if (height == 0) {
		throw std::runtime_error("Height Is 0");
	}
	pWindow.Set(new Window(title, width, height, fullscreen, minAspectRatio, maxAspectRatio));
	pWindow->SetMouseVisible(isMouseVisible);
	setAntiAliasing(antiAliasingEnabled);
	pWindow->initGlObjects();
}

void hideWindow() {
	simpleShaderProgram.reset();
	unloadAll();
	pWindow.Delete();
}

void swapBuffers() {
	pWindow->SwapBuffers();
	clearBackBuffer();
}

void clearBackBuffer() {
	if (glIsEnabled(GL_SCISSOR_TEST)) {
		// Letterboxing with SDL_VIDEODRIVER=wayland will glitch if we don't draw the black boxes on
		// every frame
		glDisable(GL_SCISSOR_TEST);
		glClearColor(0, 0, 0, 1);
		glClear(GL_COLOR_BUFFER_BIT);
		glEnable(GL_SCISSOR_TEST);
		clearBackgroundColor();
	}

	reset();
	modelviewStack = {};
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

void updateInput() {
	pWindow->updateKeyStates();
	pWindow->UpdateInput();
}

bool running() {
	return pWindow->isRunning();
}

void quit() {
	pWindow->quit();
}

void cancelQuit() {
	if (pWindow) {
		pWindow->cancelQuit();
	}
}

void setBackgroundColor(const jngl::Color color) {
	pWindow.ThrowIfNull();
	bgRed = float(color.getRed()) / 255.f;
	bgGreen = float(color.getGreen()) / 255.f;
	bgBlue = float(color.getBlue()) / 255.f;
	clearBackgroundColor();
	glClear(GL_COLOR_BUFFER_BIT);
}

void setBackgroundColor(const unsigned char red, const unsigned char green,
                        const unsigned char blue) {
	setBackgroundColor(Color{ red, green, blue });
}

Vec2 getMousePos() {
	if (getRelativeMouseMode()) {
		return { pWindow->getMouseX() / getScaleFactor(), pWindow->getMouseY() / getScaleFactor() };
	}
	return { pWindow->getMouseX() / getScaleFactor() - getScreenWidth() / 2,
		     pWindow->getMouseY() / getScaleFactor() - getScreenHeight() / 2 };
}

int getMouseX() {
	return pWindow->getMouseX();
}

int getMouseY() {
	return pWindow->getMouseY();
}

bool keyDown(const key::KeyType key) {
	return pWindow->getKeyDown(key);
}

bool keyPressed(const key::KeyType key) {
	return pWindow->getKeyPressed(key);
}

bool keyDown(const std::string& key) {
	return pWindow->getKeyDown(key);
}

bool keyPressed(const std::string& key) {
	return pWindow->getKeyPressed(key);
}

bool mouseDown(mouse::Button button) {
	return pWindow->getMouseDown(button);
}

bool mousePressed(mouse::Button button) {
	return pWindow->getMousePressed(button);
}

void setMouse(const jngl::Vec2 position) {
	pWindow->SetMouse(boost::math::iround((position.x + getScreenWidth() / 2) * getScaleFactor()),
	                  boost::math::iround((position.y + getScreenHeight() / 2) * getScaleFactor()));
}

void setRelativeMouseMode(const bool relative) {
	return pWindow->SetRelativeMouseMode(relative);
}

void setMouseVisible(const bool visible) {
	return pWindow->SetMouseVisible(visible);
}

bool isMouseVisible() {
	return pWindow->getMouseVisible();
}

bool getRelativeMouseMode() {
	return pWindow->getRelativeMouseMode();
}

void setTitle(const std::string& title) {
	return pWindow->SetTitle(title);
}

double getTextWidth(const std::string& text) {
	return static_cast<double>(pWindow->getTextWidth(text));
}

int getLineHeight() {
	return pWindow->getLineHeight();
}

void setLineHeight(int h) {
	pWindow->setLineHeight(h);
}

void print(const std::string& text, const jngl::Vec2 position) {
	pWindow->print(text, boost::math::iround(position.x), boost::math::iround(position.y));
}

void print(const std::string& text, const int xposition, const int yposition) {
	pWindow->print(text, xposition, yposition);
}

int getFontSize() {
	return pWindow->getFontSize();
}

void setFontSize(const int size) {
	pWindow->setFontSize(size);
}

std::string getFont() {
	return pWindow->getFont();
}

void setFont(const std::string& filename) {
	pWindow->setFont(filename);
}

void setFontByName(const std::string& name) {
	pWindow->setFontByName(name);
}

void setIcon(const std::string& filename) {
	pWindow->SetIcon(pathPrefix + filename);
}

double getFPS() {
	static double lastDraw = 0.0;
	double fps = 1 / (jngl::getTime() - lastDraw);
	lastDraw = jngl::getTime();
	return fps;
}

unsigned int getStepsPerSecond() {
	return pWindow->getStepsPerSecond();
}

void setStepsPerSecond(const unsigned int stepsPerSecond) {
	pWindow->setStepsPerSecond(stepsPerSecond);
}

Mat3 modelview() {
	return opengl::modelview;
}

void reset() {
	boost::qvm::set_identity(opengl::modelview);
}

void rotate(const double degree) {
	boost::qvm::rotate_z(opengl::modelview, degree * M_PI / 180.);
}

void translate(const double x, const double y) {
	opengl::translate(static_cast<float>(x * getScaleFactor()),
	                  static_cast<float>(y * getScaleFactor()));
}

void scale(const double factor) {
	opengl::scale(static_cast<float>(factor), static_cast<float>(factor));
}

void scale(const double xfactor, const double yfactor) {
	opengl::scale(static_cast<float>(xfactor), static_cast<float>(yfactor));
}

void pushMatrix() {
	modelviewStack.push(opengl::modelview);
}

void popMatrix() {
	opengl::modelview = modelviewStack.top();
	modelviewStack.pop();
}

void drawRect(const double xposition, const double yposition, const double width,
              const double height) {
	pWindow->drawRect({ xposition, yposition }, { width, height });
}

void drawRect(const Vec2 position, const Vec2 size) {
	pWindow->drawRect(position, size);
}

void drawTriangle(const Vec2 a, const Vec2 b, const Vec2 c) {
	pWindow->drawTriangle(a, b, c);
}

void drawTriangle(const double A_x, const double A_y, const double B_x, const double B_y,
                  const double C_x, const double C_y) {
	pWindow->drawTriangle({ A_x, A_y }, { B_x, B_y }, { C_x, C_y });
}

void setLineWidth(const float width) {
	glLineWidth(width);
}

void drawLine(const double xstart, const double ystart, const double xend, const double yend) {
	pWindow->drawLine({ xstart, ystart }, { xend, yend });
}

void drawLine(const Vec2 start, const Vec2 end) {
	pWindow->drawLine(start, end);
}

void drawPoint(const double x, const double y) {
	pWindow->drawEllipse({ x, y }, { 1, 1 }, 0);
}

int getWindowWidth() {
	return pWindow->getCanvasWidth();
}

int getWindowHeight() {
	return pWindow->getCanvasHeight();
}

std::array<Pixels, 2> getWindowSize() {
	return { Pixels(getWindowWidth()), Pixels(getWindowHeight()) };
}

double getMouseWheel() {
	return pWindow->getMouseWheel();
}

void setAntiAliasing(bool enabled) {
#ifdef GL_MULTISAMPLE_ARB
	if (!pWindow->isMultisampleSupported()) {
		antiAliasingEnabled = false;
		return;
	}
	if (enabled) {
		glEnable(GL_MULTISAMPLE_ARB);
	} else {
		glDisable(GL_MULTISAMPLE_ARB);
	}
	antiAliasingEnabled = enabled;
#else
	debug("WARNING: anti aliasing not available!\n");
#endif
}

bool getAntiAliasing() {
	return antiAliasingEnabled;
}

void loadSound(const std::string&); // definied in audio.cpp

Finally load(const std::string& filename) {
	if (filename.length() >= 4 && filename.substr(filename.length() - 4) == ".ogg") {
		loadSound(filename);
		return Finally(nullptr);
	}
	return loadSprite(filename);
}

#if defined(_WIN32) && !defined(JNGL_UWP)
#include <epoxy/wgl.h>

void setVerticalSync(bool enabled) {
	if (wglSwapIntervalEXT && wglSwapIntervalEXT(enabled)) {
		vsyncEnabled = enabled;
	}
#else
void setVerticalSync(bool) {
#endif
#ifdef __APPLE__
	// OSX always enables V-SYNC
	vsyncEnabled = true;
#endif
}

bool getVerticalSync() {
	return vsyncEnabled;
}

void setWork(std::shared_ptr<Work> work) {
	return pWindow->setWork(std::move(work));
}

void resetFrameLimiter() {
	pWindow->resetFrameLimiter();
}

void setWork(Work* w) {
	return pWindow->setWork(std::shared_ptr<Work>(w));
}

std::shared_ptr<Work> getWork() {
	return pWindow->getWork();
}

void setPrefix(const std::string& path) {
	pathPrefix = path;
	if (!pathPrefix.empty() && pathPrefix.back() != '/') {
		pathPrefix += '/';
	}
}

std::string getPrefix() {
	return pathPrefix;
}

void setConfigPath(const std::string& path) {
	configPath = path;
	if (configPath->back() != '/') {
		*configPath += '/';
	}
}

std::string _getConfigPath() {
	if (configPath) {
		return *configPath;
	}
#ifndef IOS
	std::stringstream path;
#if defined(__APPLE__)
	path << getSystemConfigPath() << "/" << App::instance().getDisplayName() << "/";
#elif defined(ANDROID)
	path << getSystemConfigPath() << "/";
#elif defined(_WIN32)
	path << getSystemConfigPath() << "\\" << App::instance().getDisplayName() << "\\";
#else
	path << getenv("HOME") << "/.config/" << App::instance().getDisplayName() << "/";
#endif
	return *(configPath = path.str());
#endif
	throw std::runtime_error("Couldn't get config path. Has the app been started?");
}

void setArgs(std::vector<std::string> args) {
	jngl::args = std::move(args);
}

std::vector<std::string> getArgs() {
	return args;
}

std::string getConfigPath() {
	return _getConfigPath();
}

std::stringstream readAsset(const std::string& filename) {
	if (!filename.empty() && filename[0] == '/') {
		throw std::runtime_error("Do not pass absolute paths to jngl::readAsset.");
	}
	std::stringstream sstream;
	FILE* const f = fopen((pathPrefix + filename).c_str(), "rb");
	if (!f) {
		sstream.setstate(std::ios::failbit);
		return sstream;
	}
	Finally closeFile([f]() { fclose(f); });
	if (fseek(f, 0, SEEK_END) != 0) {
		sstream.setstate(std::ios::failbit);
		return sstream;
	}
	const auto size = ftell(f);
	if (fseek(f, 0, SEEK_SET) != 0) {
		sstream.setstate(std::ios::failbit);
		return sstream;
	}
	auto content = std::make_unique<char[]>(size);
	if (fread(content.get(), size, 1, f) != 1) {
		sstream.setstate(std::ios::failbit);
		return sstream;
	}
	sstream.write(content.get(), size);
	return sstream;
}

#if !defined(__APPLE__) || !TARGET_OS_IPHONE
std::string readConfig(const std::string& key) {
	if (!key.empty() && key[0] == '/') {
		throw std::runtime_error("Do not pass absolute paths as keys to jngl::readConfig.");
	}

#ifdef _WIN32
	std::filesystem::path p = std::filesystem::u8path(_getConfigPath() + key);
	std::ifstream fin(p, std::ios::binary);
#else
	std::ifstream fin(_getConfigPath() + key, std::ios::binary);
#endif

	std::string out;
	constexpr size_t READ_SIZE = 4096;
	std::string buf(READ_SIZE, '\0');
	while (fin.read(&buf[0], READ_SIZE)) {
		out.append(buf, 0, fin.gcount());
	}
	out.append(buf, 0, fin.gcount());
	return out;
}

void writeConfig(const std::string& key, const std::string& value) {
	if (!key.empty() && key[0] == '/') {
		throw std::runtime_error("Do not pass absolute paths as keys to jngl::readConfig.");
	}
#ifdef HAVE_FILESYSTEM
	const auto configPath = std::filesystem::u8path(_getConfigPath());
	const auto directory = (configPath / std::filesystem::u8path(key)).parent_path();
	if (!std::filesystem::exists(directory)) {
		std::filesystem::create_directories(directory);
	}
#endif
#ifdef _WIN32
	std::filesystem::path p = std::filesystem::u8path(_getConfigPath() + key);
	std::ofstream fout(p, std::ios::binary);
#else
	std::ofstream fout(_getConfigPath() + key, std::ios::binary);
#endif
	fout.exceptions(std::ifstream::failbit | std::ifstream::badbit);
	fout << value;
}
#endif

ShaderProgram::Context useSimpleShaderProgram() {
	auto context = jngl::simpleShaderProgram->use();
	glUniform4f(simpleColorUniform, float(colorRed) / 255.0f, float(colorGreen) / 255.0f,
	            float(colorBlue) / 255.0f, float(colorAlpha) / 255.0f);
	glUniformMatrix3fv(simpleModelviewUniform, 1, GL_FALSE, opengl::modelview.data);

	assert(simpleShaderProgram->getAttribLocation("position") == 0);
	glEnableVertexAttribArray(0);

	return context;
}

} // namespace jngl


/* vim: set sts=0 ts=4 sw=4 noet tw=0 : */

#include <boost/polygon/voronoi.hpp>

using boost::polygon::voronoi_builder;
using boost::polygon::voronoi_diagram;

#include "MATGraphBuilder.h"
#include "utils.h"

#if ICESL_PLUGIN==1
#include <clipper.hpp>
#else
#include <polyclipping/clipper.hpp>
#endif

#include <stack>

using CLPoint = ClipperLib::IntPoint;
using CLPath = ClipperLib::Path;
using CLPaths = ClipperLib::Paths;

struct Segment {
    Vec2i p0;
    Vec2i p1;
    Segment(const CLPoint & v0, const CLPoint & v1) : p0(static_cast<int>(v0.X), static_cast<int>(v0.Y)), p1(static_cast<int>(v1.X), static_cast<int>(v1.Y)) {}
};

namespace boost {
    namespace polygon {

        template <>
            struct geometry_concept<Vec2i> { typedef point_concept type; };

        template <>
            struct point_traits<Vec2i> {
                typedef typename Vec2i::NumberType coordinate_type;

                static inline coordinate_type get(const Vec2i& point, orientation_2d orient) {
                    return (orient == HORIZONTAL) ? point.x() : point.y();
                }
            };

        template <>
            struct geometry_concept<Segment> { typedef segment_concept type; };

        template <>
            struct segment_traits<Segment> {
                typedef typename Vec2i::NumberType coordinate_type;
                typedef Vec2i point_type;

                static inline point_type get(const Segment& segment, direction_1d dir) {
                    return dir.to_int() ? segment.p1 : segment.p0;
                }
            };

    }  // polygon
}  // boost

inline Vec2i convert2d22i(const double scale, const Vec2d & v) {
    Vec2d vv = scale * v;
    return Vec2i(static_cast<int>(round(vv.x())), static_cast<int>(round(vv.y())));
}

using VD = voronoi_diagram<double>;
using VPtr = const VD::vertex_type *;
using EPtr = const VD::edge_type *;
using CPtr = const VD::cell_type *;

void buildMATGraphWithBOOST(MATGraph& mat, const CLPaths & paths, const double mm_per_unit);

void buildMATGraphWithBOOST(MATGraph& mat, const Paths & inputPoly, const double units_per_mm) {
    // For now we ASSUME the input is in MILLImeters and we convert to INTEGER MICROmeter
    if (inputPoly.empty()) return;

    // Scale and fix the input
    CLPaths paths;

    for( const auto & path : inputPoly ) {
        const size_t N = path.size();
		//cerr << "IN PATH OF SIZE " << N << endl;
        if( N < 3 ) continue;
        paths.emplace_back();
        CLPath & p = paths.back();
        for( const auto & pt : path ) {
            Vec2i v = convert2d22i(units_per_mm, pt);
            p.emplace_back(v.x(), v.y());
        }
    }
    ClipperLib::SimplifyPolygons(paths, ClipperLib::pftEvenOdd);
    buildMATGraphWithBOOST(mat, paths, 1.0/units_per_mm);
}

void buildMATGraphWithBOOST(MATGraph& mat, const CLPaths & paths, const double mm_per_unit) {
    if( paths.empty() ) return;

    std::vector<Segment> segments;

    auto toSite = [&](CPtr cell) {
        std::size_t index = cell->source_index();
        const Segment & s = segments[index];
        Vec2i p0 = boost::polygon::low(s);
        Vec2d p0d = mm_per_unit * Vec2d(p0.x(), p0.y());
        Vec2i p1 = boost::polygon::high(s);
        Vec2d p1d = mm_per_unit * Vec2d(p1.x(), p1.y());
        if( cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) {
            return Site(p0d);
        } else if( cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT) {
            return Site(p1d);
        }
        return Site(p0d, p1d);
    };

    auto voronoiCircumcircle = [&](const VPtr v) -> Disk {
        Vec2d p = mm_per_unit * Vec2d(v->x(), v->y());
        EPtr first_edge = v->incident_edge();
        EPtr edge = first_edge;
        do {
            CPtr cell = edge->cell();
            std::size_t index = cell->source_index();
            const Segment & s = segments[index];
            Site site = toSite(cell);
            if( site.is_point() )
                return Disk(p, (p-site.point()).length());
            edge = edge->rot_next();
        } while( edge != first_edge );
        CPtr cell = edge->cell();
        std::size_t index = cell->source_index();
        const Segment & s = segments[index];
        Vec2d p0d = mm_per_unit * Vec2d(s.p0.x(), s.p0.y());
        Vec2d p1d = mm_per_unit * Vec2d(s.p1.x(), s.p1.y());
        return Disk(p, Utils::distanceToLine(p, p0d, p1d-p0d));
    };

    auto printV = [=](const VD::vertex_type * v) {
        cerr << '(' << (v->x() * mm_per_unit) << ", " << (v->y() * mm_per_unit) << ')';
    };

    auto analyze = [&](const VD::vertex_type * v, bool & is_input, int & degree, int & n_secondaries) {
		const EPtr first_edge = v->incident_edge();
		EPtr edge = first_edge; // iterate over outgoing edges
		n_secondaries = degree = 0;
		is_input = false;
		do {
			degree++;
			if( edge->is_secondary() ) n_secondaries++;
			CPtr cell = edge->cell();
			std::size_t index = cell->source_index();
			const Segment & s = segments[index];
			if( (v->x() == s.p0.x()) && (v->y() == s.p0.y()) ) is_input = true;
			if( (v->x() == s.p1.x()) && (v->y() == s.p1.y()) ) is_input = true;
            edge = edge->rot_next();
		} while( edge != first_edge );
	};

    // Go
	for( const auto & path : paths ) {
        const size_t N = path.size();
		if( N < 3 ) continue;
		// We always insert loops.
		// So any point is adjacent to an even number of edges (unless degenerate cases).
        CLPoint p0 = path[N-1];
        for( const auto & p : path ) {
            CLPoint p1 = p;
            segments.emplace_back(p0, p1);
            p0 = p1;
        }
    }

	if( segments.empty() ) return;

    VD vd;
    construct_voronoi(segments.begin(), segments.end(), &vd);
    //cerr << "BOOST Voronoi built over " << segments.size() << " input segments\n";

	// Find start vertex
    VPtr curv = nullptr;
    const int outside_flag = 1; // we currently don't know the color of the "outside" vertices. so we store -1
    const int inside_flag = 1 - outside_flag;
	int start_flag;

	for ( auto it = vd.vertices().begin(); it != vd.vertices().end(); ++it ) {
		VPtr v = &(*it);
		EPtr first_edge = v->incident_edge();
		EPtr edge = first_edge; // iterate over outgoing edges
		do {
			if( ! edge->is_infinite() ) {
				edge = edge->rot_next();
				continue;
			}
			curv = v;
			start_flag = edge->is_secondary() ? inside_flag : outside_flag;
			break;
		} while( edge != first_edge );
		if( curv ) break;
	}

	assert(curv != nullptr);
	//cerr << "START VERTEX IS "; printV(curv); cerr << endl;

    // Flag the vertices
    curv->color(2+start_flag); // color = 2 * visited + in/out flag // visited=1, color=0
    stack<VPtr> visitee;
    while( true ) {
		int degree, n_sec; bool is_input;
		analyze(curv, is_input, degree, n_sec);
		//cerr << "VISITING "; printV(curv); cerr << (is_input?" INPUT":" INTERNAL") << ", deg=" << degree << ", n_sec=" << n_sec << endl;
        EPtr edge = curv->incident_edge();
        int flag = curv->color() % 2;
		if( is_input && (n_sec == 0) ) {
			assert(degree % 2 == 0);
			curv->color(2+inside_flag);
			int fl = -1;
			EPtr start = curv->incident_edge();
			do {
				VPtr nv = start->vertex1();
				assert( nv != nullptr );
				int scol = static_cast<int>(nv->color());
				if( scol / 2 == 1 ) { // already visited?
					fl = scol % 2;
					break;
				}
				start = start->rot_next();
			} while (start != curv->incident_edge());
			assert( fl != -1 );
			edge = start;
			do {
				VPtr nv = edge->vertex1();
				assert( nv != nullptr );
				if( nv->color() / 2 == 1 ) { // already visited?
					assert( nv->color() % 2 == fl );
					edge = edge->rot_next();
					fl = 1 - fl;
					continue;
				}
				nv->color(2+fl);
				visitee.push(nv);
				fl = 1 - fl;
				edge = edge->rot_next();
			} while (edge != start );
		} else {
			do {
				VPtr nv = edge->vertex1();
				if( nv == curv ) { cerr << "SELF-LOOP ERROR"; exit(-1); }
				if( (nullptr == nv) || (nv->color() / 2 == 1) ) { // |nv| infinite or already visited?
					edge = edge->rot_next();
					continue;
				}
				visitee.push(nv);
				if( edge->is_primary() ) {
					nv->color(2 + flag);
				} else {
					//cerr << "SECONDARY EDGE from "; printV(curv);
					//cerr << " to "; printV(nv); cerr << endl;
					nv->color(2 + 1 - flag);
				}
				edge = edge->rot_next();
			} while (edge != curv->incident_edge());
		}
        if( visitee.empty() ) break;
        curv = visitee.top();
        visitee.pop();
    }
    //cerr << "Outside flag is " << outside_flag << endl;
    if( outside_flag == -1 ) { cerr << "outside flag ERROR"; return; }

    // Build the MAT
    std::map<VPtr, MATvert *> mat_vertex;
    int i(0);
	for ( auto it = vd.vertices().begin(); it != vd.vertices().end(); ++it ) {
		VPtr v = &(*it);
        if ( v->color() % 2 != inside_flag ) continue;

        mat.verts.emplace_back(voronoiCircumcircle(v));
        MATvert& vert = mat.verts.back();
		mat_vertex[v] = & vert;
		//cerr << "V at " << vert.circumcircle.center_ << " with radius " << vert.circumcircle.radius_ << endl;
	}
	for ( auto it = vd.vertices().begin(); it != vd.vertices().end(); ++it ) {
		VPtr v = &(*it);
		if ( v->color() % 2 != inside_flag ) continue;

		MATvert * matv = mat_vertex[v];

		EPtr first_edge = v->incident_edge();
		EPtr edge = first_edge; // iterate over outgoing edges
		do {
			auto neighbor_it = mat_vertex.find(edge->vertex1());
			if( (!edge->is_primary()) || (neighbor_it == mat_vertex.end()) ) {
				edge = edge->rot_next();
				continue;
			}
			MATvert * neighbor = neighbor_it->second;
			// create edge
			matv->edges_.emplace_back();
			MATedge & mate = matv->edges_.back();
			// setup mate.to_ pointer to neighbor
			mate.to_ = neighbor;
			// setup mate.type
			CPtr cell = edge->twin()->cell(); // the cell to the right of the edge
			CPtr opp_cell = edge->cell();
			if( cell->contains_point() != opp_cell->contains_point() ) {
				mate.type = EdgeType::VertEdge;
			} else if( cell->contains_point() ) {
				mate.type = EdgeType::VertVert;
			} else {
				mate.type = EdgeType::EdgeEdge;
			}
			// setup mate.site_
			mate.site_ = toSite(cell);
			// setup mate.twin_
			if( ! neighbor->edges_.empty() ) { // the neighbor has already created its edges, let's find our twin
				bool found = false;
				for( EdgeIterator twin_candidate = neighbor->edges_.begin();
				  twin_candidate != neighbor->edges_.end(); ++twin_candidate ) {
					if( twin_candidate->to_ == matv ) {
						twin_candidate->twin_ = --matv->edges_.end();
						mate.twin_ = twin_candidate;
						found = true;
					}
				}
				if( ! found ) {
					cerr << "TWIN NOT FOUND !\n";
					exit(-1);
				}
			}
			edge = edge->rot_next();
		} while( edge != first_edge );
    }
	mat.splitApexes();
}


// Copyright (c) 2015-2021 Daniel Cooke
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

#include "allele_frequency.hpp"

#include <algorithm>
#include <iterator>

#include <boost/variant.hpp>

#include "core/types/allele.hpp"
#include "io/variant/vcf_record.hpp"
#include "utils/concat.hpp"
#include "allele_depth.hpp"
#include "assigned_depth.hpp"

namespace octopus { namespace csr {

const std::string AlleleFrequency::name_ = "AF";

std::unique_ptr<Measure> AlleleFrequency::do_clone() const
{
    return std::make_unique<AlleleFrequency>(*this);
}

Measure::ValueType AlleleFrequency::get_value_type() const
{
    return double {};
}

Measure::ResultType AlleleFrequency::do_evaluate(const VcfRecord& call, const FacetMap& facets) const
{
    const auto allele_depths = boost::get<Array<Array<Optional<ValueType>>>>(AlleleDepth{}.evaluate(call, facets));
    const auto depths = boost::get<Array<ValueType>>(AssignedDepth{}.evaluate(call, facets));
    const auto num_alleles = 1 + call.alt().size();
    Array<Array<Optional<ValueType>>> result(allele_depths.size(), Array<Optional<ValueType>>(num_alleles));
    for (std::size_t s {0}; s < allele_depths.size(); ++s) {
        assert(allele_depths[s].size() == num_alleles);
        const auto depth = boost::get<std::size_t>(depths[s]);
        for (std::size_t a {0}; a < num_alleles; ++a) {
            if (allele_depths[s][a]) {
                if (depth > 0) {
                    result[s][a] = static_cast<double>(boost::get<std::size_t>(*allele_depths[s][a])) / depth;
                } else {
                    result[s][a] = 1.0;
                }
            }
        }
    }
    return result;
}

Measure::ResultCardinality AlleleFrequency::do_cardinality() const noexcept
{
    return ResultCardinality::samples_and_alleles;
}

const std::string& AlleleFrequency::do_name() const
{
    return name_;
}

std::string AlleleFrequency::do_describe() const
{
    return "Empirical allele frequency (AD / ADP)";
}

std::vector<std::string> AlleleFrequency::do_requirements() const
{
    return concat(AlleleDepth{}.requirements(), AssignedDepth{}.requirements());
}

boost::optional<Measure::Aggregator> AlleleFrequency::do_aggregator() const noexcept
{
    return Measure::Aggregator::min_tail;
}

} // namespace csr
} // namespace octopus


/*
 * Copyright 2009, 2010 Jan Gasthaus (j.gasthaus@gatsby.ucl.ac.uk)
 *
 * This file is part of libPLUMP.
 *
 * libPLUMP is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * libPLUMP is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with libPLUMP.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <iostream>
#include <fstream>
#include <libgen.h>
#include <cmath>
#include <boost/program_options.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/convenience.hpp>
#include <boost/scoped_ptr.hpp>

#include <libplump/libplump.h>
#include <libplump/switching_restaurant.h>

#define DISCOUNT sigmoid(d0)*sigmoid(d1)
#define CONCENTRATION sigmoid(d0)*sigmoid(d1)*exp(a)

using namespace std;
using namespace gatsby::libplump;
namespace po = boost::program_options;
namespace fs = boost::filesystem;

static unsigned int num_types = 2;

IParameters* getParameters(po::variables_map& vm) {
  return new SimpleParameters(vm["disc"].as<d_vec>(), vm["alpha"].as<double>());
}

double testSingleGradients(po::variables_map& vm) {
  seq_type seq;

  boost::scoped_ptr<IParameters> parameters(new SimpleParameters(vm["disc"].as<d_vec>(), vm["alpha"].as<double>()));
  boost::scoped_ptr<IAddRemoveRestaurant> restaurant(new HistogramRestaurant());
  void* payload = restaurant->getFactory().make();

  double d0 = logit(0.5);
  double d1 = logit(0.5);
  double a = 0.;
  double eps = 10e-7;

  for (int i = 0; i < 100; ++i) {
    restaurant->addCustomer(payload, 0, 1/2., DISCOUNT, CONCENTRATION);
    restaurant->addCustomer(payload, 1, 1/2., DISCOUNT, CONCENTRATION);
    restaurant->addCustomer(payload, 1, 1/2., DISCOUNT, CONCENTRATION);
  }
  cout << "c0: " <<   restaurant->getC(payload, 0)
       << ", t0: " << restaurant->getT(payload, 0)
       << ", c: "  << restaurant->getC(payload)
       << ", t: "  << restaurant->getT(payload)
       << endl;

  for (int i = 0; i < 100; ++i) {

    double p0 =  restaurant->computeProbability(payload, 0, 1/2., DISCOUNT, CONCENTRATION);
    double p1 =  restaurant->computeProbability(payload, 1, 1/2., DISCOUNT, CONCENTRATION);

    cout << "disc: " << DISCOUNT<< ", alpha: " << CONCENTRATION<< ", prob of 0: " << p0 << ", prob of 1: " << p1 << endl;

    double gd0 = PYPPredictiveGradientIndividualDiscount(restaurant->getC(payload, 0),
                                          restaurant->getT(payload, 0),
                                          restaurant->getC(payload),
                                          restaurant->getT(payload),
                                          1/2.,
                                          DISCOUNT,
                                          sigmoid(d0),
                                          CONCENTRATION,
                                          0.0)*sigmoid(d0)*(1-sigmoid(d0))/p0;
    double gd1 = PYPPredictiveGradientIndividualDiscount(restaurant->getC(payload, 1),
                                          restaurant->getT(payload, 1),
                                          restaurant->getC(payload),
                                          restaurant->getT(payload),
                                          1/2.,
                                          DISCOUNT,
                                          sigmoid(d0),
                                          CONCENTRATION,
                                          0.0)*sigmoid(d0)*(1-sigmoid(d0))/p1;
    double ga0 = PYPPredictiveGradientConcentration(restaurant->getC(payload, 0),
                                          restaurant->getT(payload, 0),
                                          restaurant->getC(payload),
                                          restaurant->getT(payload),
                                          1/2.,
                                          DISCOUNT,
                                          CONCENTRATION,
                                          0.0)*exp(a)/p0;
    double ga1 = PYPPredictiveGradientConcentration(restaurant->getC(payload, 1),
                                          restaurant->getT(payload, 1),
                                          restaurant->getC(payload),
                                          restaurant->getT(payload),
                                          1/2.,
                                          DISCOUNT,
                                          CONCENTRATION,
                                          0.0)*exp(a)/p1;
    double p0p =  log(restaurant->computeProbability(payload, 0, 1/2., sigmoid(d0+eps)*sigmoid(d1),sigmoid(d0+eps)*sigmoid(d1)*exp(a) ));
    double p0m =  log(restaurant->computeProbability(payload, 0, 1/2., sigmoid(d0-eps)*sigmoid(d1),sigmoid(d0-eps)*sigmoid(d1)*exp(a) ));
    double gd0_est = (p0p - p0m)/(2*eps);
    p0p = log(restaurant->computeProbability(payload, 0, 1/2., DISCOUNT, DISCOUNT*exp(a+eps)));
    p0m = log(restaurant->computeProbability(payload, 0, 1/2., DISCOUNT, DISCOUNT*exp(a-eps)));
    double ga0_est = (p0p - p0m)/(2*eps);
    cout << "Discount: grad of 0: " << gd0
         << ", estimate: " << gd0_est
         << ", grad of 1: " << gd1 << endl
         << "Alpha grad of 0: " << ga0
         << ", estimate: " << ga0_est
         << ", grad of 1: " << ga1
         << endl;
    //restaurant->addCustomer(payload, 0, 1/2., 0.5, 1);
    //restaurant->addCustomer(payload, 0, 1/2., sigmoid(d), exp(a));
    d0 += gd0;
    a += ga0;
  }

  restaurant->getFactory().recycle(payload);

}

int main(int argc, char* argv[]) {
  const double sm_disc[] = {.62, .69, .74, .80, .95};
  d_vec default_discounts;
  default_discounts.assign(sm_disc,&sm_disc[5]);
  // Declare the supported options.
  po::options_description generic("Generic options");
  generic.add_options()
    ("alpha,a", po::value<double>()->default_value(5), "Concentration parameter")
    ("disc,d", po::value<d_vec>()->default_value(default_discounts,"..."), "Discount parameter(s)")
    ("num-types", po::value<int>()->default_value(256), "Number of types")
    ;

  po::options_description cmdline_options;
  cmdline_options.add(generic);

  po::options_description visible_options;
  visible_options.add(generic);

  po::variables_map vm;
  po::store(po::command_line_parser(argc, argv).
      options(cmdline_options).run(), vm);
  po::notify(vm);

  string usage = "Usage: gradient_test [OPTIONS]...\n";

  num_types = vm["num-types"].as<int>();

  init_rng();

  testSingleGradients(vm);

  free_rng();
}


#include "hoCuNDArray_utils.h"
#include "radial_utilities.h"
#include "hoNDArray_fileio.h"
#include "cuNDArray_fileio.h"
#include "cuNDArray.h"
#include "cuNDArray_math.h"
#include "cuNDArray_utils.h"
#include "imageOperator.h"
#include "identityOperator.h"
#include "hoPartialDerivativeOperator.h"
#include "hoCuConebeamProjectionOperator.h"
#include "cuConvolutionOperator.h"
#include "hoCuIdentityOperator.h"
#include "hoCuNDArray_math.h"
#include "hoCuNDArray_blas.h"
#include "hoCuCgSolver.h"
#include "CBCT_acquisition.h"
#include "complext.h"
#include "encodingOperatorContainer.h"
#include "vector_td_io.h"
#include "hoCuPartialDerivativeOperator.h"
#include "GPUTimer.h"
#include "FFTRebinning.h"

#include <iostream>
#include <algorithm>
#include <sstream>
#include <math_constants.h>
#include <boost/program_options.hpp>
#include <boost/make_shared.hpp>
#include "cuNFFT.h"
#include "hoNDArray_math.h"

using namespace std;
using namespace Gadgetron;

typedef float REAL;
typedef cuNFFT_plan<REAL,2> PLAN;
namespace po = boost::program_options;

boost::shared_ptr<cuNDArray<vector_td<REAL,2>>> make_points(std::vector<size_t> & dims){
	hoNDArray<vector_td<REAL,2>> points({dims[0]*dims[1]});
	vector_td<REAL,2> * data = points.get_data_ptr();

	for (int n = 0; n < dims[1]; n++)
		for (int m = 0; m < dims[0]; m++)
			data[m+n*dims[0]] = vector_td<REAL,2>(REAL(m)/(dims[0]-1)-0.5f,REAL(n)/(dims[1]-1)-0.5f);

	auto result = boost::make_shared<cuNDArray<vector_td<REAL,2>>>(points);
	return result;

}

int
main(int argc, char** argv)
{
  string outputFile;
  string inputFile;

  po::options_description desc("Allowed options");
  desc.add_options()
    ("help", "produce help message")
    ("input,a", po::value<string>(&inputFile)->default_value("lena.real"),"Input filename")
    ("output,f", po::value<string>(&outputFile)->default_value("reconstruction.real"), "Output filename")
    ;

  po::variables_map vm;
  po::store(po::parse_command_line(argc, argv, desc), vm);
  po::notify(vm);

  if (vm.count("help")) {
    cout << desc << "\n";
    return 1;
  }
  std::cout << "Command line options:" << std::endl;
  for (po::variables_map::iterator it = vm.begin(); it != vm.end(); ++it){
    boost::any a = it->second.value();
    std::cout << it->first << ": ";
    if (a.type() == typeid(std::string)) std::cout << it->second.as<std::string>();
    else if (a.type() == typeid(int)) std::cout << it->second.as<int>();
    else if (a.type() == typeid(unsigned int)) std::cout << it->second.as<unsigned int>();
    else if (a.type() == typeid(REAL)) std::cout << it->second.as<REAL>();
    else if (a.type() == typeid(vector_td<REAL,3>)) std::cout << it->second.as<vector_td<REAL,3> >();
    else if (a.type() == typeid(vector_td<int,3>)) std::cout << it->second.as<vector_td<int,3> >();
    else if (a.type() == typeid(vector_td<unsigned int,3>)) std::cout << it->second.as<vector_td<unsigned int,3> >();
    else std::cout << "Unknown type" << std::endl;
    std::cout << std::endl;
  }
  cudaDeviceReset();

  //Really weird stuff. Needed to initialize the device?? Should find real bug.
  cudaDeviceManager::Instance()->lockHandle();
  cudaDeviceManager::Instance()->unlockHandle();

  auto input = cuNDArray<float>(*read_nd_array<float>(inputFile.c_str()));
  //auto rInput = convert_to<float,REAL>(&input);
  auto cuInput = real_to_complex<complext<REAL>>(&input);
  auto points = make_points(*input.get_dimensions());

  PLAN plan;

  auto dims = from_std_vector<size_t,2>(*input.get_dimensions());
  plan.setup(dims,dims*size_t(2),7.5);
  plan.preprocess(points.get(),PLAN::NFFT_PREP_ALL);

  auto output = *cuInput;
  clear(&output);

  cuInput->reshape(std::vector<size_t>{cuInput->get_number_of_elements()});
  /*
  std::vector<size_t> dims_oversampled{cuInput->get_size(0)*2,cuInput->get_size(1)*2};
  output = cuNDArray<float_complext>(dims_oversampled);

  mirrorpad<float_complext,2>(cuInput.get(),&output);
  */
  //output.reshape(std::vector<size_t>{cuInput->get_number_of_elements()});

  plan.compute(cuInput.get(),&output,nullptr,PLAN::NFFT_FORWARDS_NC2C);
  //output.reshape(cuInput->get_dimensions());
  //cuNDFFT<REAL>::instance()->fft(&output);
  clear(cuInput.get());
  plan.compute(&output,cuInput.get(),nullptr,PLAN::NFFT_BACKWARDS_C2NC);
  output = *cuInput;

/*
  plan.deapodize(cuInput.get());
  cuNDFFT<REAL>::instance()->fft(cuInput.get());
  plan.convolve(cuInput.get(),&output,nullptr,PLAN::NFFT_CONV_C2NC,false);

  output.reshape(cuInput->get_dimensions());
  cuNDFFT<REAL>::instance()->ifft(&output);
  */
/*
  plan.convolve(cuInput.get(),&output,nullptr,PLAN::NFFT_CONV_NC2C,false);

  cuNDFFT<REAL>::instance()->fft(&output);
  plan.deapodize(&output,true);
  cuNDFFT<REAL>::instance()->ifft(&output);
  */
  //plan.convolve_NFFT_NC2C(&cuInput,&output,false);
  //plan.deapodize(&cuInput);
  /*
  cuNDFFT<REAL>::instance()->fft(&cuInput);
  output = cuInput;
  //plan.convolve(&cuInput,&output,nullptr,PLAN::NFFT_CONV_C2NC);

  cuNDFFT<REAL>::instance()->ifft(&output);
*/

/*
  plan.compute(&cuInput,&output,nullptr,PLAN::NFFT_FORWARDS_C2NC);
  //cuNDFFT<REAL>::instance()->ifft(&output);

  std::cout << "Output nrm " << nrm2(&output) << std::endl;
  clear(&cuInput);
  plan.compute(&output,&cuInput,nullptr,PLAN::NFFT_BACKWARDS_NC2C);

  std::cout << "Output nrm " << nrm2(&cuInput) << std::endl;
  //cuNDFFT<REAL>::instance()->ifft(&output);

  output = cuInput;
*/
  write_nd_array<REAL>( abs(&output).get(), outputFile.c_str());
}


#include <vector>
#include <iterator>
#include <typeinfo>
#include <openssl/crypto.h>

#include <boost/algorithm/string.hpp>

#include "user.h"
#include "utils.h"

User::User(unsigned int keysize)
{
    this->keysize = keysize;
    this->numberG = BN_new();
    BIGNUM * number1 = BN_new();
    BN_one(number1);

    BN_add(this->numberG, number1, number1);

    BN_clear_free(number1);
};

User::~User(void)
{
    if (this->numberX_1)
        BN_clear_free(this->numberX_1);
    if (this->numberS)
        BN_clear_free(this->numberX_1);
    if (this->numberG)
        BN_clear_free(this->numberG);
    if (this->primeQ)
        BN_clear_free(this->primeQ);
}

void User::setUserKey(const BIGNUM * userKey)
{
    this->numberX_1 = BN_dup(userKey);
};

void User::setNumberG(const BIGNUM * serverKey)
{
    this->numberG = BN_dup(serverKey);
};

void User::setPrimeQ(const BIGNUM * Q)
{
    this->primeQ = BN_dup(Q);
};

void User::setSalt(const BIGNUM * saltKey)
{
    this->numberS = BN_dup(saltKey);
};

std::pair<BIGNUM *, BIGNUM *> User::UserTD(const unsigned char * component, unsigned int size_component)
{
    if(this->primeQ == NULL || this->numberG == NULL)
    {
        printf("Values not initialized\n");
        exit(-1);
    }
    /* Convert the user string into a number */
    BIGNUM * componentInBinary = BN_new();
    if (size_component > 160) // TODO: FIX THIS LIMIT ACCORDING TO THE KEYSIZE, SHOW A WARNING MESSAGE!
        size_component = 160;
    componentInBinary = BN_bin2bn(component, size_component, componentInBinary);

    BIGNUM * r = BN_new();
    BN_CTX * ctx = BN_CTX_new();
    BN_div(NULL, r, componentInBinary, this->primeQ, ctx);
    BN_CTX_free(ctx);

    BN_clear_free(componentInBinary);

    /* TODO: use a pseudorandom function */

    /* Calculate first part of the pair */
    BIGNUM * t_1 = BN_new();
    ctx = BN_CTX_new();
    BN_mod_exp(t_1, this->numberG, r, this->primeQ, ctx);
    BN_CTX_free(ctx);

    /* Calculate second part of the pair */
    BIGNUM * t_2 = BN_new();
    ctx = BN_CTX_new();
    BN_mod_exp(t_2, t_1, this->numberX_1, this->primeQ, ctx);
    BN_CTX_free(ctx);

    BN_clear_free(r);

    /* Return the key pair */

    return std::make_pair(t_1, t_2);
};

char * User::FullUserTD(const std::vector<std::pair<BIGNUM *, BIGNUM *> > * user_content_names)
{

    char * new_content_name;
    unsigned int offset = 0;
    new_content_name = (char *) calloc(sizeof(char), user_content_names->size() * ((SECURITY_KEYSIZE/4)+1 + 1) * 2); // 1 for the / 1 for the \0

    std::vector<std::pair<BIGNUM *, BIGNUM *> >::const_iterator it;

    /* Initialize name */
    memcpy((void *) (new_content_name + offset), (void *) "/", 1 + 1);
    offset +=1;

    for (it = user_content_names->begin() ; it != user_content_names->end(); it++)
    {
        std::pair<BIGNUM *, BIGNUM *> pair_name = *it;
        //res->push_back(encrypted_bignum);

        //TODO: me puedo estar olvidando un cero aca
        char * aux_component = BN_bn2hex(pair_name.first);
        unsigned int size_aux_component = strnlen(aux_component, SECURITY_KEYSIZE/4 + 1 + 1); // Potentil \0 and potential minus sign
        memcpy((void *) (new_content_name + offset), (void *) aux_component, size_aux_component);
        offset += size_aux_component;
        memcpy((void *) (new_content_name + offset), (void *) "/", 1 + 1);
        offset +=1;
        OPENSSL_free(aux_component);

        aux_component = BN_bn2hex(pair_name.second);
        size_aux_component = strnlen(aux_component, SECURITY_KEYSIZE/4 + 1 + 1); // Potentil \0 and potential minus sign
        memcpy((void *) (new_content_name + offset), (void *) aux_component, size_aux_component);
        offset +=(size_aux_component);
        memcpy((void *) (new_content_name + offset), (void *) "/", 1 + 1);
        offset +=1;

        OPENSSL_free(aux_component);
    }

    return new_content_name;
}

// TODO: untested
std::pair<BIGNUM *, BIGNUM *> User::ContentProviderEnc(const unsigned char * component, unsigned int size_component)
{
    if(this->primeQ == NULL || this->numberG == NULL)
    {
        printf("Values not initialized\n");
        exit(-1);
    }

    /* Convert the user string into a number */
    BIGNUM * componentInBinary = BN_new();
    if (size_component > 160)
        size_component = 160;
    componentInBinary = BN_bin2bn(component, size_component, componentInBinary);

	// Calculate random r
	BIGNUM * r = BN_new();
    BN_rand(r, this->keysize, -1, 0);

	// Calculate g^r
	BIGNUM * t_1 = BN_new();
	BN_CTX * ctx = BN_CTX_new();
    BN_mod_exp(t_1, this->numberG, r, this->primeQ, ctx);
    BN_CTX_free(ctx);

	// Calculate (g^r)^Xuser
	BIGNUM * aux = BN_new();
	ctx = BN_CTX_new();
	BN_mod_exp(aux, t_1, this->numberX_1, this->primeQ, ctx);
	BN_CTX_free(ctx);

	// Multiply the result
	BIGNUM * t_2 = BN_new();
	ctx = BN_CTX_new();
	BN_mod_mul(t_2, aux, componentInBinary, this->primeQ, ctx);
	BN_CTX_free(ctx);

	BN_clear_free(aux);
	BN_clear_free(r);
	BN_clear_free(componentInBinary);

	// Create pair (g^r, g^{r \times Xserver} \times d)
	return std::make_pair(t_1, t_2);
};

BIGNUM * User::UserDec(const std::pair<BIGNUM *, BIGNUM *> e)
{

	if(this->primeQ == NULL || this->numberG == NULL || this->numberX_1 == NULL)
    {
        printf("Values not initialized\n");
        exit(-1);
    }

	BIGNUM * aux = BN_new();
	BN_CTX * ctx = BN_CTX_new();
	BN_mod_exp(aux, e.first, this->numberX_1, this->primeQ, ctx);
	BN_CTX_free(ctx);

	ctx = BN_CTX_new();
	BIGNUM * aux2 = BN_mod_inverse(NULL, aux, this->primeQ, ctx);
	BN_CTX_free(ctx);

	BIGNUM * res = BN_new();
	ctx = BN_CTX_new();
	BN_mod_mul(res,  aux2, e.second, this->primeQ, ctx);
	BN_CTX_free(ctx);

	BN_clear_free(aux);
	BN_clear_free(aux2);

	return res;
}

/* WRAPPER FOR C */
extern "C" char * EncryptUserName(const char * original_content_name)
{
    User *u = new User(512); //TODO: change

    char * res_getUserK = retrieveKeyFromServer("userK:0");
    char * res_getPrimeQ = retrieveKeyFromServer("primeQ");

    BIGNUM * userKey = BN_new();
    BIGNUM * primeQ = BN_new();

    BN_hex2bn(&primeQ, res_getPrimeQ);
    BN_hex2bn(&userKey, res_getUserK);
    u->setUserKey(userKey);
    u->setPrimeQ(primeQ);

    BN_clear_free(primeQ);
    BN_clear_free(userKey);

    free(res_getPrimeQ);
    free(res_getUserK);

    /* split in keywords */
    std::vector<std::string> keywords;
    boost::split(keywords, original_content_name, boost::is_any_of("/"));

    std::vector<std::pair<BIGNUM *, BIGNUM *> > * vec = new std::vector<std::pair<BIGNUM *, BIGNUM *> >();
    for(std::vector<std::string>::iterator it = keywords.begin(); it != keywords.end(); it++)
    {
        std::string t = *it;
        if (t.compare("") == 0)
            continue;
        std::pair<unsigned char *, unsigned int> hashed_component = hash_message((const char*)(t.c_str()));

        std::pair<BIGNUM *, BIGNUM *> utrapdoor = u->UserTD(hashed_component.first, hashed_component.second);

        vec->push_back(utrapdoor);

        free(hashed_component.first);
    }

    char * new_user_content_names = u->FullUserTD(vec);
    //printf("%s\n", new_user_content_names);

    // Atach ccnx: at the begining
    int length = strnlen(new_user_content_names, 1 + ((SECURITY_KEYSIZE/4 + 1)* vec->size() * 2) + 1 + 1);
    char *str2 = (char*) calloc(sizeof(char), length + 5 + 1);
    strcpy(str2, "ccnx:");
    strcat(str2, new_user_content_names);

    for(std::vector<std::pair<BIGNUM *, BIGNUM *> >::iterator it2 = vec->begin(); it2 != vec->end(); it2++)
    {
        std::pair<BIGNUM *, BIGNUM *> p = *it2;

        BN_clear_free(p.first);
        BN_clear_free(p.second);

    }
    delete vec;

    delete u;

    free(new_user_content_names);

    return str2;
}

// Encode and encrypt the content
extern "C" char * EncryptUserContentNoNetwork(const unsigned char * original_content, const unsigned int content_len, const BIGNUM * primeQ, const BIGNUM * userKey)
{
	//std::pair<unsigned char *, unsigned int> hashed_component = encodeIntoBase64(original_content, content_len);
	User *u = new User(512); // TODO: CHANGE

    u->setPrimeQ(primeQ);
    u->setUserKey(userKey);
	std::pair<BIGNUM *, BIGNUM *> p1 = u->ContentProviderEnc(original_content, content_len);

	char * first_part_encr = BN_bn2hex(p1.first);
	char * second_part_encr = BN_bn2hex(p1.second);

	/* pack both in a string: append both parts with a pipe */
    char *encrypted_content = (char*) calloc(sizeof(char), (SECURITY_KEYSIZE/4 + 1) * 2 + 1 + 1);
    strcpy(encrypted_content, first_part_encr);
	strcat(encrypted_content, "|");
    strcat(encrypted_content, second_part_encr);

	/* Free all the elements */
	BN_clear_free(p1.first);
	BN_clear_free(p1.second);
	OPENSSL_free(first_part_encr);
	OPENSSL_free(second_part_encr);

	return encrypted_content;
}

extern "C" char * libprotector_EncryptUserContentWithKeys(const unsigned char * original_content, const unsigned int content_len, const char * user_key, const char * prime_q)
{
    BIGNUM * primeQ = BN_new();
    BIGNUM * userKey = BN_new();

    BN_hex2bn(&primeQ, prime_q);
    BN_hex2bn(&userKey, user_key);

    User *u = new User(512); // TODO: CHANGE

    u->setPrimeQ(primeQ);
    u->setUserKey(userKey);
	std::pair<BIGNUM *, BIGNUM *> p1 = u->ContentProviderEnc(original_content, content_len);
	delete u;

	char * first_part_encr = BN_bn2hex(p1.first);
	char * second_part_encr = BN_bn2hex(p1.second);

	/* pack both in a string: append both parts with a pipe */
    char *encrypted_content = (char*) calloc(sizeof(char), (SECURITY_KEYSIZE/4 + 1) * 2 + 1 + 1);
    strcpy(encrypted_content, first_part_encr);
	strcat(encrypted_content, "|");
    strcat(encrypted_content, second_part_encr);

	/* Free all the elements */
	BN_clear_free(p1.first);
	BN_clear_free(p1.second);
	OPENSSL_free(first_part_encr);
	OPENSSL_free(second_part_encr);

	BN_clear_free(primeQ);
	BN_clear_free(userKey);

	return encrypted_content;
}

// Encode and encrypt the content
extern "C" char * libprotector_EncryptUserContent(const unsigned char * original_content, const unsigned int content_len)
{
	//std::pair<unsigned char *, unsigned int> hashed_component = encodeIntoBase64(original_content, content_len);

	char * res_getUserK = retrieveKeyFromServer("userK:0");
    char * res_getPrimeQ = retrieveKeyFromServer("primeQ");

	char * encrypted_content = libprotector_EncryptUserContentWithKeys(original_content, content_len, res_getUserK, res_getPrimeQ);

	free(res_getPrimeQ);
	free(res_getUserK);

	return encrypted_content;
}

extern "C" unsigned char * libprotector_ReDecryptContentWithKeys(const char * encrypted_content, const char * res_getUserK, const char * res_getPrimeQ)
{
    BIGNUM * primeQ = BN_new();
    BIGNUM * userKey = BN_new();

    BN_hex2bn(&primeQ, res_getPrimeQ);
    BN_hex2bn(&userKey, res_getUserK);

    User *u = new User(512); // TODO: CHANGE

    u->setPrimeQ(primeQ);
    u->setUserKey(userKey);

	// Convert char into BIGNUM
	std::vector<std::string> keywords;
    boost::split(keywords, encrypted_content, boost::is_any_of("|"));

	BIGNUM *b1 = BN_new();
	BIGNUM *b2 = BN_new();

	const char * c1 = keywords[0].c_str();
	const char * c2 = keywords[1].c_str();
	BN_hex2bn(&b1, c1);
	BN_hex2bn(&b2, c2);

	std::pair<BIGNUM *, BIGNUM *> utrapdoor = std::make_pair(b1, b2);

	BIGNUM * res = u->UserDec(utrapdoor);
	char * res_char = (char *) calloc(sizeof(char), SECURITY_KEYSIZE/4 + 2);
	BN_bn2bin(res, (unsigned char *) res_char);

	delete u;

	BN_clear_free(res);

	BN_clear_free(b1);
	BN_clear_free(b2);

	BN_clear_free(userKey);
	BN_clear_free(primeQ);

	return res_char;
}

extern "C" unsigned char * libprotector_ReDecryptContent(const char * encrypted_content)
{
	char * res_getUserK = retrieveKeyFromServer("userK:0");
    char * res_getPrimeQ = retrieveKeyFromServer("primeQ");

	char * res_char = libprotector_ReDecryptContentWithKeys(encrypted_content, res_getUserK, res_getPrimeQ);

	free(res_getPrimeQ);
	free(res_getUserK);

	return (unsigned char *) res_char;//res_final;
}

extern "C" unsigned char * libprotector_ReDecryptAndSplitContent(const char * encrypted_content)
{
	char * res_getUserK = retrieveKeyFromServer("userK:0");
    char * res_getPrimeQ = retrieveKeyFromServer("primeQ");

	BIGNUM * primeQ = BN_new();
    BIGNUM * userKey = BN_new();

    BN_hex2bn(&primeQ, res_getPrimeQ);
    BN_hex2bn(&userKey, res_getUserK);

    User *u = new User(512); // TODO: CHANGE

    u->setPrimeQ(primeQ);
    u->setUserKey(userKey);

	// Convert char into BIGNUM
	std::vector<std::string> keywords;
    boost::split(keywords, encrypted_content, boost::is_any_of("|"));

    char * b64_message = (char *) calloc(sizeof(char), (SECURITY_KEYSIZE/4 + 2)*keywords.size()/2);
    b64_message[0] = '\0';
    int offset = 0;

    //printf("There are # %d keywords (%d)\n", keywords.size(), strlen(encrypted_content));

    for(unsigned int i=0; i<keywords.size(); i+=2)
    {
	    BIGNUM *b1 = BN_new();
	    BIGNUM *b2 = BN_new();

	    const char * c1 = keywords[i].c_str();
	    const char * c2 = keywords[i+1].c_str();
	    BN_hex2bn(&b1, c1);
	    BN_hex2bn(&b2, c2);

	    std::pair<BIGNUM *, BIGNUM *> utrapdoor = std::make_pair(b1, b2);

	    BIGNUM * res = u->UserDec(utrapdoor);
        //TODO: HERE IS THE PROBLEM!!!
	    char * aux = (char *) calloc(sizeof(char), SECURITY_KEYSIZE/4 + 2);
	    int copied_bytes = BN_bn2bin(res, (unsigned char *) aux);
	    BN_clear_free(res);
        memcpy(b64_message+offset, aux, copied_bytes);
        //printf("COPIED: %d\n", copied_bytes);
        offset += copied_bytes;

        free(aux);

        BN_clear_free(b1);
        BN_clear_free(b2);
    }
    b64_message[offset+1] = '\0';

    delete u;

    BN_clear_free(primeQ);
    BN_clear_free(userKey);

    free(res_getPrimeQ);
    free(res_getUserK);

    //printf("The base64 message is %s (%d vs %d)\n", b64_message, strlen(b64_message),  offset);

	return (unsigned char*) b64_message;
}

/* libprotector C interface */
extern "C" User * libprotector_User_new(unsigned int keysize)
{
    return new User(keysize);
}

extern "C" void libprotector_User_setUserKey(User * u, const char * user_key)
{
    BIGNUM * aux = BN_new();
    BN_hex2bn(&aux, user_key);
    reinterpret_cast<User *>(u)->setUserKey(aux);
    BN_clear_free(aux);
};

extern "C" void libprotector_User_setNumberG(User * u, const char * number_g)
{
    BIGNUM * aux = BN_new();
    BN_hex2bn(&aux, number_g);
    reinterpret_cast<User *>(u)->setNumberG(aux);
    BN_clear_free(aux);
};

extern "C" void libprotector_User_setPrimeQ(User * u, const char * prime_q)
{
    BIGNUM * aux = BN_new();
    BN_hex2bn(&aux, prime_q);
    reinterpret_cast<User *>(u)->setPrimeQ(aux);
    BN_clear_free(aux);
};

extern "C" void libprotector_User_setSalt(User * u, const char * salt_key)
{
    BIGNUM * aux = BN_new();
    BN_hex2bn(&aux, salt_key);
    reinterpret_cast<User *>(u)->setSalt(aux);
    BN_clear_free(aux);
};

extern "C" void libprotector_User_UserTD(User * u, const unsigned char * component, unsigned int size_component, char *& p1, char *& p2)
{
    std::pair<BIGNUM *, BIGNUM *> t = reinterpret_cast<User *>(u)->UserTD(component, size_component);
    unsigned int keysize = reinterpret_cast<User *>(u)->keysize;
    p1 = (char *) calloc(sizeof(char), ((keysize/4)) );
    char * aux_component = BN_bn2hex(t.first);
    memcpy((void *) (p1), (void *) aux_component, (keysize/4));

    p2 = (char *) calloc(sizeof(char), ((keysize/4)) );
    char * aux_component2 = BN_bn2hex(t.second);
    memcpy((void *) (p2), (void *) aux_component2, (keysize/4));

    free(aux_component);
    free(aux_component2);
}

extern "C" void libprotector_User_ContentTD(User * u, const unsigned char * block, const unsigned int block_size, char *& p1, char *& p2)
{
    std::pair<BIGNUM *, BIGNUM *> t = reinterpret_cast<User *>(u)->ContentProviderEnc(block, block_size);

    unsigned int keysize = reinterpret_cast<User *>(u)->keysize;
    p1 = (char *) calloc(sizeof(char), ((keysize/4)) );
    char * aux_component = BN_bn2hex(t.first);
    memcpy((void *) (p1), (void *) aux_component, (keysize/4));

    p2 = (char *) calloc(sizeof(char), ((keysize/4)) );
    char * aux_component2 = BN_bn2hex(t.second);
    memcpy((void *) (p2), (void *) aux_component2, (keysize/4));

    free(aux_component);
    free(aux_component2);
}

extern "C" void libprotector_User_ClientDec(User * u, const char * component1, const char * component2, char *& p1)
{
    unsigned int keysize = reinterpret_cast<User *>(u)->keysize;

    BIGNUM * aux = BN_new();
    BN_hex2bn(&aux, component1);

    BIGNUM * aux2 = BN_new();
    BN_hex2bn(&aux2, component2);

    std::pair<BIGNUM *, BIGNUM *> encrypted_payload = std::make_pair(aux, aux2);

    BIGNUM * decrypted_value = reinterpret_cast<User *>(u)->UserDec(encrypted_payload);

    p1 = (char *) calloc(sizeof(char), ((keysize/4)) );
    char * aux_component = BN_bn2hex(decrypted_value);

    BN_bn2bin(decrypted_value, (unsigned char *) p1);

    free(aux_component);
}