// Two dimensional, 1 - time and 1 -space, discrete fde
#include <math.h>
#include <stream.h>
#include <stdlib.h>
#include "ObjProGen/errcode.h"
#include "ObjProDSP/portable.h"
#include "fde1.h"
#include "fde1sim.h"
#include "ObjProComGui/cgidbg.h"
#include "ObjProGui/yacintfc.h"

struct Power {
	double amp ;
	double dt ;
	double dx ;
	double c ;
	double csq ;
	void clear();
	Power(double c_val):c(c_val),csq(c_val*c_val){clear();}
	void sample(double * curr, double prev, double next);
	void sample(int32 * curr, int32 prev, int32 next);
	void dump(const char * label);
};

void Power::clear()
{
	amp = dt = dx = 0 ;
}

void Power::dump(const char * label)
{
	LogOut << label << ": dt = " << dt << ", dx = " << dx << " " << dx * csq  
		<< ", amp = " << amp << ", tot = " << dt + dx * csq  + amp << "\n" ;
}

#define SAMPLE_CODE \
	amp += (*curr * *curr + prev * prev + next * next)/3. ; \
	double tmp = *curr - prev ; \
	double t = tmp * tmp ; \
	tmp = next - *curr  ; \
	dt += (t + tmp*tmp) * .5 ; \
	tmp = curr[-1] - *curr ; \
	dx += tmp * tmp ;

void Power::sample(double * curr, double prev, double next)
{
	SAMPLE_CODE
}

void Power::sample(int32 * curr, int32 prev, int32 next)
{
	SAMPLE_CODE
}

struct Extrema {
	int MinX ;
	int MaxX ;
	double MinV ;
	double MaxV ;
	Extrema(int min_x, int max_x, double min_v, double max_v):
		MinX(min_x),
		MaxX(max_x),
		MinV(min_v),
		MaxV(max_v)
	{}
	int Equal(Extrema& Ck) ;
	int AnyGreater(Extrema& Ck) ;
	int AnyGreaterUpdate(int min_x, int max_x, double min_v, double max_v);
	void Print() const ;
};

int Extrema::Equal(Extrema&Ck)
{
	if (MinX != Ck.MinX) return 0 ;
	if (MaxX != Ck.MaxX) return 0 ;

	if (MinV != Ck.MinV) return 0 ;
	if (MaxV != Ck.MaxV) return 0 ;
	return 1 ;
}

int Extrema::AnyGreater(Extrema&Ck)
{
	if (MinX < Ck.MinX) return 1 ;
	if (MaxX > Ck.MaxX) return 1 ;

	if (MinV < Ck.MinV) return 1 ;
	if (MaxV > Ck.MaxV) return 1 ;
	return 0 ;
}

int Extrema::AnyGreaterUpdate(int min_x, int max_x, double min_v, double max_v)
{
	int DidUpdate = 0 ;

	if (MinX > min_x) {MinX = min_x; DidUpdate=1;}
	if (MaxX < max_x) {MaxX = max_x; DidUpdate=1;}

	if (MinV > min_v) {MinV = min_v; DidUpdate=1;}
	if (MaxV < max_v) {MaxV = max_v; DidUpdate=1;}
	if (DidUpdate) return 1 ;
	return 0 ;
}

void Extrema::Print() const
{
	LogOut << MinX << " : " << MaxX << " (" << MinV << "," <<
		MaxV << ")\n" ;
}

class Fde1Grid {
	int * Previous ;
	int * Current ;
	int * Next ;
	int next_output_index ;
	double * previous ;
	double * current ;
	double * next ;
	int TimeStep ;
	int interval ;
	int max_float_steps ;
	int Size ;
	int min_zero_index ;
	int MinZeroIndex ;
	int max_zero_limit ;
	int MaxZeroLimit ;
	double f ;
	Extrema * TheExtreme ;
	Extrema * extreme ;
	ZFde1 & param ;
	ErrCode state ;
	int truncate_scheme ;
	Power float_power ;
	Power int_power ;
public:
	Fde1Grid(int size, float c, int interv, ZFde1& p);
	void Simulate();
	void simulate();
	int output(int n) ;
	void Print();
	void print();
	void diff_print();
	void Kernel (int index);
	void kernel (int index);
	void SetValue(int Value, int Index);
	void Iterate(int N, int Prt);
	ErrCode iterate(int32 samples);
	void init(int32 * a, int sizea, int32 * b, int sizeb);
	void parameter_changed();
};

