/*
 * Copyright (c) 2006
 *  Morten Slot Kristensen, Århus, Denmark.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <iomanip>
#include <sstream>
#include <math.h>
#include <vector>
#include <fcntl.h>
#include <stdio.h>
#include "matrix.hpp"
#include "operations.hpp"
using namespace std;

/*
 * Multiplicates two matrices and returns the product
 */
matrix multiplicate(matrix *first, matrix *second)
{
	matrix result(first->get_rows(), second->get_columns());
	float v;

	for(int i=0; i<result.get_rows(); i++)
	{
		matrix row = get_row(i, first);
		for(int j=0; j<result.get_columns(); j++)
		{
			matrix column = get_column(j, second);
			v = 0.0;
		
			for(int h=0; h<row.get_columns(); h++)
			{
				v += row.get_value(0, h) * column.get_value(h, 0);
			}

			result.set_value(i, j, v);
		}
	}

	return result;
}

/*
 * Multiplicates a matrix with a number and returns the product
 */
matrix multiplicate(matrix *m, float number)
{
	matrix result(m->get_rows(), m->get_columns());

	for(int i=0; i<result.get_rows(); i++)
	{
		for(int j=0; j<result.get_columns(); j++)
		{
			result.set_value(i, j, m->get_value(i, j)*number);
		}
	}

	return result;
}

/*
 * Sums two matrices and returns the result
 */
matrix add(matrix *first, matrix *second)
{
	matrix result(first->get_rows(), first->get_columns());

	for(int i=0; i<first->get_rows(); i++)
	{
		for(int j=0; j<first->get_columns(); j++)
		{
			result.set_value(i, j, first->get_value(i, j) +
					second->get_value(i, j));
		}
	}

	return result;
}

/*
 * Substracts two matrices and returns the result
 */
matrix substract(matrix *first, matrix *second)
{
	matrix result(first->get_rows(), first->get_columns());

	for(int i=0; i<first->get_rows(); i++)
	{
		for(int j=0; j<first->get_columns(); j++)
		{
			result.set_value(i, j, first->get_value(i, j) -
					second->get_value(i, j));
		}
	}

	return result;
}

/*
 * Inverts a given matrix and returns the result
 */
matrix invert(matrix *m)
{
	matrix result(m->get_rows(), m->get_columns());

	for(int i=0; i<m->get_rows(); i++)
	{
		for(int j=0; j<m->get_columns(); j++)
		{
			float v = m->get_value(i, j);

			if(v == 0)
				result.set_value(i, j, 0);
			else
				result.set_value(i, j, 1/v);
		}
	}

	return result;
}

/*
 * Transposes a given matrix and returns the result
 */
matrix transpose(matrix *m)
{
	matrix result(m->get_columns(), m->get_rows());

	for(int i=0; i<result.get_rows(); i++)
	{
		matrix column = get_column(i, m);
		for(int j=0; j<column.get_rows(); j++)
		{
			result.set_value(i, j, column.get_value(j, 0));
		}
	}

	return result;
}

/*
 * Retrieves the determinant of a given matrix
 */
float determinant(matrix *m)
{
	float det = 0.0;
	int done = 0, lsize = m->get_rows(), swt = 1;
	vector<matrix> list;

	// put the first one in at once
	list.push_back(*m);

	// run while m.row>2,col>2
	while(m->get_rows() > 2 && m->get_columns() > 2)
	{
		vector<matrix>::iterator it;
		vector<matrix>::iterator end = list.end();

		// go through the items and compute new matrices
		// breaking the first into smaller matrices untill
		// there is only 2x2's left
		for(it = list.begin(); it != end; it++)
		{
			if(it->get_rows() > 2 && it->get_columns() > 2)
			{
				matrix tmp = *it;
				for(int i=0; i<tmp.get_columns(); i++)
				{
					matrix m2 = get_matrix_ex_r_c(0, i, &tmp);
					m2.set_temp(tmp.get_value(0, i)*tmp.get_temp());
					lsize = m2.get_rows();
					list.push_back(m2);
				}
			}
		}
		
		// delete the matrices from the previos run-through
		// so only the new-generated ones remain
		for(it = list.begin(); it != list.end(); it++)
		{
			if(it->get_rows() > lsize)
				list.erase(it);
		}

		// when there's only 2x2's left then break
		done = 1;
		for(it = list.begin(); it != list.end(); it++)
		{
			if(it->get_rows() > 2 && it->get_columns() > 2)
			{
				done = 0;
			}
		}

		if(done)
			break;
	}

	// calculate the determinant
	// NOTE: add the first result and the next then switch between adding and substracting
	int add = 2;
	for(vector<matrix>::iterator it = list.begin(); it != list.end(); it++)
	{
		if(add == 1)
		{
			det -= it->get_temp()*((it->get_value(0, 0)*it->get_value(1, 1)) - (it->get_value(1, 0)*it->get_value(0, 1)));
			add = 0;
		}
		else if(add == 2)
		{
			det = it->get_temp()*((it->get_value(0, 0)*it->get_value(1, 1)) - (it->get_value(1, 0)*it->get_value(0, 1)));
			add = 1;
		}
		else if(add == 0)
		{
			det += it->get_temp()*((it->get_value(0, 0)*it->get_value(1, 1)) - (it->get_value(1, 0)*it->get_value(0, 1)));
			add = 1;
		}
	}

	return det;
}

/*
 * Retrieves a given row from a matrix and returns it as matrix
 */
matrix get_row(int row, matrix *m)
{
	matrix result(1, m->get_columns());

	for(int i=0; i<m->get_columns(); i++)
	{
		result.set_value(0, i, m->get_value(row, i));
	}

	return result;
}

/*
 * Retrieves a given column from a matrix and returns it as matrix
 */
