
///////////////////////////////////////////////////////////////////////////////
//                                                                           //
//                                  Main                                     //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////
// (c) 2017 Miroslav Nemecek, Panda38@seznam.cz, panda38.sweb.cz

#include "..\include.h"

// performance timer
s64 TimeFreq = 10000000;	// timer frequency
s64 TimeStart;				// timer start mark

// input file
CText InFile;				// input file
CTextList RowList;			// list of input file rows
int InFileInx = 1;			// current input file row
CText Row;					// text of current input file row, without result, without variable and without comment
CText RowCom;				// comment at end of input file row (after '#', empty=no comment)
CText RowRes;				// row result (after '?', empty=no result)
BOOL ResOK;					// request to display result
bignum Res;					// row result
CText RowVar;				// variable (before '=')
int VarInx = -1;			// variable index (-1 = none)
int RowPos = 0;				// position in current input file row

// numbers
bint IntMax = BIGDEFINT;	// size of integer part (in bytes)
bint DecMax = BIGDEFDEC;	// size of decimal part (in bytes)
bint DecPrec = BIGDEFPREC;	// decimal precision (in digits)
bint IntRandMin = 1;		// min. number of digits of integer part of random numbers
bint DecRandMin = 1;		// min. number of digits of decimal part of random numbers
bint IntRand = BIGDEFPREC;	// max. number of digits of integer part of random numbers
bint DecRand = BIGDEFPREC;	// max. number of digits of decimal part of random numbers
bignum Var[VARNUM];			// variables a..z, A..Z
bignum Stack[STACKSIZE];	// stack of semiresults
int StackNum = 0;			// number of numbers in stack

// random generator
u64 RandSeed = 12345678;

///////////////////////////////////////////////////////////////////////////////
// get random number

u64 Rand()
{
	RandSeed = RandSeed*214013 + 2531011;
	return RandSeed;
}

u32 Rand32()
{
	RandSeed = RandSeed*214013 + 2531011;
	return (u32)(RandSeed >> 32);
}

// random number with limitation
u64 Rand(u64 max)
{
	if (max == 0) return 0;

	u8 bits = ::Bits64(max);

	u64 res;
	do {
		res = Rand() >> (64 - bits);
	} while (res > max);

	return res;
}

// random bit string of integer (bits = max. number of bits, returns real number of bits)
int RandBits(void* dst, int bits, bool noneg /* = false */)
{
	// real number of bits
	int b = (int)Rand((bits*2+1)*256+255);
	int rol = (b & 0xff);
	b >>= 8;
	bool neg = (b & 1) != 0;
	if (noneg) neg = false;
	b >>= 1;
	int i = b;

	// clear string
	memset(dst, neg ? -1 : 0, bits/8);

	// set 1 bit
	if (rol == 0)
	{
		if (i < bits)
		{
			u8* d = (u8*)dst;
			d[i/8] ^= (1 << (i & 3));
		}
		return b;
	}

	// write 32-bit samples
	u32* d32 = (u32*)dst;
	while (i >= 32)
	{
		*d32++ = Rand32();
		i -= 32;
		bits -= 32;
	}

	// write 8-bit samples
	u8* d = (u8*)d32;
	while (i >= 8)
	{
		*d++ = (u8)Rand32();
		i -= 8;
		bits -= 8;
	}

	// write rest number of bits
	if (i > 0)
	{
		*d = (u8)(((1 << i) - 1) & Rand32());
		if (neg) *d = ~ *d;
	}

	return b;
}

///////////////////////////////////////////////////////////////////////////////
// start performance timer (and start measuring time delta)

void StartTime()
{
	::QueryPerformanceFrequency((LARGE_INTEGER*)&TimeFreq);
	::QueryPerformanceCounter((LARGE_INTEGER*)&TimeStart);
}

///////////////////////////////////////////////////////////////////////////////
// get time delta in sec

double GetTime()
{
	s64 t;
	::QueryPerformanceCounter((LARGE_INTEGER*)&t);
	return (double)(t - TimeStart)/(double)TimeFreq;
}

