#include <time.h>
#include "test.h"
#include "../extendedmatrix.h"

#define FF_OPTIMIZATION			0
#define FF_OPERATION				1	
#define FF_MODE							2
#define FF_A								3
#define FF_B								4
#define FF_ROW							5
#define FF_COL							6
#define FF_ROWS							7
#define FF_COLS							8
#define FF_REPS							9
#define FF_SIZE							10
#define FF_SPEEDTEST				11

static void SInsertR(int Size, int Reps, int Row, int Rows, int Optimized);
static void SInsertC(int Size, int Reps, int Col, int Cols, int Optimized);
static void TInsertR(matrix *A, matrix *B, int Row, int Mode, int Optimized);
static void TInsertC(matrix *A, matrix *B, int Col, int Mode, int Optimized);
static void SRemoveR(int Size, int Reps, int Row, int Rows, int Optimized);
static void SRemoveC(int Size, int Reps, int Col, int Cols, int Optimized);
static void TRemoveR(matrix *A, int Row, int Rows, int Mode, int Optimized);
static void TRemoveC(matrix *A, int Col, int Cols, int Mode, int Optimized);
static void SInsertNullR(int Size, int Reps, int Row, int Rows, int Optimized);
static void SInsertNullC(int Size, int Reps, int Col, int Cols, int Optimized);
static void TInsertNullR(matrix *A, int Row, int Rows, int Mode, int Optimized);
static void TInsertNullC(matrix *A, int Col, int Cols, int Mode, int Optimized);
static void TSwapR(matrix* A, matrix* B, int Row, int Optimized);
static void TSwapC(matrix* A, matrix* B, int Col, int Optimized);
static void SSwapR(int Size, int Reps, int Row, int Optimized);
static void SSwapC(int Size, int Reps, int Col, int Optimized);
static void TOptimizeR(matrix *A);
static void TOptimizeC(matrix *A);

static void PrintPercent(int num, int denom);
static matrix* AllocateMatrices(int rows, int cols, int Reps, int Transpose);
static void DeallocateMatrices(int reps, matrix *m);
void SetDefaults(char* optimization, char* Operation, int *Mode, matrix *A, matrix *B, int *Row, int *Column, int* Rows, int *Columns, int *Reps, int *Size, char *speedtest);