Fde1Grid::Fde1Grid(int size, float c, int interv, ZFde1& p):
	TheExtreme(0),
	float_power(c),
	int_power(c),
	extreme(0),
	min_zero_index(1),
	MinZeroIndex(1),
	max_zero_limit(size-1),
	MaxZeroLimit(size-1),
	Size(size),
	Previous(new int[size]),
	Current(new int[size]),
	Next(new int[size]),
	previous(0),
	current(0),
	next(0),
	TimeStep(0),
	next_output_index(-1),
	f(c),
	state(OK),
	interval(interv),
	param(p),
	truncate_scheme(p.GetDiscrScheme()),
	max_float_steps(p.GetDoubMaxSteps())
{
	// LogOut << "Fde1Grid ctor\n" ;
	if (max_float_steps > 0) {
		next= new double[size];
		previous = new double[size];
		current = new double[size];
		for (int i = 0 ; i <  Size;i++) previous[i] = current[i] = next[i] = 0 ;
	}
	for (int i = 0 ; i <  Size;i++) Previous[i] = Current[i] = Next[i] = 0 ;
	// LogOut << "Fde1Grid ctor exit\n" ;
}

void Fde1Grid::SetValue(int Val, int Index)
{
	Previous[Index] = Current[Index] = Val ;
	if (current) previous[Index] = current[Index] = Val ;
	// LogOut << "Prev["  <<  Index << "] = " << Previous[Index] << "\n" ;
}

void Fde1Grid::init(int32 * a, int sizea, int32 * b, int sizeb)
{
	int32 middle = Size/2 + 1 ;
	int32 start = middle - sizea/2 ;
	for (int32 i = 0 ; i < sizea ; i++) {
		int32 index = i + start ;
		Previous[index] = a[i] ;
		if (previous) previous[index] = a[i] ;
	}

	start = middle - sizeb/2 ;
	for (i = 0 ; i < sizeb ; i++) {
		int32 index = i + start ;
		Current[index] = b[i] ;
		if (current) current[index] = b[i] ;
	}
}


void Fde1Grid::Print()
{
	int PreviousPrint = 0 ;
	int DidPrint = 0 ;
	int OutCount = 0 ;
	for (int i = MinZeroIndex ; i < MaxZeroLimit ;i++) {
	/*
	 *	if (!Current[i]) {
	 *		PreviousPrint = 0 ;
	 *		continue ;
	 *	}
  	 */
		if (!PreviousPrint) LogOut << " [" << i << "] =" ;
		LogOut << " " << Current[i] ;
		if (++OutCount > 10) {
			LogOut << "\n" ;
			OutCount = 0 ;
		}
		DidPrint = PreviousPrint = 1 ;
	}
	if (DidPrint) LogOut << "\n" ;
}

void Fde1Grid::diff_print()
{
	int PreviousPrint = 0 ;
	int DidPrint = 0 ;
	int OutCount = 0 ;
	double max_diff = 0 ;
	if (!current) return ;
	for (int i = min_zero_index ; i < max_zero_limit ;i++) {
	/*
	 *	if (!current[i]) {
	 *		PreviousPrint = 0 ;
	 *		continue ;
	 *	}
  	 */
		if (!PreviousPrint) LogOut << " [" << i << "] =" ;
		double diff = current[i] - Current[i] ;
		LogOut << " " << diff ;
		if (diff < 0) diff = -diff ;
		if (diff > max_diff) max_diff = diff ;
		if (++OutCount > 10) {
			LogOut << "\n" ;
			OutCount = 0 ;
		}
		DidPrint = PreviousPrint = 1 ;
	}
	if (DidPrint) LogOut << "\n" ;
	LogOut << "Maximum difference: " << max_diff << "\n" ;
}
void Fde1Grid::print()
{
	int PreviousPrint = 0 ;
	int DidPrint = 0 ;
	int OutCount = 0 ;
	if (!current) return ;
	for (int i = min_zero_index ; i < max_zero_limit ;i++) {
	/*
	 *	if (!current[i]) {
	 *		PreviousPrint = 0 ;
	 *		continue ;
	 *	}
  	 */
		if (!PreviousPrint) LogOut << " [" << i << "] =" ;
		LogOut << " " << current[i] ;
		if (++OutCount > 10) {
			LogOut << "\n" ;
			OutCount = 0 ;
		}
		DidPrint = PreviousPrint = 1 ;
	}
	if (DidPrint) LogOut << "\n" ;
}

