#include "Gdr.h"
#include <iostream.h>
#include <fstream.h>

/*******************************************************************/

void Layer :: Initialize(long N,Function F,Function D)
{
	Destroy();
	No = N;
	Neuron = new double [No];
	Func = F;
	DerF = D;
}

double Layer :: Get(long i)
{
	return Neuron [i];
}

void Layer :: Set(long i,double val)
{
	Neuron[i] = val;
}

void Layer :: ApplyFunc(double al)
{
	for(long i = 0;i < No;i ++)
	{
		Neuron[i] = Func(Neuron[i],al);
	}
}

double Layer :: Derivative(long i)
{
	return DerF(Neuron[i],1);
}

long Layer :: NoOfNeurons()
{
	return No;
}

/*void Layer :: operator = (Layer L)
{
	No = L.No;
	if(Neuron != NULL)
		delete [] Neuron;
	Neuron = new double [No];
	for(long i = 0;i < No;i ++)
	{
		Neuron[i] = L.Neuron[i];
	}
	Func = L.Func;
	DerF = L.DerF;
}*/



/*
Layer :: Layer (Layer & L) {
	*this = L;
}
*/

void Layer :: SetDouble(double D[])
{
	for(long i = 0;i < No;i ++)
	{
		D[i] = Neuron[i];
	}
}

/****************************************************************/

void Weight :: Initialize(long P,long N)
{
	Destroy();
	Present = P;
	Next = N;
	Wt = new double * [P];
	for(long i = 0;i < P;i ++)
	{
		Wt[i] = new double [N];
	}
	time_t t;
	srand((unsigned) time (&t));
	for(i = 0;i < P;i ++)
	{
		for(long j = 0;j < N;j ++)
		{
			Wt[i][j] = rand() % 100;
			Wt[i][j] /= 10000;
		}
	}
}

double Weight :: Get(long i,long j)
{
	return Wt[i][j];
}

void Weight :: Add(long i,long j,double Delta)
{
	Wt[i][j] += Delta;
}

/******************************************************************/

void Network ::Initialize(int N)
{
	Destroy();
	No = N;
	L = new Layer [N];
	W = new Weight [N - 1];
}

/*void Network :: Initialize(long I,long H,long O,double Lrate)
{
	L[0].Initialize(I,Linear,DerLin);
	L[1].Initialize(H,Sigmoid,DerSig);
	L[2].Initialize(O,Sigmoid,DerSig);
	LRate = Lrate;
	W[0].Initialize(I,H);
	W[1].Initialize(H,O);
}*/

void Network :: Initialize(long * N,double Lrate)
{
	for(int i = 0;i < No;i ++)
	{
		L[i].Initialize(N[i],Sigmoid,DerSig);
	}
	LRate = Lrate;
	for(i = 0;i < (No - 1);i ++)
	{
		W[i].Initialize(N[i],N[i + 1]);
	}
}

void Network :: SetInput(Layer & I)
{
	for(long i = 0;i < L[0].NoOfNeurons();i ++)
	{
		L[0].Set(i,I.Get(i));
	}
}

void Network :: SetInput(int * I)
{
	for(long i = 0;i < L[0].NoOfNeurons();i ++)
	{
		L[0].Set(i,I[i]);
	}
}

void Network :: GetOutput(Layer & O)
{
	//Layer O;
	//O.Initialize(L[No - 1].NoOfNeurons(),Sigmoid,DerSig);
	for(long i = 0;i < L[No - 1].NoOfNeurons();i ++)
	{
		O.Set(i,L[No - 1].Get(i));
	}
	//return O;
}

void Network :: CalcOutput()
{
	double o;
	//for(long k = 0;k < 2;k ++)
	for(long k = 0;k < (No - 1);k ++)
	{
		for(long i = 0;i < L[k + 1].NoOfNeurons();i ++)
		{
			o = 0;
			for(long j = 0;j < L[k].NoOfNeurons();j ++)
			{
				o += L[k].Get(j) * W[k].Get(j,i);
			}
			L[k + 1].Set(i,o);
		}
		L[k + 1].ApplyFunc(1);
	}
}

	/* Training with a single test set */
	/* Can be used for Face Recognition */