int main(int argc, char* argv[])
{
	FILE* input;
	char Desc[] = "string optimization, string operation, int mode, matrix A, matrix B, int row, int column, int rows, int columns, int repetitions, int size, string speedtest";
	char Operation[128], optimization[128], speedtest[128];
	matrix A, B;
	int *Filled;
	int Mode, Row, Column, Rows, Columns, Reps, Size, Optimized, SpeedTest;
	MemoryManager mem;
	
	if (! (input = ParseCmdLine(argc,argv)))
	{
		return 0;
	}
	
	mem = CreateMemoryManager(5, 2, 0, 0);
	SetDefaults(optimization, Operation, &Mode, &A, &B, &Row, &Column, &Rows, &Columns, &Reps, &Size, speedtest);
	while (ParseStructure(input, Desc, &Filled, &mem, optimization, Operation, &Mode, &A, &B, &Row, &Column, &Rows, &Columns, &Reps, &Size, speedtest))
	{		
		Optimized = ! strcmp(optimization, "yes");
		if (Optimized) Mode = -1;
		SpeedTest = ! strcmp(speedtest, "yes");
		if (! Filled[FF_OPERATION])
		{
			printf("Warning: No operation code specified. Skipping problem.\n");
		}
		else if (! strcmp(Operation, "InsertRows"))
		{
			if (SpeedTest)
			{
				SInsertR(Size, Reps, Row, Rows, Optimized);
			}
			else
			{
				TInsertR(&A, &B, Row, Mode, Optimized);
			}
		}
		else if (! strcmp(Operation, "InsertColumns"))
		{
			if (SpeedTest)
			{
				SInsertC(Size, Reps, Column, Columns, Optimized);
			}
			else
			{
				TInsertC(&A, &B, Column, Mode, Optimized);
			}
		}
		else if (! strcmp(Operation, "InsertNullRows"))
		{
			if (SpeedTest)
			{
				SInsertNullR(Size, Reps, Row, Rows, Optimized);
			}
			else
			{
				TInsertNullR(&A, Row, Rows, Mode, Optimized);
			}
		}
		else if (! strcmp(Operation, "InsertNullColumns"))
		{
			if (SpeedTest)
			{
				SInsertNullC(Size, Reps, Column, Columns, Optimized);
			}
			else
			{
				TInsertNullC(&A, Column, Columns, Mode, Optimized);
			}
		}
		else if (! strcmp(Operation, "RemoveRows"))
		{
			if (SpeedTest)
			{
				SRemoveR(Size, Reps, Row, Rows, Optimized);
			}
			else
			{
				TRemoveR(&A, Row, Rows, Mode, Optimized);
			}
		}
		else if (! strcmp(Operation, "RemoveColumns"))
		{
			if (SpeedTest)
			{
				SRemoveC(Size, Reps, Column, Columns, Optimized);
			}
			else
			{
				TRemoveC(&A, Column, Columns, Mode, Optimized);
			}
		}
		else if (! strcmp(Operation, "SwapRows"))
		{
			if (SpeedTest)
			{
				SSwapR(Size, Reps, Row, Optimized);
			}
			else
			{
				TSwapR(&A, &B, Row, Optimized);
			}
		}
		else if (! strcmp(Operation, "SwapColumns"))
		{
			if (SpeedTest)
			{
				SSwapC(Size, Reps, Column, Optimized);
			}
			else
			{
				TSwapC(&A, &B, Column, Optimized);
			}
		}
		else if (! strcmp(Operation, "OptimizeRows"))
		{
			TOptimizeR(&A);
		}
		else if (! strcmp(Operation, "OptimizeColumns"))
		{
			TOptimizeC(&A);
		}
		else
		{
			printf("Warning: Unrecognized operation '%s'.\n", Operation);
		}
		printf("-------------------------------------------------------------------------------\n");
		FreeMemory(&mem);
		mem = CreateMemoryManager(10, 2, 0, 0);
		SetDefaults(optimization, Operation, &Mode, &A, &B, &Row, &Column, &Rows, &Columns, &Reps, &Size, speedtest);
	}
	FreeMemory(&mem);
	return 0;
}

void SInsertR(int Size, int Reps, int Row, int Rows, int Optimized)
{
	printf("Speed-testing row insertion (%s).\n", Optimized ? "optimized" : "unoptimized");
	printf("Inserting %d rows into a square matrix of size %d, %d times...\n", Rows, Size, Reps);
	
	matrix *outer = AllocateMatrices(Size, Size, Reps, ! Optimized);
	matrix *inner = AllocateMatrices(Rows, Size, Reps, ! Optimized);
	ShowMatrix(outer, "Sample Matrix Before");
	clock_t t0 = clock();
	int i;
	for (i = 0; i < Reps; i++)
	{
		InsertRows(outer + i, inner + i, Row, -1);
	}
	double time = ((double) (clock() - t0)) / ((double) CLOCKS_PER_SEC);
	ShowMatrix(outer, "Sample Matrix After");
	printf("Time: %.2f ms\n", time * 1000.0); 
	DeallocateMatrices(Reps, outer);
}

void SInsertC(int Size, int Reps, int Col, int Cols, int Optimized)
{
	printf("Speed-testing column insertion (%s).\n", Optimized ? "optimized" : "unoptimized");
	printf("Inserting %d columns into a square matrix of size %d, %d times...\n", Cols, Size, Reps);
	
	matrix *outer = AllocateMatrices(Size, Size, Reps, Optimized);
	matrix *inner = AllocateMatrices(Size, Cols, Reps, Optimized);
	ShowMatrix(outer, "Sample Matrix Before");
	clock_t t0 = clock();
	int i;
	for (i = 0; i < Reps; i++)
	{
		InsertColumns(outer + i, inner + i, Col, -1);
	}
	double time = ((double) (clock() - t0)) / ((double) CLOCKS_PER_SEC);
	ShowMatrix(outer, "Sample Matrix After");
	printf("Time: %.2f ms\n", time * 1000.0); 
	DeallocateMatrices(Reps, outer);
}

