#include "../pnheaders/general.h"
#include "../pnheaders/matrix.h"
#include "matrixmath.h"
#include "extendedmatrix.h"

static matrix InsertRowsCopy(matrix* outer, matrix* inner, int start, int mode);
static matrix InsertRowsOrig(matrix* outer, matrix* inner, int start);
static matrix RemoveRowsCopy(matrix* m, int row, int rows, int mode);
static matrix RemoveRowsOrig(matrix* m, int row, int rows);
static matrix InsertNullRowsCopy(matrix *m, int row, int rows, int mode);
static matrix InsertNullRowsOrig(matrix *m, int row, int rows);

matrix InsertRows(matrix* outer, matrix* inner, int start, int mode)
{	
	matrix null;
	
	/*Check parameters. None of the pointers can be null.*/
	if(! (outer && inner))
	{
		merror(0, "InsertRows/Columns: One of the operands is a null pointer");
		AllocateMatrix(&null, 0, 0);
		return null;
	}	
	/*inner and outer must have the same number of columns.*/
	if (NumberOfColumns(*inner) != NumberOfColumns(*outer))
	{
		merror(0, "InsertRows/Columns: The operands do not have the same number of columns/rows");
		AllocateMatrix(&null, 0, 0);
		return null;
	}
	
	/*If start is negative, default to adding to the end.*/
	if (start < 0) start = NumberOfRows(*outer);
	/*start cannot point to a row farther out than the first possible new row*/
	if (start > NumberOfRows(*outer))
	{
		merror(0, "InsertRows/Columns: The index of the first inserted row/column is out of range");
		AllocateMatrix(&null, 0, 0);
		return null;
	}
	
	/*If mode is 0 or positive, we are writing to a copy.*/
	if (mode >= 0)
	{
		return InsertRowsCopy(outer, inner, start, mode);
	}	
	/*Otherwise, we want to actually merge the matrices.*/
	else
	{
		return InsertRowsOrig(outer, inner, start);
	}	
}
	
matrix InsertColumns(matrix* outer, matrix* inner, int start, int mode)
{
	/*This is the column analogue of InsertRows. Transpose the operands and call 
	InsertRows.*/
	matrix result;
	TransposeMatrix(outer);
	TransposeMatrix(inner);
	result = InsertRows(outer, inner, start, mode);
	TransposeMatrix(outer);
	if (mode >= 0) TransposeMatrix(inner);
	TransposeMatrix(&result);	
	return result;
}
		
matrix InsertNullRows(matrix* m, int start, int rowsToAdd, int mode)
{
	matrix null;
	if (! m)
	{
		merror(0, "InsertNewRows/COLUMNS: Null matrix pointer");
		AllocateMatrix(&null, 0, 0);
		return null;		
	}	
	/*Make sure the starting point is valid. Start will be the index of the first
	of the new rows added. If index < 0, default to adding at the end of the
	matrix.*/
	if (start < 0) start = NumberOfRows(*m);
	if (start > NumberOfRows(*m))
	{
		merror(0, "InsertNewRows/COLUMNS: First new index is out of range");
		AllocateMatrix(&null, 0, 0);
		return null;
	}
	/*Make sure the rowsToAdd is valid. Cannot be less than 1.*/
	if (rowsToAdd < 1)
	{
		merror(0, "InsertNewRows/COLUMNS: Rows/Columns to add is negative or zero");
		AllocateMatrix(&null, 0, 0);
		return null;
	}
	if (mode >= 0)
	{
		return InsertNullRowsCopy(m, start, rowsToAdd, mode);
	}
	else
	{
		return InsertNullRowsOrig(m, start, rowsToAdd);
	}
}

matrix InsertNullColumns(matrix* m, int start, int colsToAdd, int mode)
{	
	matrix result;
	/*This is the column analogue of InsertNewRows. Transpose the matrix and call
	InsertNewRows.*/
	TransposeMatrix(m);
	result = InsertNullRows(m, start, colsToAdd, mode);
	TransposeMatrix(&result);
	TransposeMatrix(m);
	return result;
}