void Network :: Train(Layer & I,Layer & Op,double Th)
{
	double * O = new double [ L[No - 1].NoOfNeurons() ];
	double * T = new double [ L[No - 1].NoOfNeurons() ];
	double Delta;
	double * Doe;
	double * Doe2;
	double E;
	do
	{
		SetInput(I);
		CalcOutput();
		L[No-1].SetDouble(O);
		Op.SetDouble(T);
		E = 0;
		for(long i = 0;i < L[No - 1].NoOfNeurons();i ++)
		{
			E += (T[i] - O[i]) * (T[i] - O[i]);
		}
		E /= 2;
		cout << E << endl;
		if(E > Th)
		{
			Doe = new double [ L[No - 1].NoOfNeurons() ];
			for(i = 0;i < L[No - 1].NoOfNeurons();i ++)
			{
				Doe[i] = (T[i] - O[i]) * L[No - 1].Derivative(i);
			}
			for(i = 0;i < L[No - 2].NoOfNeurons();i ++)
			{
				for(long j = 0;j < L[No - 1].NoOfNeurons();j ++)
				{
					Delta = LRate * Doe[j] * L[No - 2].Get(i);
					W[No - 2].Add(i,j,Delta);
				}
			}
			
			for(int k = No - 2;k > 0;k --)
			{
				Doe2 = new double [ L[k].NoOfNeurons() ];
				for(i = 0;i < L[k].NoOfNeurons();i ++)
				{
					Delta = 0;
					for(long j = 0;j < L[k+1].NoOfNeurons();j ++)
					{
						Delta += Doe[j] * W[k].Get(i,j);
					}
					Doe2[i] = Delta * L[k].Derivative(i);
				}
				for(i = 0;i < L[k - 1].NoOfNeurons();i ++)
				{
					for(long j = 0;j < L[k].NoOfNeurons();j ++)
					{
						Delta = LRate * Doe2[j] * L[k-1].Get(i);
						W[k-1].Add(i,j,Delta);
					}
				}
				delete [] Doe;
				Doe = new double [ L[k].NoOfNeurons() ];
				for(i = 0;i < L[k].NoOfNeurons();i ++)
				{
					Doe[i] = Doe2[i];
				}
				delete [] Doe2;
			}
		}
	}while(E > Th);
}

	/* Training with multiple training inpute */
	/* Can be used for heature extraction	*/

double Network ::Train(Layer & I,Layer & Op,double Th,int Dummy)
{
	double * O = new double [ L[No - 1].NoOfNeurons() ];
	double * T = new double [ L[No - 1].NoOfNeurons() ];
	double Delta;
	double * Doe;
	double * Doe2;
	double E;

	SetInput(I);//[count]);
	CalcOutput();
	L[No-1].SetDouble(O);
	Op/*[count]*/.SetDouble(T);
	E = 0;
	for(long i = 0;i < L[No - 1].NoOfNeurons();i ++)
	{
		E += (T[i] - O[i]) * (T[i] - O[i]);
	}
	E /= 2;
	//cout << E << endl;
	if(E > Th)
	{
		Doe = new double [ L[No - 1].NoOfNeurons() ];
		for(i = 0;i < L[No - 1].NoOfNeurons();i ++)
		{
			Doe[i] = (T[i] - O[i]) * L[No - 1].Derivative(i);
		}
		for(i = 0;i < L[No - 2].NoOfNeurons();i ++)
		{
			for(long j = 0;j < L[No - 1].NoOfNeurons();j ++)
			{
				Delta = LRate * Doe[j] * L[No - 2].Get(i);
				W[No - 2].Add(i,j,Delta);
			}
		}
		
		for(int k = No - 2;k > 0;k --)
		{
			Doe2 = new double [ L[k].NoOfNeurons() ];
			for(i = 0;i < L[k].NoOfNeurons();i ++)
			{
				Delta = 0;
				for(long j = 0;j < L[k+1].NoOfNeurons();j ++)
				{
					Delta += Doe[j] * W[k].Get(i,j);
				}
				Doe2[i] = Delta * L[k].Derivative(i);
			}
			for(i = 0;i < L[k - 1].NoOfNeurons();i ++)
			{
				for(long j = 0;j < L[k].NoOfNeurons();j ++)
				{
					Delta = LRate * Doe2[j] * L[k-1].Get(i);
					W[k-1].Add(i,j,Delta);
				}
			}
			delete [] Doe;
			Doe = new double [ L[k].NoOfNeurons() ];
			for(i = 0;i < L[k].NoOfNeurons();i ++)
			{
				Doe[i] = Doe2[i];
			}
			delete [] Doe2;
		}
	}
	return E;
}


/*******************************************************************/