///////////////////////////////////////////////////////////////////////////////
// load input file (return FALSE on error)

BOOL LoadInFile()
{
	// read input file
	if (!InFile.LoadFileName(INFILE) || (InFile.Length() < 5))
	{
		fprintf(stderr, "Cannot read input script file " INFILE "\n");
		return FALSE;
	}

	// split file to rows
	RowList.SplitLines(InFile);
	if (RowList.Num() < 1)
	{
		fprintf(stderr, "Invalid input script file " INFILE "\n");
		return FALSE;
	}

	// process first setup row
	CTextList lst;
	lst.Split(RowList[0], ',');
	if (lst.Num() < 8)
	{
		fprintf(stderr, "Invalid number of entries in first setup row of script file " INFILE "\n");
		return FALSE;
	}
	IntMax = DigToByte(lst[0].Int());
	DecMax = DigToByte(lst[1].Int());
	DecPrec = lst[2].Int();
	RandSeed = lst[3].Int();
	IntRandMin = lst[4].Int();
	DecRandMin = lst[5].Int();
	IntRand = lst[6].Int();
	DecRand = lst[7].Int();
	if (RandSeed == 0) RandSeed = (buint)::time(NULL);

	// initialize random
	int i;
	for (i = 10; i > 0; i--) ::Rand();

	// reinicialize numbers
	for (i = 0; i < VARNUM; i++) Var[i].SetMaxSize(IntMax, DecMax);
	for (i = 0; i < STACKSIZE; i++) Stack[i].SetMaxSize(IntMax, DecMax);
	Res.SetMaxSize(IntMax, DecMax);
	printf("%s\n", (LPCTSTR)RowList[0]);
	fprintf(stderr, "Setup: IntMaxSize = %lu B, DecMaxSize = %lu B\n", Res.IntMaxSize(), Res.DecMaxSize());

	// start time
	StartTime();

	// process rows
	for (InFileInx = 1; InFileInx < RowList.Num(); InFileInx++)
	{
		Row = RowList[InFileInx];
		if ((InFileInx == RowList.Num()-1) && Row.IsEmpty()) break;

		// prepare comment
		lst.Split(Row, '#', 2);
		if (lst.Num() > 0) Row = lst[0];
		RowCom.Empty();
		if (lst.Num() > 1) RowCom = lst[1];

		// prepare result from script
		lst.Split(Row, '?', 2);
		if (lst.Num() > 0) Row = lst[0];
		RowRes.Empty();
		Res.Set0();
		ResOK = FALSE;
		if (lst.Num() > 1)
		{
			RowRes = lst[1];
			Res.FromText(RowRes);
			ResOK = TRUE;
		}

		// prepare destination variable (and use as provisional result)
		lst.Split(Row, '=', 2);
		if (lst.Num() > 1)
			Row = lst[1];
		else if (lst.Num() > 0)
			Row = lst[0];
		Row.Trim();
		RowVar.Empty();
		VarInx = -1;
		if (lst.Num() > 1)
		{
			RowVar = lst[0];
			RowVar.Trim();
			if (RowVar.Length() > 0)
			{
				char ch = RowVar[0];
				if ((ch >= 'a') && (ch <= 'z')) VarInx = ch - 'a';
				if ((ch >= 'A') && (ch <= 'Z')) VarInx = ch - 'A' + 26;
				if (VarInx >= 0) Res.Copy(Var[VarInx]);
			}
		}

		// process command (store result to Res)
		if (Row.IsNotEmpty())
		{
			Parse();
		}

		// add variable
		if (VarInx >= 0)
		{
			if (Row.IsNotEmpty()) Var[VarInx].Copy(Res);
			Row = RowVar + " = " + Row;
		}

		// prepare result
		if (ResOK)
		{
			RowRes = Res.ToText(DecPrec);
			Row = Row + " ? " + RowRes;
		}

		// add comment
		if (RowCom.IsNotEmpty())
		{
			if (Row.IsNotEmpty()) Row += ' ';
			Row += '#';
			Row += RowCom;
		}

		// print row
		printf("%s\n", (LPCTSTR)Row);
	}

	// time
	double td = GetTime() - 0.000015;
	u64 us = (u64)(td*1000000);
	u32 sec = (u32)(us/1000000);
	us -= sec*1000000;
	u32 min = sec/60;
	sec -= min*60;
	u32 hour = min/60;
	min -= hour*60;
	fprintf(stderr, "Processed in %02u:%02u:%02u.%06u (hour:min:sec.us)\n", hour, min, sec, (u32)us);

	return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
// test bignum timming

void TestBigNumTime()
{
#define DIGINT 50000
#define DIGDEC 50000

	bignum num1((buint)(DIGINT*BYTEPERDIG)+5, (buint)(DIGDEC*BYTEPERDIG)+5);
	bignum num2((buint)(DIGINT*BYTEPERDIG)+5, (buint)(DIGDEC*BYTEPERDIG)+5);
	bignum num3((buint)(2*DIGINT*BYTEPERDIG)+5, (buint)(2*DIGDEC*BYTEPERDIG)+5);
	//bignum num4((buint)(2*DIGINT*BYTEPERDIG)+5, (buint)(2*DIGDEC*BYTEPERDIG)+5);
	num1.RandRaw(DIGINT, DIGDEC, DIGINT, DIGDEC);
	num2.RandRaw(DIGINT, DIGDEC, DIGINT, DIGDEC);
	CText t, t2;
	//num2.Copy(num1);
	//num3.Mul(num1, num2);
	//num1.RandRaw(DIGINT, DIGDEC, DIGINT, DIGDEC);

	t = num1.ToText();

	int i;
	StartTime();
	int k = 100;

	for (i = k; i > 0; i--)
	{
		num3.Copy(num1);
		num3.Mul(num2);//(buint)1234565578912345678);
	}
	double td2 = GetTime();
	bint bytes = num2.IntSize() + num2.DecSize();

	printf("ns = %d, bytes = %lu, digits = %u\n", (int)(td2*1000000000/k+0.8), bytes, t.Length());
}




///////////////////////////////////////////////////////////////////////////////
// main function

int main(int argc, char* argv[])
{
	// create or verify bit tables (returns FALSE on table error)
	if (!InitBitTab())
	{
		fprintf(stderr, "Invalid bit tables!\n");
		return 1;
	}

#ifdef X86
	// check X64 compiler compatibility
	if (CheckComp_x64(123, 456, 789, 321, 654, 987) != 1)
	{
		fprintf(stderr, "Compiler error CheckComp_x64\n");
		return 1;
	}
#endif // X86

/*
	fix128 p, q;
	CText t;
	p.Set((s64)30); p.DegRad(); p.Tan(); t = p.ToText();
	p.Set((s64)45); p.DegRad(); p.Tan(); t = p.ToText();
	p.Set((s64)60); p.DegRad(); p.Tan(); t = p.ToText();
	p.Set((s64)90); p.DegRad(); p.Tan(); t = p.ToText();
	p.Set((s64)-30); p.DegRad(); p.Tan(); t = p.ToText();
	p.Set((s64)-60); p.DegRad(); p.Tan(); t = p.ToText();

	t = t;
*/

/*
	fix256 p0, p, q, r;
	CText t, t2, t3;
	s64 i;
	p0.Set((s64)10);//000000000000000000ULL);
	p0.Sqrt();
	for (i = -10; i <=10; i++)
	{
		p.Pow(p0, i);
		t = p.ToText();

		q.Set(pow(p0.Double(), (double)i));
		t2 = q.ToText();

		t2=t;

		p.Exp10();
		t = p.ToText();
		t2 = p0.ToText();

		t2 = t;
	}
*/


/*
	int i;
	fix64 p, q;
	double p2, q2, q3;
	for (i = 10000000; i > 0; i--)
	{
		do RandBits(&p, 64, true); while (p.IsZero());
		q.Sqrt(p);

		p2 = p.Double();
		q2 = q.Double();

		q3 = sqrt(p2);

		if (fabs(q2 - q3) > 0.0000001)
		{
			q2 = q3;
		}
	}

	q2 = q3;
*/

/*
	int i;
	uint64B kk;
	CText kt;
	for (i = 0; i <= (int)uint64::FactMax+1; i++)
	{
		kk.Fact(i);
		kt = kk.ToText();
		i=i;
	}

	sint64B kk2;
	for (i = 0; i <= (int)sint64::FactMax+1; i++)
	{
		kk2.Fact(i);
		kt = kk2.ToText();
		i=i;
	}

	fix64B kk3;
	for (i = 0; i <= (int)fix64::FactMax+1; i++)
	{
		kk3.Fact(i);
		kt = kk3.ToText();
		i=i;

		kk3.FactRec(i);
		kt = kk3.ToText();
		i=i;
	}
*/

	// check double integer library (compile with WIN64 to check ASM)
#define TESTNUM 100000000

#ifdef TESTNUM

	Check_dbluint(TESTNUM);
	Check_dblsint(TESTNUM);
	Check_dblfix(TESTNUM);
	Check_quaduint(TESTNUM);
	Check_quadsint(TESTNUM);
	Check_quadfix(TESTNUM);
	Check_quadudbl(TESTNUM);
	Check_quadsdbl(TESTNUM);

#ifdef WIN64
	Check_dbluint_asm(TESTNUM);
	Check_dblsint_asm(TESTNUM);
	Check_quaduint_asm(TESTNUM);
	Check_quadsint_asm(TESTNUM);
#endif // WIN64

#endif // TESTNUM



	//// load input file
	//if (!LoadInFile()) return 1;
	////// test bignum timming
	//TestBigNumTime();


#if 0 // test text conversions

#define BASEUINT u64		// base integer unsigned
#define BASESINT s64		// base integer signed
#define DBLFIX fix256		// double fixed point class
#define DBLSINT sint256

#define BASEBITS (sizeof(BASEUINT)*8)	// number of bits of base integer
#define BASELAST ((BASEUINT)1 << (BASEBITS-1)) // highest bit of base integer
#define BASENOT  (~(BASEUINT)0)			// all bits '1' (or ((BASEUINT)-1) )
#define DBLFIX_MAX ((double)(BASELAST-1)*BASELAST*2 + (double)BASENOT + \
	(double)BASENOT/BASELAST/2 + (double)BASENOT/BASELAST/2/BASELAST/2) // max. decimal number as double
#define DBLFIX_MIN (-(double)BASELAST*BASELAST*2) // min. decimal number as double

	DBLFIX a, b, c;
	DBLSINT k;
	//a.Mul(1234, -56);
	//b.Set(-178.25);

	//a.Set(3.14159265358979323846264338327950288419716939937510);
	//a.Set(-12345678987654321.234567898765432);
	//a.Add(3.14159265358979323846264338327950288419716939937510);
	//a.Add(0.00000000000314159265358979323846264338327950288419716939937510);
	//((DBLSINT*)&a)->Neg();

		//(double)0x123456789abcdef0ULL * (1ULL << 32));
	//double r = a.Double();
	//u64 z = (u64)(r / (1ULL << 32));

	//a = (BASESINT)-100;//-12.3456;
	//a.Div(a, (BASESINT)6);
	////((DBLSINT*)&a)->Inc();

	//a.FromText(CText("-3.14159265358979323846264338327950288419716939937510"));

	a.FromText(CText("92345678909876543212345678909876543210.12345678909876543212345678909876543215"));

	////k.Set((s64)13);
	////a.TestFastDiv(a, k);
	////a.MulHigh(a, b, &c);
	////a.N0() = a.N1();
	////a.N1() = c.N0();
	//double r = a.Double();
	CText t;
	t = a.ToText();
	////double q = c.Double();

	return 0;

#endif

/*
	// test dblint timming
	TestDblIntTime();

	// test quadint timming
	TestQuadIntTime();
*/

	return 0;
}