matrix get_column(int column, matrix *m)
{
	matrix result(m->get_rows(), 1);

	for(int i=0; i<m->get_rows(); i++)
	{
		result.set_value(i, 0, m->get_value(i, column));
	}

	return result;
}

/*
 * Retrieves the matrix except for the row and column
 */
matrix get_matrix_ex_r_c(int row, int column, matrix *mat)
{
	matrix result(mat->get_rows()-1, mat->get_columns()-1);

	int x = 0, y = 0, a_col = 0;
	for(int i=0; i<mat->get_rows(); i++)
	{
		for(int j=0; j<mat->get_columns(); j++)
		{
			if(i != row && j != column)
			{
				result.set_value(y, x, mat->get_value(i, j));
				x++;
				a_col = 1;
			}
		}

		if(a_col)
		{
			y++;
			a_col = 0;
		}

		x = 0;
	}
	
	return result;
}

/*
 * Determines if two matrices are equal
 */
int is_identical(matrix *first, matrix *second)
{
	if(first->get_rows() == second->get_rows() && first->get_columns() == second->get_columns())
	{
		int equal = 1;

		for(int i=0; i<first->get_rows(); i++)
		{
			for(int j=0; j<first->get_columns(); j++)
			{
				if(first->get_value(i, j) != second->get_value(i, j))
					equal = 0;
			}
		}

		return equal;
	}
	else
		return 0;
}

/*
 * Generates a random number from 'min' to 'max' from '/dev/random'
 */
int random_number(int min, int max)
{
	static int dev_fd = open("/dev/random", O_RDONLY);
	int bytes_to_read;
	unsigned rnd_val;
	char* next_byte = (char*)&rnd_val;
	
	bytes_to_read = sizeof(rnd_val);

	do
	{
		int bytes_read;
		bytes_read = read(dev_fd, next_byte, bytes_to_read);
		bytes_to_read -= bytes_read;
		next_byte += bytes_read;
	} while(bytes_to_read > 0);

	return min+(rnd_val%(max-min+1));
}

/*
 * Generates a text representation of a matrix
 */
string gen_text_matrix(matrix *m)
{
	string result = "", 
		   biggest = "";
	vector<TabItem> tab_info;

	// go through every column and get the biggest value
	for(int i=0; i<m->get_columns(); i++)
	{
		matrix row = get_column(i, m);
		biggest = "";

		for(int h=0; h<row.get_rows(); h++)
		{
			float tmp = row.get_value(h, 0);
			if(to_string(tmp).length() > biggest.length())
				biggest = to_string(tmp);
		}

		float tabs = ceil((float)biggest.length()/3.0);

		TabItem tab_item;
		tab_item.num_tabs = tabs;
		tab_item.val_one_tab = atof(biggest.c_str());
		tab_info.push_back(tab_item);
	}

	// go through every TabItem at make the highest
	// number of tabs the same for every column
	// but not the ones that equals TabItem's 
	// 'val_one_tab' and the ones which calced
	// tab is equal to 'val_one_tab'

	for(int i=0; i<m->get_rows(); i++)
	{
		for(int j=0; j<m->get_columns(); j++)
		{
			int col=0;
			TabItem tab_item;
			for(vector<TabItem>::iterator it = tab_info.begin();
					it != tab_info.end(); it++)
			{
				if(col == j)
					tab_item = *it;

				col++;
			}

			float val = m->get_value(i, j);
			result += to_string(val);
			float tab = ceil((float)to_string(val).length()/3.0);
			
			// add the number of tabs described in 
			// (tab_item.num_tabs - the ones for the singular + 1)
			for(int h=0; h<(tab_item.num_tabs-tab+1); h++)
			  result += "\t";
		}
		
		result += "\n";
	}

	return result;
}

/*
 * Generates a matrix from an imported
 * text representation
 */
matrix gen_matrix_from_text(int rows, int columns, const string &mdata)
{
	/*
	  Format of text matrix:

	  	 rows\n
		 columns\n
		 x,x,x,x,x,x,x,x,... (as many items as rows*columns)
		 (separated bt commas!)
	 */

	matrix result(rows, columns);

	vector<string> tokens;
	string del = ",";
	tokenize_string(mdata, tokens, del);

	int pos = 0;
	for(vector<string>::iterator it = tokens.begin();
			it !=  tokens.end(); it++)
	{
		float value = atof(it->c_str());
		result.set_value_t(pos, value);

		pos++;
	}

	return result;
}

/*
 * Tokenizes a string into a vector with the specified delimiter
 */
void tokenize_string(const string &str, vector<string> &tokens, const string &delimiters)
{
    // Skip delimiters at beginning.
    string::size_type lastPos = str.find_first_not_of(delimiters, 0);

    // Find first "non-delimiter".
    string::size_type pos = str.find_first_of(delimiters, lastPos);
    
	while (string::npos != pos || string::npos != lastPos)
    {
    	tokens.push_back(str.substr(lastPos, pos - lastPos));
    	
		// Skip delimiters.  Note the "not_of"
    	lastPos = str.find_first_not_of(delimiters, pos);
    	
		// Find next "non-delimiter"
    	pos = str.find_first_of(delimiters, lastPos);
    }
}

/*
 * Generates the text to write to a file
 * when exporting the matrix
 */
string export_matrix(matrix *m)
{
	string result = to_string(m->get_rows())+"\n"
		+to_string(m->get_columns())+"\n";

	for(int i=0; i<m->get_rows(); i++)
	{
		for(int j=0; j<m->get_columns(); j++)
		{
			result += to_string(m->get_value(i, j)) + ",";
		}
	}

	// skip the last comma
	return result.substr(0, result.length()-1);
}


syntax highlighted by Code2HTML, v. 0.9.1