void TInsertR(matrix *A, matrix *B, int Row, int Mode, int Optimized)
{
	printf("Testing row insertion: B -> A at row %d...\n", Row);
	printf("Mode: %d. Optimization: %s.\n", Mode, Optimized ? "Yes" : "No");
	matrix A1, B1;
	if (Optimized)
	{
		AllocateMatrixType(2, &A1, NumberOfRows(*A), NumberOfColumns(*A));
		CopyMatrix(A, &A1);
		AllocateMatrixType(2, &B1, NumberOfRows(*B), NumberOfColumns(*B));
		CopyMatrix(B, &B1);
	}
	else
	{
		AllocateMatrixType(2, &A1, NumberOfColumns(*A), NumberOfRows(*A));
		TransposeMatrix(&A1);
		CopyMatrix(A, &A1);
		AllocateMatrixType(2, &B1, NumberOfColumns(*B), NumberOfRows(*B));
		TransposeMatrix(&B1);
		CopyMatrix(B, &B1);
	}	
	
	ShowMatrix(&A1, "A");
	ShowMatrix(&B1, "B");
	matrix result;
	result = InsertRows(&A1, &B1, Row, Mode);
	ShowMatrix(Mode < 0 ? &A1 : &result, "result");
	
	DeallocateMatrix(&A1);
	if (B1.type) DeallocateMatrix(&B1);
	if (Mode >= 0) DeallocateMatrix(&result);
}
	
void TInsertC(matrix *A, matrix *B, int Col, int Mode, int Optimized)
{
	printf("Testing column insertion: B -> A at column %d...\n", Col);
	printf("Mode: %d. Optimization: %s.\n", Mode, Optimized ? "Yes" : "No");
	matrix A1, B1;
	if (! Optimized)
	{
		AllocateMatrixType(2, &A1, NumberOfRows(*A), NumberOfColumns(*A));
		CopyMatrix(A, &A1);
		AllocateMatrixType(2, &B1, NumberOfRows(*B), NumberOfColumns(*B));
		CopyMatrix(B, &B1);
	}
	else
	{
		AllocateMatrixType(2, &A1, NumberOfColumns(*A), NumberOfRows(*A));
		TransposeMatrix(&A1);
		CopyMatrix(A, &A1);
		AllocateMatrixType(2, &B1, NumberOfColumns(*B), NumberOfRows(*B));
		TransposeMatrix(&B1);
		CopyMatrix(B, &B1);
	}	
	
	ShowMatrix(&A1, "A");
	ShowMatrix(&B1, "B");
	matrix result;
	result = InsertColumns(&A1, &B1, Col, Mode);
	ShowMatrix(Mode < 0 ? &A1 : &result, "result");
	
	DeallocateMatrix(&A1);
	if (B1.type) DeallocateMatrix(&B1);
	if (Mode >= 0) DeallocateMatrix(&result);
}
	
void SRemoveR(int Size, int Reps, int Row, int Rows, int Optimized)
{
	printf("Speed-testing row removal (%s).\n", Optimized ? "optimized" : "unoptimized");
	printf("Removing %d rows from a square matrix of size %d, %d times...\n", Rows, Size, Reps);
	
	matrix *m = AllocateMatrices(Size, Size, Reps, ! Optimized);
	
	ShowMatrix(m, "Sample Matrix Before");
	clock_t t0 = clock();
	int i;
	for (i = 0; i < Reps; i++)
	{
		RemoveRows(m + i, Row, Rows, -1);
	}
	double time = ((double) (clock() - t0)) / ((double) CLOCKS_PER_SEC);
	ShowMatrix(m, "Sample Matrix After");
	printf("Time: %.2f ms\n", time * 1000.0); 
	DeallocateMatrices(Reps, m);
}