matrix RemoveRows(matrix* m, int start, int rowsToRemove, int mode)
{
	/*Matrix pointer cannot be null.*/
	matrix null;
	if (! m)
	{
		merror(0, "REMOVEROWS/COLUMNS: Null matrix pointer");
		AllocateMatrix(&null, 0, 0);
		return null;
	}
	/*Start has to be the index of the first row to remove.*/
	if (start < 0 || start >= NumberOfRows(*m))
	{
		merror(0, "REMOVEROWS/COLUMNS: Index of first row/column to remove is out of range");
		AllocateMatrix(&null, 0, 0);
		return null;
	}
	/*The row count cannot be less than 1, nor can it be so great that it
	requires the removal of rows that do not exist.*/
	if (rowsToRemove < 1 || start + rowsToRemove > NumberOfRows(*m))
	{
		merror(0, "REMOVEROWS/COLUMNS: Number of rows/columns to remove is out of range");
		AllocateMatrix(&null, 0, 0);
		return null;
	}
	if (mode >= 0)
	{
		return RemoveRowsCopy(m, start, rowsToRemove, mode);
	}
	else
	{
		return RemoveRowsOrig(m, start, rowsToRemove);
	}
}

matrix RemoveColumns(matrix* m, int start, int colsToRemove, int Mode)
{
	matrix result;
	/*This is the column analogue of RemoveRows. Transpose the matrix and call
	RemoveRows.*/
	TransposeMatrix(m);
	result = RemoveRows(m, start, colsToRemove, Mode);
	TransposeMatrix(&result);
	TransposeMatrix(m);
	return result;
}

matrix InsertRowsCopy(matrix* outer, matrix* inner, int start, int mode)
{
	matrix result;
	/*Insert null rows.*/
	result = InsertNullRowsCopy(outer, start, NumberOfRows(*inner), mode);
	/*Copy the inner matrix.*/
	CopyBlock(NumberOfRows(*inner), NumberOfColumns(*inner), inner, 0, 0, &result, start, 0);		
	return result;
}
	
matrix InsertRowsOrig(matrix* outer, matrix* inner, int start)
{
	int i;
	mtype **newAr;
	/*If the outer and inner matrices are both type 2 untransposed, we can combine
	memory directly. Otherwise we'll want to insert new blank rows into outer
	and copy the contents of inner.*/
	if (outer->type == 2 && inner->type == 2 && !(outer->trans || inner->trans))
	{
		/*Combine row memory directly.*/
		newAr = tcalloc(outer->nr + inner->nr, sizeof(mtype*));
		outer->nr += inner->nr;
		for (i = 0; i < outer->nr; i++)
		{
			if (i < start)
			{
				newAr[i] = outer->ar[i];
			}
			else if (i < start + inner->nr)
			{
				newAr[i] = inner->ar[i - start];
			}
			else
			{
				newAr[i] = outer->ar[i - inner->nr];
			}
		}
		free(outer->ar);
		free(inner->ar);
		outer->ar = newAr;
		memset(inner, 0, sizeof(matrix));
	}
	else
	{
		/*Otherwise insert the new null rows and copy.*/
		InsertNullRowsOrig(outer, start, NumberOfRows(*inner));
		CopyBlock(NumberOfRows(*inner), NumberOfColumns(*inner), inner, 0, 0, outer, start, 0);
		DeallocateMatrix(inner);
		memset(inner, 0, sizeof(matrix));
	}
	return *outer;
}
		
matrix RemoveRowsCopy(matrix* m, int row, int rows, int mode)
{
	matrix result;
	
	/*Allocate the result matrix. Preserve transpose state.*/
	int nr = m->trans ? NumberOfColumns(*m) : NumberOfRows(*m) - rows;
	int nc = m->trans ? NumberOfRows(*m) - rows : NumberOfColumns(*m);
	if (mode)
	{
		AllocateMatrixType(mode, &result, nr, nc);
	}
	else
	{
		AllocateMatrix(&result, nr, nc);
	}
	if (m->trans) TransposeMatrix(&result);
	
	/*Copy the first-half block.*/
	if (row)
	{
		CopyBlock(row, NumberOfColumns(*m), m, 0, 0, &result, 0, 0);
	}
	/*Copy the second-half block.*/
	if (NumberOfRows(*m) - row - rows)
	{
		CopyBlock(NumberOfRows(*m) - row - rows, NumberOfColumns(*m), m, row + rows, 0, &result, row, 0);
	}
	return result;
}
		
matrix RemoveRowsOrig(matrix* m, int row, int rows)
{	
	int i;
	/*Check to see if the remove can be optimized.*/
	if (m->type == 2 && (! m->trans))
	{	
		/*Do not allocate a new ar member if we can help it. Just leave the old one
		and move the memory.*/	
		for (i = row; i < row + rows; i++)
		{
			free(m->ar[i]);
		}
		for (i = row + rows; i < m->nr; i++)
		{
			m->ar[i - rows] = m->ar[i];
		}
		m->nr -= rows;
		/*If we have removed all rows, deallocate the ar pointer.*/
		if (! m->nr) 
		{
			free(m->ar);
			m->ar = 0;
		}
	}
	else
	{
		/*If not, do a slow remove. This has to create a complete copy of the
		matrix. Preserve matrix type.*/		
		matrix old = *m;
		*m = RemoveRowsCopy(m, row, rows, m->type);
		DeallocateMatrix(&old);
	}
	return *m;
}
		