void Fde1Grid::kernel(int index)
{
	if (!current) return ;
	double * curr = current + index ;
	double * nxt = next + index ;
	double * prev = previous + index ;
	double Result ;

	*nxt =  2 * *curr - *prev - (2 * *curr - curr[-1] - curr[+1]) * f  ;

	float_power.sample(curr,*prev,*nxt);
	if (0) if(*curr) {
		LogOut << "Prev = " << *prev << ", Curr = " << *curr <<
			", Next = " << *nxt << "\n" ;
	}
}

void Fde1Grid::Kernel(int index)
{
	// LogOut<<"Kernel("<<index << "), curr = " << Current[index] << "\n";
	int * curr = Current + index ;
	int * nxt = Next + index ;
	int * prev = Previous + index ;
	int Result ;
	int lim = 0 ;
	

	double ToRound =  (2 * *curr - curr[-1] - curr[+1]) * f  ;
	if (ToRound < 0) Result = -(int) -ToRound  ;
	else Result = (int) ToRound  ;
	switch(truncate_scheme) {
default:
		state = FatalError ;
		return ;
case 0: // truncate towards 0
		break ;
case 1: // discretize to larger integer
		if  (ToRound != Result) if (*curr * ToRound < 0)
			if (ToRound > 0) Result++ ; else Result-- ;
		break ;
case 2: // rounded truncation 
		if (ToRound < 0) Result = - (int) ( - ToRound + .5) ;
		else Result = (int) (ToRound + .5) ;
		break ;
	}

	*nxt =  2 * *curr - *prev - Result ;
	int_power.sample(curr,*prev,*nxt);

	if (0) if(*curr) {
		LogOut << "Prev = " << *prev << ", Curr = " << *curr <<
			", Next = " << *nxt << "\n" ;
		LogOut << "ToRound = "  << ToRound << "\n" ;
	}
}

int Fde1Grid::output(int n)
{
	if (next_output_index < 0) return n ;
	if (n < 1) return n ;
	int float_out = current && TimeStep < max_float_steps ;
	int lim = max_zero_limit ;
	if (!float_out) lim = MaxZeroLimit ;
	for (; next_output_index <=  lim; next_output_index++) {
		param.WriteInteger(Current[next_output_index]) ;
		if (float_out) param.WriteInteger((int32) current[next_output_index]) ;
		else param.WriteInteger(0);
		if (--n < 1) return 0 ;
	}
	next_output_index = - 1 ;
	return n ;
}

ErrCode Fde1Grid::iterate(int n)
{
	n = output(n);
	if (!n) return state ;
	int PrtCnt = 0 ;
	for (;;) {
		int_power.clear();
		float_power.clear();
		Simulate();
		int float_out = current && TimeStep < max_float_steps ;
		if (float_out)  simulate();

		// int_power.dump("Int");
		// float_power.dump("Flt");

		if (State.IsError()) return FatalError ;
		if (PrtCnt++ >= interval) {
			PrtCnt = 0 ;
			// Print();
			// print();
			next_output_index = (float_out ? min_zero_index:MinZeroIndex) - 30 ;
			if (next_output_index < 0) next_output_index = 0 ;
			n = output(n);
			if (!n) return state ;
		}
	}
}

void Fde1Grid::Iterate(int N, int Prt)
{
	int PrtCnt = 0 ;
	for (int i = 0 ; i < N ; i++)  {
		Simulate();
		simulate();
		if (PrtCnt++ >= Prt) {
			PrtCnt = 0 ;
			Print();
			print();
			diff_print();
		}
		if (state >= EndOfData) return ;
	}
}