void SRemoveC(int Size, int Reps, int Col, int Cols, int Optimized)
{
	printf("Speed-testing column removal (%s).\n", Optimized ? "optimized" : "unoptimized");
	printf("Removing %d columns from a square matrix of size %d, %d times...\n", Cols, Size, Reps);
	
	matrix *m = AllocateMatrices(Size, Size, Reps, Optimized);
	
	ShowMatrix(m, "Sample Matrix Before");
	clock_t t0 = clock();
	int i;
	for (i = 0; i < Reps; i++)
	{
		RemoveColumns(m + i, Col, Cols, -1);
	}
	double time = ((double) (clock() - t0)) / ((double) CLOCKS_PER_SEC);
	ShowMatrix(m, "Sample Matrix After");
	printf("Time: %.2f ms\n", time * 1000.0); 
	DeallocateMatrices(Reps, m);
}

void TRemoveR(matrix *A, int Row, int Rows, int Mode, int Optimized)
{
	printf("Testing row removal: %d rows from A at row %d.\n", Rows, Row);
	printf("Mode: %d. Optimization: %s.\n", Mode, Optimized ? "Yes" : "No");
	matrix A1;
	if (Optimized)
	{
		AllocateMatrixType(2, &A1, NumberOfRows(*A), NumberOfColumns(*A));
		CopyMatrix(A, &A1);
	}
	else
	{
		AllocateMatrixType(2, &A1, NumberOfColumns(*A), NumberOfRows(*A));
		TransposeMatrix(&A1);
		CopyMatrix(A, &A1);
	}	
	
	ShowMatrix(&A1, "A Before");
	matrix result;
	result = RemoveRows(&A1, Row, Rows, Mode);
	ShowMatrix(Mode < 0 ? &A1 : &result, "A After");
	
	DeallocateMatrix(&A1);	
	if (Mode >= 0) DeallocateMatrix(&result);
}
	
void TRemoveC(matrix *A, int Col, int Cols, int Mode, int Optimized)
{
	printf("Testing column removal: %d columns from A at column %d.\n", Cols, Col);
	printf("Mode: %d. Optimization: %s.\n", Mode, Optimized ? "Yes" : "No");
	matrix A1;
	if (! Optimized)
	{
		AllocateMatrixType(2, &A1, NumberOfRows(*A), NumberOfColumns(*A));
		CopyMatrix(A, &A1);
	}
	else
	{
		AllocateMatrixType(2, &A1, NumberOfColumns(*A), NumberOfRows(*A));
		TransposeMatrix(&A1);
		CopyMatrix(A, &A1);
	}	
	
	ShowMatrix(&A1, "A Before");
	matrix result;
	result = RemoveColumns(&A1, Col, Cols, Mode);
	ShowMatrix(Mode < 0 ? &A1 : &result, "A After");
	
	DeallocateMatrix(&A1);
	if (Mode >= 0) DeallocateMatrix(&result);
}

void SInsertNullR(int Size, int Reps, int Row, int Rows, int Optimized)
{
	printf("Speed-testing null row insertion (%s).\n", Optimized ? "optimized" : "unoptimized");
	printf("Inserting %d rows into a square matrix of size %d, %d times.\n", Rows, Size, Reps);
	
	matrix *m = AllocateMatrices(Size, Size, Reps, ! Optimized);
	
	ShowMatrix(m, "Sample Matrix Before");
	clock_t t0 = clock();
	int i;
	for (i = 0; i < Reps; i++)
	{
		InsertNullRows(m + i, Rows, Rows, -1);
	}
	double time = ((double) (clock() - t0)) / ((double) CLOCKS_PER_SEC);
	ShowMatrix(m, "Sample Matrix After");
	printf("Time: %.2f ms\n", time * 1000.0); 
	DeallocateMatrices(Reps, m);
}