matrix InsertNullRowsCopy(matrix *m, int row, int rows, int mode)
{
	matrix result;
	/*Allocate the result matrix. Preserve transpose state.*/
	int nr = m->trans ? NumberOfColumns(*m) : NumberOfRows(*m) + rows;
	int nc = m->trans ? NumberOfRows(*m) + rows : NumberOfColumns(*m);
	if (mode)
	{
		AllocateMatrixType(mode, &result, nr, nc);
	}
	else
	{
		AllocateMatrix(&result, nr, nc);
	}
	if (m->trans) TransposeMatrix(&result);
	
	/*Copy the first-half block.*/	
	if (row)
	{
		CopyBlock(row, NumberOfColumns(*m), m, 0, 0, &result, 0, 0);
	}		
	/*Copy the second-half block.*/
	if (NumberOfRows(*m) - row)
	{
		CopyBlock(NumberOfRows(*m) - row, NumberOfColumns(*m), m, row, 0, &result, row + rows, 0);
	}
	return result;
}

matrix InsertNullRowsOrig(matrix *m, int row, int rows)
{
	mtype **newAr;
	int i;
	/*See if the insertion can be optimized.*/
	if (m->type == 2 && (! m->trans))
	{
		m->nr += rows;
		newAr = tcalloc(m->nr, sizeof(mtype*));		
		for (i = 0; i < m->nr; i++)
		{
			if (i < row)
			{
				newAr[i] = m->ar[i];
			}
			else if (i < row + rows)
			{
				newAr[i] = tcalloc(NumberOfColumns(*m), sizeof(mtype));
			}
			else
			{
				newAr[i] = m->ar[i - rows];
			}
		}
		free(m->ar);		
		m->ar = newAr;
	}
	else
	{
		/*To insert blank rows we'll need to do a complete copy anyway.*/
		matrix old;
		old = *m;
		*m = InsertNullRowsCopy(m, row, rows, m->type);
		DeallocateMatrix(&old);
	}
	return *m;
}

void SwapRows(matrix* a, matrix* b, int rowA, int rowB)
{
	if (! (a && b))
	{
		merror(0, "SWAPROWS/COLUMNS: One of the matrix pointers is null");
		return;
	}
	if (NumberOfColumns(*a) != NumberOfColumns(*b))
	{
		merror(0, "SWAPROWS/COLUMNS: The dimensions of the matrices do not agree");
		return;
	}
	if (rowA < 0 || rowA >= NumberOfRows(*a) || rowB < 0 || rowB >= NumberOfRows(*b))
	{
		merror(0, "SWAPROWS/COLUMNS: One of the row/column numbers is out of range");
		return;
	}
	/*If a and b refer to the same matrix and the same row numbers are used,
	no operation is necessary.*/
	if (a == b && rowA == rowB) return;
	
	/*If the matrices are type-2 untransposed, do a swap of the pointers in ar
	rather than copying whole rows.*/
	if (a->type == 2 && b->type == 2 && !(a->trans || b->trans))
	{
		mtype* temp = b->ar[rowB];
		b->ar[rowB] = a->ar[rowA];
		a->ar[rowA] = temp;
	}
	/*Otherwise we'll have to iterate through the whole row and do a copy-type
	swap.*/
	else
	{
		int temp, i;
		for (i = 0; i < NumberOfColumns(*a); i++)
		{
			temp = GetMatrixEl(a, rowA, i);
			SetMatrixEl(a, rowA, i, GetMatrixEl(b, rowB, i));
			SetMatrixEl(b, rowB, i, temp);
		}
	}
}

void SwapColumns(matrix *a, matrix* b, int colA, int colB)
{
	if (a && b)
	{
		TransposeMatrix(a);
		TransposeMatrix(b);
		SwapRows(a, b, colA, colB);
		TransposeMatrix(a);
		TransposeMatrix(b);
	}
	else
	{
		merror(0, "SWAPROWS/COLUMNS: One of the matrix pointers is null");
		return;
	}
}

void OptimizeRows(matrix* m)
{
	matrix newM;
	if (m->type != 2 || m->trans)
	{
		AllocateMatrixType(2, &newM, NumberOfRows(*m), NumberOfColumns(*m));
		CopyMatrix(m, &newM);
		DeallocateMatrix(m);
		*m = newM;
	}
}

void OptimizeColumns(matrix *m)
{
	TransposeMatrix(m);
	OptimizeRows(m);
	TransposeMatrix(m);
}