void Fde1Grid::Simulate()
{
	int i ;
	int NewMin = Size ;
	int NewMax = 0 ;
	int MinValue = 1 ;
	int MaxValue = -1 ;
	// LogOut << MinZeroIndex << " to " << MaxZeroLimit << "\n" ;
	int NoChange = 1 ;
	for (i = MinZeroIndex ; i < MaxZeroLimit ; i++) {
		Kernel(i);
		if (Current[i] || Next[i]) {
			if (i < NewMin) NewMin = i ;
			if (i > NewMax) NewMax = i ;
		}
		if (Next[i] < MinValue) MinValue = Next[i] ;
		if (Next[i] > MaxValue) MaxValue = Next[i] ;
		if (NoChange) {
			if (Current[i] != Next[i]) NoChange = 0 ;
			else if (Current[i] != Previous[i]) NoChange = 0 ;
		}
	}
	if (!TheExtreme) {
		TheExtreme = new Extrema(NewMin,NewMax,MinValue,MaxValue);
		// LogOut << TimeStep << " @ " ;
		// TheExtreme->Print();
	} else if (TheExtreme->AnyGreaterUpdate(NewMin,NewMax,MinValue,
	    MaxValue)) {
		// LogOut << TimeStep << " @ " ;
		// TheExtreme->Print();
	}
	if (NewMin < 1 || NewMax > Size-2) {
		LogOut << NewMin << " : " << NewMax << "\n" ;
		state = EndOfData ;
		return ;
	}
	if (NoChange) {
		LogOut << "No change.\n" ;
		state = EndOfData ;
		return ;
	}
	MinZeroIndex = NewMin -1 ;
	MaxZeroLimit = NewMax + 2 ;
	int * Temp = Previous ;
	Previous = Current ;
	Current = Next ;
	Next = Temp ;
	TimeStep++ ;
}

void Fde1Grid::simulate()
{
	int i ;
	int NewMin = Size ;
	int NewMax = 0 ;
	double MinValue = 1 ;
	double MaxValue = -1 ;
	if (!current) return ;
	// LogOut << min_zero_index << " to " << max_zero_limit << "\n" ;
	for (i = min_zero_index ; i < max_zero_limit ; i++) {
		kernel(i);
		if (current[i] || next[i]) {
			if (i < NewMin) NewMin = i ;
			if (i > NewMax) NewMax = i ;
		}
		if (next[i] < MinValue) MinValue = next[i] ;
		if (next[i] > MaxValue) MaxValue = next[i] ;
	}	
	if (!extreme) {
		extreme = new Extrema(NewMin,NewMax,MinValue,MaxValue);
		// LogOut << TimeStep << " @ " ;
		// extreme->Print();
	} else if (extreme->AnyGreaterUpdate(NewMin,NewMax,MinValue,
	    MaxValue)) {
		// LogOut << TimeStep << " @ " ;
		// extreme->Print();
	}
	if (NewMin < 1 || NewMax > Size-2) {
		LogOut << NewMin << " : " << NewMax << "\n" ;
		state = EndOfData ;
		return ;
	}
	min_zero_index = NewMin -1 ;
	max_zero_limit = NewMax + 2 ;
	double * temp = previous ;
	previous = current ;
	current = next ;
	next = temp ;
	// TimeStep++ ;
}


void  Fde1Grid::parameter_changed()
{
	// LogOut << "Fde1Grid::parameter_changed\n" ;
	interval = param.GetInterval();
	max_float_steps = param.GetDoubMaxSteps();
	truncate_scheme= param.GetDiscrScheme();
	// LogOut << "Fde1Grid::parameter_changed exit\n" ;
}

Fde1Sim::Fde1Sim(ZFde1& p):param(p)
{
	// LogOut << "Fde1Sim ctor\n" ;
	int32 Size = p.GetMaxSize() ;
	the_grid = new Fde1Grid(Size,p.GetFactor(),p.GetInterval(),p);
	the_grid->init( p.GetTime0(), p.GetTime0_Length(),
		p.GetTime1(), p.GetTime1_Length());
	// LogOut << "Fde1Sim ctor exit\n" ;
}

Fde1Sim::~Fde1Sim()
{
	delete the_grid ;
}

ErrCode Fde1Sim::simulate(int32 k)
{
	return the_grid->iterate(k);
}

void Fde1Sim::parameter_changed()
{
	// LogOut << "Fde1Sim::parameter_changed\n"  ;
	the_grid->parameter_changed();
	// LogOut << "Fde1Sim::parameter_changed exit\n"  ;
}