void SInsertNullC(int Size, int Reps, int Col, int Cols, int Optimized)
{
	printf("Speed-testing null column insertion (%s).\n", Optimized ? "optimized" : "unoptimized");
	printf("Inserting %d columns into a square matrix of size %d, %d times.\n", Cols, Size, Reps);
	
	matrix *m = AllocateMatrices(Size, Size, Reps, Optimized);
	
	ShowMatrix(m, "Sample Matrix Before");
	clock_t t0 = clock();
	int i;
	for (i = 0; i < Reps; i++)
	{
		InsertNullColumns(m + i, Col, Cols, -1);
	}
	double time = ((double) (clock() - t0)) / ((double) CLOCKS_PER_SEC);
	ShowMatrix(m, "Sample Matrix After");
	printf("Time: %.2f ms\n", time * 1000.0); 
	DeallocateMatrices(Reps, m);
}

void TInsertNullR(matrix *A, int Row, int Rows, int Mode, int Optimized)
{
	printf("Testing null row insertion: %d rows into A at row %d.\n", Rows, Row);
	printf("Mode: %d. Optimization: %s.\n", Mode, Optimized ? "Yes" : "No");
	matrix A1;
	if (Optimized)
	{
		AllocateMatrixType(2, &A1, NumberOfRows(*A), NumberOfColumns(*A));
		CopyMatrix(A, &A1);
	}
	else
	{
		AllocateMatrixType(2, &A1, NumberOfColumns(*A), NumberOfRows(*A));
		TransposeMatrix(&A1);
		CopyMatrix(A, &A1);
	}	
	
	ShowMatrix(&A1, "A Before");
	matrix result;
	result = InsertNullRows(&A1, Row, Rows, Mode);
	ShowMatrix(Mode < 0 ? &A1 : &result, "A After");
	
	DeallocateMatrix(&A1);
	if (Mode >= 0) DeallocateMatrix(&result);
}

void TInsertNullC(matrix *A, int Col, int Cols, int Mode, int Optimized)
{
	printf("Testing null column insertion: %d columns into A at column %d.\n", Cols, Col);
	printf("Mode: %d. Optimization: %s.\n", Mode, Optimized ? "Yes" : "No");
	matrix A1;
	if (Optimized)
	{
		AllocateMatrixType(2, &A1, NumberOfRows(*A), NumberOfColumns(*A));
		CopyMatrix(A, &A1);
	}
	else
	{
		AllocateMatrixType(2, &A1, NumberOfColumns(*A), NumberOfRows(*A));
		TransposeMatrix(&A1);
		CopyMatrix(A, &A1);
	}	
	
	ShowMatrix(&A1, "A Before");
	matrix result;
	result = InsertNullColumns(&A1, Col, Cols, Mode);
	ShowMatrix(Mode < 0 ? &A1 : &result, "A After");
	
	DeallocateMatrix(&A1);
	if (Mode >= 0) DeallocateMatrix(&result);
}

void TSwapR(matrix* A, matrix* B, int Row, int Optimized)
{
	printf("Testing row swap: A(%d,:) <-> B(%d,:).\n", Row, 0);
	printf("Optimization: %s.\n", Optimized ? "Yes" : "No");
	ShowMatrix(A, "A Before");
	ShowMatrix(B, "B Before");
	if (Optimized)
	{
		OptimizeRows(A);
		OptimizeRows(B);
	}
	else
	{
		OptimizeColumns(A);
		OptimizeColumns(B);
	}
	SwapRows(A, B, Row, 0);
	ShowMatrix(A, "A After");
	ShowMatrix(B, "B After");
}

void TSwapC(matrix* A, matrix* B, int Col, int Optimized)
{
	printf("Testing column swap: A(:,%d) <-> B(:,%d).\n", Col, 0);
	printf("Optimization: %s.\n", Optimized ? "Yes" : "No");
	ShowMatrix(A, "A Before");
	ShowMatrix(B, "B Before");
	if (! Optimized)
	{
		OptimizeRows(A);
		OptimizeRows(B);
	}
	else
	{
		OptimizeColumns(A);
		OptimizeColumns(B);
	}
	SwapColumns(A, B, Col, 0);
	ShowMatrix(A, "A After");
	ShowMatrix(B, "B After");
}
	