// Added by Rajesh
// Default Constructors 
// Make all pointers NULL is all I do here

Layer::Layer() {
	Neuron = NULL;
	Func = NULL;
	DerF = NULL;
}

Weight::Weight() {
	Wt = NULL;
}

Network::Network() {
	L = NULL;
	W = NULL;
}


// Destroy methods ... Same as destructors 
// I use these to destory the contents of an object
// before reading into them from file ...

void Layer::Destroy() {
	if(Neuron != NULL) {
		delete [] Neuron;
		Neuron = NULL;
	}
}

void Weight::Destroy() {
	if(Wt != NULL) {
		for(long i = 0;i < Present;i ++)
			delete [] Wt[i] ;
		delete [] Wt;
		Wt = NULL;
	}	
}

void Network::Destroy() {
	if(L != NULL) {
		delete [] L;
		L = NULL;
	}
	if( W != NULL) {
		delete [] W;
		W = NULL;
	}
}

void Network::DestroyAll() {
	for ( int i = 0 ; i < No ; i++ ) {
		L[i].Destroy();
		if( i < No - 1 )
			W[i].Destroy();
	}
	Destroy();
}

// Destructors : Call their respective Destroy methods

Layer::~Layer() {
//	Destroy();
}

Weight::~Weight() {
//	Destroy();
}

Network :: ~Network() {
//	DestroyAll();
}

// Extraction and Insertion operators overloaded

ostream& operator << ( ostream & os , Layer & l ) {
	
	os.write((char*)&l,sizeof(Layer));
	os.write((char*)l.Neuron,l.NoOfNeurons()*sizeof(double));
	
	int code;
	if(l.Func == Sigmoid)
		code = Layer::Sig;
	else if(l.Func == Linear)
		code = Layer::Lin;

	os.write((char*)&code,sizeof(code));
			
	if(l.DerF == DerSig)
		code = Layer::DSig;
	else if(l.DerF == DerLin)
		code = Layer::DLin;

	os.write((char*)&code,sizeof(code));

	return os;
}

istream& operator >> ( istream & is , Layer & l ) {
	
	l.Destroy();

	is.read((char*)&l,sizeof(Layer));

	l.Neuron = new double [l.No];

	is.read((char*)l.Neuron, l.No * sizeof(double));

	int code;

	is.read((char*)&code,sizeof(code));

	if(code == Layer::Sig)
		l.Func = Sigmoid;
	else if(code == Layer::Lin)
		l.Func = Linear;
			
	is.read((char*)&code,sizeof(code));

	if(code == Layer::DSig)
		l.DerF = DerSig;
	else if(code == Layer::DLin)
		l.DerF = DerLin;


	return is;
}

ostream& operator << ( ostream & os , Weight & w ) {
	
	os.write((char*)&w,sizeof(Weight));

	for(long i = 0;i < w.Present;i ++) {
		os.write( (char*) w.Wt[i] , w.Next * sizeof(double) );
	}
	return os;
}

istream& operator >> ( istream & is , Weight & w ) {

	w.Destroy();

	is.read((char*)&w,sizeof(Weight));

	w.Wt = new double * [w.Present];
	
	for(long i = 0;i < w.Present;i ++) {
		w.Wt[i] = new double [w.Next];
		is.read( (char*) w.Wt[i] , w.Next * sizeof(double) );
	}

	return is;
}

ostream& operator << ( ostream & os , Network & n ) {
	
	os.write((char*)&n,sizeof(Network));

	for(int i = 0;i < n.No;i ++)
		os << n.L[i];

	for(i = 0;i < (n.No - 1);i ++)
		os << n.W[i];
	
	return os;
}

istream& operator >> ( istream & is , Network & n ) {
	
	n.Destroy();

	is.read((char*)&n,sizeof(Network));

	n.L = new Layer [n.No];
	n.W = new Weight [n.No - 1];

	for(int i = 0;i < n.No;i ++)
		is >> n.L[i];

	for(i = 0;i < (n.No - 1);i ++)
		is >> n.W[i];
	
	return is;
}

bool Network::ReadFromFile(char * szFileName) {
	ifstream f;
	f.open(szFileName,ios::binary);
	if(!f)
		return false;
	f >> *this;
	f.close();
	return true;
}

bool Network::WriteToFile (char * szFileName) {
	ofstream f;
	f.open(szFileName,ios::binary);
	if(!f)
		return false;
	f << *this;
	f.close();
	return true;
}


// Added by Rajesh