import * as LogAbstract from "@terrencecrowley/logabstract";
import * as Context from "@terrencecrowley/context";

interface TestEnv { context: Context.IContext, log: LogAbstract.ILog };

import * as OT from "../lib/ottypes";
import * as OTC from "../lib/otcomposite";
import * as OTA from "../lib/otarray";
import * as fs from 'fs';
import { testutil as testutil } from './testutil'

export namespace test {

	// function: test_transform
	//
	// Description:
	//	Takes a JSON file containing an array of OTCompositeResources.
	//	For each unit of four, composes the first two and compares with the final to see if they match
	//

	export function test_transform(env: TestEnv): void
		{
			env.log.log("===transform Tests===");
			let s: string = fs.readFileSync(testutil.find_data_path('transform.json'), 'utf8');
			let o: any = JSON.parse(testutil.strip_comments(s));
			let a: OTC.OTCompositeResource[] = [];

			for (let i = 0; i < o['tests'].length; i++)
				a[i] = OTC.OTCompositeResource.constructFromObject((o['tests'])[i]);
			let nFail: number = 0;
			let nTests: number = a.length / 4;

			for (let i = 0; i < a.length; i += 4)
			{
				let a1: OTC.OTCompositeResource = a[i].copy();
				let a2: OTC.OTCompositeResource = a[i+1].copy();
				let r1: OTC.OTCompositeResource = a[i+2].copy();
				let r2: OTC.OTCompositeResource = a[i+3].copy();

				let n: number = i/4;

				let tmp: OTC.OTCompositeResource = a1.copy();
				tmp.transform(a2, false);
				let bOK: boolean = tmp.effectivelyEqual(r1);
				if (!bOK)
					env.log.log("transformTest: " + n + " (1 with prior 2): " + (bOK ? "success" : "fail"));
				if (! bOK)
				{
					nFail++;
					env.log.log("\tActual: " + JSON.stringify(tmp), 1);
					env.log.log("\t  Goal: " + JSON.stringify(r1), 1);
				}

				tmp = a2.copy();
				tmp.transform(a1, true);
				bOK = tmp.effectivelyEqual(r2);
				if (!bOK)
					env.log.log("transformTest: " + n + " (2 with prior 1): " + (bOK ? "success" : "fail"));
				if (! bOK)
				{
					nFail++;
					env.log.log("\tActual: " + JSON.stringify(tmp), 1);
					env.log.log("\t  Goal: " + JSON.stringify(r2), 1);
				}
			}

			if (nFail == 0)
				env.log.log("transform Tests: " + String(nTests) + " successful.");
				env.log.log("transformTest: all successful.");
		}

	export function test_transform_random(env: TestEnv): void
		{
			const NTests: number = 1000;
			const InitStringLength: number = 12;
			let ops = new OTA.OTStringOperations();
			let nFail: number = 0;

			env.log.log("===Random transform Tests===");
			for (let i: number = 0; i < NTests; i++)
			{
				let a1: OTC.OTCompositeResource = new OTC.OTCompositeResource('1', '0');
				let sEdit1 = new OTA.OTStringResource("1");
				a1.edits.push(sEdit1);
				let a2: OTC.OTCompositeResource = new OTC.OTCompositeResource('1', '1');
				let sEdit2 = new OTA.OTStringResource("1");
				a2.edits.push(sEdit2);

				sEdit1.generateRandom(InitStringLength, a1.clientID);
				sEdit2.generateRandom(InitStringLength, a2.clientID);
				let t1 = a1.copy();
				t1.transform(a2, false);
				let sEditT1 = t1.edits[0] as OTA.OTStringResource;
				let t2 = a2.copy();
				t2.transform(a1, true);
				let sEditT2 = t2.edits[0] as OTA.OTStringResource;

				// Basic structural failure - no possible way it is correct
				let bStructuralFail: boolean = 
					sEdit2.finalLength() != sEditT1.originalLength()
					|| sEdit1.finalLength() != sEditT2.originalLength()
					|| sEditT1.finalLength() != sEditT2.finalLength();

				// If not structural failure, could logically not result in same string
				let bFunctionalFail: boolean = false;
				if (! bStructuralFail)
				{
					let arrRes1 = [ ops.constructN(InitStringLength) ];
					let arrRes2 = arrRes1.slice();
					arrRes1 = a2.apply(arrRes1);
					arrRes1 = t1.apply(arrRes1);
					arrRes2 = a1.apply(arrRes2);
					arrRes2 = t2.apply(arrRes2);
					bFunctionalFail = arrRes1[0] !== arrRes2[0];
				}

				if (bStructuralFail)
				{
					env.log.log("transformTest: " + i + ": Structural failure");
					nFail++;
				}
				else if (bFunctionalFail)
				{
					env.log.log("transformTest: " + i + ": Functional failure");
					nFail++;
				}

				// For regression testing, limit to simplest possible case
				if ((bStructuralFail || bFunctionalFail) && a1.length+a2.length < 10)
				{
					env.log.log(JSON.stringify(a1) + ",", 1);
					env.log.log(JSON.stringify(a2) + ",", 1);
					env.log.log(JSON.stringify(t1) + ",", 1);
					env.log.log(JSON.stringify(t2) + ",", 1);
					break;
				}
			}
			if (nFail > 0)
				env.log.log("Random transform Tests: " + (NTests - nFail) + " of " + NTests + " successful.");
			else
				env.log.log("Random transform Tests: all successful.");
		}

	export function tests(): Array<(env: TestEnv) => void> {
		let a = [ test.test_transform, test.test_transform_random ];
		return a;
	}
};