void SSwapR(int Size, int Reps, int Row, int Optimized)
{
	printf("Speed-testing row swap (%s).\n", Optimized ? "optimized" : "unoptimized");

	printf("Swapping the %d-th rows of two empty square matrices of size %d, %d times.\n", Row, Size, Reps);
	
	matrix *a = AllocateMatrices(Size, Size, Reps, ! Optimized);
	matrix *b = AllocateMatrices(Size, Size, Reps, ! Optimized);

	clock_t t0 = clock();
	int i;
	for (i = 0; i < Reps; i++)
	{
		SwapRows(a + i, b + i, Row, Row);
	}
	double time = ((double) (clock() - t0)) / ((double) CLOCKS_PER_SEC);	
	printf("Time: %.2f ms\n", time * 1000.0); 
	DeallocateMatrices(Reps, a);
	DeallocateMatrices(Reps, b);
}

void SSwapC(int Size, int Reps, int Col, int Optimized)
{
	printf("Speed-testing column swap (%s).\n", Optimized ? "optimized" : "unoptimized");

	printf("Swapping the %d-th columns of two empty square matrices of size %d, %d times.\n", Col, Size, Reps);
	
	matrix *a = AllocateMatrices(Size, Size, Reps, Optimized);
	matrix *b = AllocateMatrices(Size, Size, Reps, Optimized);

	clock_t t0 = clock();
	int i;
	for (i = 0; i < Reps; i++)
	{
		SwapColumns(a + i, b + i, Col, Col);
	}
	double time = ((double) (clock() - t0)) / ((double) CLOCKS_PER_SEC);	
	printf("Time: %.2f ms\n", time * 1000.0); 
	DeallocateMatrices(Reps, a);
	DeallocateMatrices(Reps, b);
}

void TOptimizeR(matrix *A)
{
	ShowMatrix(A, "A before row optimization");
	OptimizeRows(A);
	ShowMatrix(A, "A after row optimization");
	printf("A is %stransposed.\n", A->trans ? "" : "un");
}

void TOptimizeC(matrix *A)
{
	ShowMatrix(A, "A before column optimization");
	OptimizeColumns(A);
	ShowMatrix(A, "A after column optimization");
	printf("A is %stransposed.\n", A->trans ? "" : "un");
}

/*Display a progress percentage that changes with time instead of being left
in the output buffer.*/
void PrintPercent(int num, int denom)
{
	static int lastPercent = -1;
	int percent;
	percent = (num * 100) / denom;
	if (percent != lastPercent)
	{
		lastPercent = percent;
		if (percent) printf("\b\b\b\b");
		printf("%3d%%", percent);
		fflush(stdout);
	}	
}

/*Allocate an array of matrices to be used for a speed test.*/
matrix* AllocateMatrices(int rows, int cols, int reps, int Transpose)
{
	int i;
	matrix *m = tcalloc(reps, sizeof(matrix));
	printf("Allocating test matrices...");
	PrintPercent(0, 1);
	for (i = 0; i < reps; i++)
	{		
		AllocateMatrixType(2, m + i, Transpose ? cols : rows, Transpose ? rows : cols);
		if (Transpose) TransposeMatrix((m) + i);
		PrintPercent(i + 1, reps);
	}
	printf("\n");
	return m;
}

/*Deallocate an array of matrices that have been allocated for a speed test.*/
void DeallocateMatrices(int reps, matrix *m)
{
	int i;
	printf("Deallocating test matrices...");
	PrintPercent(0, 1);
	for (i = 0; i < reps; i++)
	{
		DeallocateMatrix(m + i);	
		PrintPercent(i + 1, reps);
	}
	printf("\n");
	free(m);	
}

void SetDefaults(char* optimization, char* Operation, int *Mode, matrix *A, matrix *B, int *Row, int *Column, int* Rows, int *Columns, int *Reps, int *Size, char *speedtest)
{
	strcpy(optimization, "no");
	strcpy(Operation, "");
	*Mode = 0;
	memset(A, 0, sizeof(matrix));
	memset(B, 0, sizeof(matrix));
	*Row = -1;
	*Column = -1;
	*Rows = 1;
	*Columns = 1;
	*Reps = 1;
	*Size = 2;
	strcpy(speedtest, "no");
}
