/***************************************************************************
 *   Copyright (C) 2007 by Honorius Galmeanu                               *
 *   honorius_galmeanu@yahoo.com                                           *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 *                                                                         *
 *   Copyright: See COPYING file that comes with this distribution         *
 *                                                                         *
 ***************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <math.h>
#include <assert.h>
#include <time.h>
#include "idcommon.h"


static int g_dbgLevel = 3;


struct x_randomizer {
 	x_randomizer (void) {
 		srand (time (NULL));
 	}
} _rnd;


///////////////////////////////////////////////////////////////////////////////
//
// x_vector class
//
///////////////////////////////////////////////////////////////////////////////

template <class T>
x_vector<T>::x_vector (void) : e(NULL), length(0) { }


template <class T>
x_vector<T>::x_vector (int l) : length(l) {
	e = new T[l];
	if (e == NULL)
	{
		TRACE ("memory exhausted");
		exit (1);
	}
}


template <class T>
x_vector<T>::~x_vector (void) {
	delete e;
	e = NULL;
}


template <class T>
void x_vector<T>::ensure (int l)
{
	if (e)
		delete e;
	int * plen = (int *)&length;
	*plen = l;

	e = new T[length];
	if (e == NULL)
	{
		TRACE ("memory exhausted");
		exit (1);
	}
}


template <class T>
inline T& x_vector<T>::operator[] (int idx) {
	if (idx >= length || idx < 0) {
		TRACE ("out of bounds");
		exit (1);
	}

	return e[idx];
}

template <>
int x_vector<int>::store (FILE * f)
{
	if (f == NULL)
	{
		TRACE ("store: invalid file descriptor");
		return -1;
	}

	char signature [] = "idsvm-int";
	fwrite (signature, sizeof (signature), 1, f);

	fwrite (&length, sizeof (int), 1, f);
	for (int i = 0 ; i < length ; i ++)
		fwrite (&e[i], sizeof (int), 1, f);

	return 0;
}

template <>
int x_vector<int>::restore (FILE * f)
{
	if (f == NULL)
	{
		TRACE ("restore: invalid file descriptor");
		exit(1);
	}

	char signature [] = "idsvm-int";
	fread (signature, sizeof (signature), 1, f);
	if (strcmp (signature, "idsvm-int"))
	{
		TRACE ("restore: idsvm-int signature not found");
		exit(1);
	}

	int len = 0;
	fread (&len, sizeof (int), 1, f);
	if (len <= 0 || len > 5000000)
	{
		TRACE ("restore: invalid length: %d", len);
		exit(1);
	}

	if (e) delete e;
	int * plen = (int *)&length;
	*plen = len;
	e = new int [length];
	for (int i = 0 ; i < length ; i ++)
		if (fread (&e[i], sizeof (int), 1, f) < 1)
		{
			TRACE ("restore: file shorter than expected");
			exit(1);
		}

	return 0;
}


template class x_vector<double>;
template class x_vector<int>;


///////////////////////////////////////////////////////////////////////////////
//
// x_line class
//int * ptr = (int *)&size;
///////////////////////////////////////////////////////////////////////////////

template <class T>
x_line<T>::x_line (int size_) : length (0), size (size_)
{
	e = new T [size_];
}


template <class T>
x_line<T>::~x_line (void)
{
	if (e) delete e;
}


template <class T>
void x_line<T>::expand (int l)
{
	if (length + l <= size)
	{
		length += l;
	} else {
		size = (2 * size + 1) > (size + l) ? (2 * size + 1) : (size + l);
		length += l;

		// reallocate
		T * new_e = new T [size];
		for (int i = 0 ; i < length - l ; i ++)
			new_e[i] = e[i];

		delete e;
		e = new_e; 
	}
}


template <class T>
inline void x_line<T>::ensure (int l)
{
	if (length < l)
		expand (l - length);
	else
		length = l;
}


template <class T>
void x_line<T>::add (T& t)
{
	expand ();
	e[length - 1] = t;
}


template <class T>
void x_line<T>::remove (int pos)
{
	if (length <= 0 || pos >= length)
	{
		TRACE ("cannot remove zero-dimension x_line or invalid argument");
		exit (1);
	}

	for (int i = pos ; i < length - 1 ; i ++)
		e[i] = e[i + 1]; 
	
	length --;
}


template <class T>
inline T& x_line<T>::operator[] (int idx)
{
	if (idx >= length || idx < 0)
	{
		TRACE ("out of bounds");
		exit (1);
	}

	return e[idx]; 
}


template class x_line<double>;
template class x_line<x_elem>;
template class x_line<int>;

template <>
void x_line<double>::print (char * string)
{
	if (string)
		INFO (2, "%s len: %d\n", string, len ());
	else
		INFO (2, "len: %d\n", len ());
	for (int i = 0 ; i < length ; i ++)
		INFO (2, "%.6lf ", e[i]);
	INFO (2, "\n");
}

template <>
void x_line<x_elem>::print (char * string)
{
	if (string)
		INFO (2, "%s len: %d\n", string, len ());
	else
		INFO (2, "len: %d\n", len ());
	for (int i = 0 ; i < length ; i ++)
	{
		INFO (2, "(%d) ", e[i].idx);
		switch (e[i].type)
		{
			case x_elem::UNBOUNDED:
				INFO (2, "un");
			case x_elem::BOUNDED:
				INFO (2, "bounded");
				break;
			case x_elem::ZERO:
				INFO (2, "no");
				break;
		}
		INFO (2, " SV lambda: %g\n" , e[i].lambda);
	}
}


///////////////////////////////////////////////////////////////////////////////
//
// x_matrix class
//
///////////////////////////////////////////////////////////////////////////////

template <class T>
x_matrix<T>::x_matrix (int size_) : length (0), size (size_)
{
	e = new T * [size];
	for (int i = 0 ; i < size ; i ++)
		e[i] = new T [size];
}


template <class T>
x_matrix<T>::~x_matrix (void)
{
	if (e && size > 0)
	{
		for (int i = 0 ; i < size ; i ++)
			delete e[i];
		delete e;
	}
}


template <class T>
void x_matrix<T>::expand (int l)
{
	if (length + l <= size)
	{
		length += l;
	} else {
		size = (2 * size + 1) > (size + l) ? (2 * size + 1) : (size + l);
		length += l;

		T ** new_e = new T * [size];
		for (int i = 0 ; i < length - l ; i ++)
		{
			T * new_ei = new T [size];
			for (int j = 0 ; j < length - l ; j ++)
				new_ei[j] = e[i][j];
			for (int j = length - l ; j < length ; j ++)
				new_ei[j] = 0;
			new_e[i] = new_ei;
		}
		for (int i = length - l ; i < size ; i ++)
		{
			new_e[i] = new T [size];
			for (int j = 0 ; j < size ; j ++)
				new_e[i][j] = 0;
		}

		for (int i = 0 ; i < length - l ; i ++)
			delete e[i];
		delete e;
		e = new_e; 
	}
}

template <class T>
inline void x_matrix<T>::ensure (int l)
{
	if (length < l)
		expand (l - length);
	else
		length = l;
}


template <class T>
void x_matrix<T>::remove (int pos)
{
	if (length <= 0 || pos >= length)
	{
		TRACE ("cannot remove zero-dimension x_line or invalid argument");
		exit (1);
	}

	T * old = e[pos];
	for (int i = pos ; i < length - 1 ; i ++)
		e[i] = e[i + 1];
	e[length - 1] = old;

	for (int i = 0 ; i < length ; i ++)
		{
			T tmp = e[i][pos];
			for (int j = pos ; j < length - 1 ; j ++)
				e[i][j] = e[i][j + 1];
			e[i][length - 1] = tmp;
		}
	
	length --;
}


template <class T>
inline T* x_matrix<T>::operator[] (int idx)
{
	if (idx < 0 || idx > length || idx >= size)
	{
		TRACE ("out of bounds");
		exit (1);
	}

	return e[idx];
}

template class x_matrix<double>;

template <>
void x_matrix<double>::print (void)
{
	INFO (2, "len: %d\n", len ());
	for (int i = 0 ; i < length ; i ++)
	{
		for (int j = 0 ; j < length ; j ++)
			INFO (2, "%.2lf ", e[i][j]);
		INFO (2, "\n");
	}
}


///////////////////////////////////////////////////////////////////////////////
//
// x_product class
//
///////////////////////////////////////////////////////////////////////////////

template <class T>
void x_product<T>::dot_ll (/* in */ x_line<T>& l1, /* in */ x_line<T>& l2, /* out */ T& out)
{
	// perform some checking
	int len = l1.len ();
	if (len != l2.len ())
	{
		TRACE ("invalid dimensions");
		exit (1);
	}

	out = 0;
	for (int i = 0 ; i < len ; i ++)
		out += l1[i] * l2[i];
}


template <class T>
void x_product<T>::cross_ll (/* in */ x_line<T>& l1, /* in */ x_line<T>& l2, /* out */ x_matrix<T>& out)
{
	// perform some checking
	int len = l1.len ();
	if (len != l2.len () || len != out.len ())
	{
		TRACE ("invalid dimensions");
		exit (1);
	}

	for (int i = 0 ; i < len ; i ++)
		for (int j = 0 ; j < len ; j ++)
			out[i][j] = l1[i] * l2[j];
}


template <class T>
void x_product<T>::mul_ml (/* in */ x_matrix<T>& m, /* in */ x_line<T>& l, /* out */ x_line<T>& out)
{
	// perform some checking
	int len = m.len ();
	if (len != l.len () || len != out.len ())
	{
		TRACE ("invalid dimensions");
		exit (1);
	}

	for (int i = 0 ; i < len ; i ++)
	{
		out[i] = 0;
		for (int j = 0 ; j < len ; j ++)
			 out[i] += m[i][j] * l[j];
	}
}


template <class T>
void x_product<T>::mul_lm (/* in */ x_line<T>& l, /* in */ x_matrix<T>& m, /* out */ x_line<T>& out)
{
	// perform some checking
	int len = m.len ();
	if (len != l.len () || len != out.len ())
	{
		TRACE ("invalid dimensions");
		exit (1);
	}

	for (int i = 0 ; i < len ; i ++)
	{
		out[i] = 0;
		for (int j = 0 ; j < len ; j ++)
			out[i] += l[j] * m[j][i];
	}
}


template <class T>
void x_product<T>::mul_mm (/* in */ x_matrix<T>& m1, /* in */ x_matrix<T>& m2, /* out */ x_matrix<T>& out)
{
	int len = m1.len ();
	if (len != m2.len () || len != out.len ())
	{
		TRACE ("invalid dimensions");
		exit (1);
	}

	for (int i = 0 ; i < len ; i ++)
		for (int j = 0 ; j < len ; j ++)
		{
			out[i][j] = 0;
			for (int k = 0 ; k < len ; k ++)
				out[i][j] += m1[i][k] * m2[k][j];
		}
}


template class x_product<double>;


///////////////////////////////////////////////////////////////////////////////
//
// x_svm_problem class
//
///////////////////////////////////////////////////////////////////////////////

x_svm_problem::x_svm_problem (void) : n(0), y(NULL), x(NULL), x_all(NULL) { }

x_svm_problem::~x_svm_problem (void)
{
	if (y) { free (y); y = NULL; }
	if (x_all) { free (x_all); x_all = NULL; }
	if (x) { free (x); x = NULL; }
}


///////////////////////////////////////////////////////////////////////////////
//
// x_svm_param class
//
///////////////////////////////////////////////////////////////////////////////

x_svm_param::x_svm_param (void) : kernel_type (1), sigma (0), C(1), saveEvery (0), restoreFrom (NULL),
	saveTo (NULL), info (0), newC (0), input_file (NULL), test_file (NULL), firstLabel (-1), secondLabel (-1),
	cross_k (0), cross_test (0) { }

x_svm_param::~x_svm_param (void) { 
	if (input_file) { free (input_file); input_file = NULL; }
	if (test_file) { free (test_file); test_file = NULL; }
	if (restoreFrom) { free (restoreFrom); restoreFrom = NULL; }
	if (saveTo) { free (saveTo); saveTo = NULL; }
}


///////////////////////////////////////////////////////////////////////////////
//
// x_svm_kernel class
//
///////////////////////////////////////////////////////////////////////////////

x_svm_kernel::x_svm_kernel (int n, const struct x_svm_node * const * x_, const x_svm_param& param)
	: x(x_), x_square (NULL), kernel_type (param.kernel_type), sigma (param.sigma)
{
	if (x_ == NULL || n <= 0 || kernel_type >= x_svm_kernel::LAST)
	{
		TRACE ("invalid parameters");
		exit (1);
	}

	// construct x_square
	if (kernel_type == x_svm_kernel::RBF)
	{
		x_square = ALLOC (double, n);
		if (x_square == NULL)
		{
			TRACE ("memory exhausted");
			exit (1);
		}

		int i;
		for (i = 0 ; i < n ; i ++)
			x_square[i] = dot (x[i], x[i]);
	}

	switch (kernel_type)
	{
		case x_svm_kernel::LINEAR:
			kernel_function = &x_svm_kernel::kernel_linear;
			kernel_extended = &x_svm_kernel::kernel_linear_ext;
			break;
		case x_svm_kernel::RBF:
			kernel_function = &x_svm_kernel::kernel_rbf;
			kernel_extended = &x_svm_kernel::kernel_rbf_ext;
			break;
		default:
			TRACE ("implicit assignement of RBF kernel");
			kernel_function = &x_svm_kernel::kernel_rbf;
	}
}


x_svm_kernel::~x_svm_kernel (void)
{
	if (x_square)
	{
		free (x_square);
		x_square = NULL;
	}
}


inline double x_svm_kernel::dot (const x_svm_node * px, const x_svm_node * py)
{
	double sum = 0;
	while (px->idx != -1 && py->idx != -1)
	{
		if (px->idx == py->idx)
		{
			sum += px->val * py->val;
			px ++;
			py ++;
		} else {
			if (px->idx > py->idx)
				py ++;
			else
				px ++;
		}
	}

	return sum;
}


inline double x_svm_kernel::kernel_linear (int i, int j) const
{
	return dot (x[i], x[j]);
}


inline double x_svm_kernel::kernel_linear_ext (const x_svm_node * px, const x_svm_node * py) const
{
	return dot (px, py);
}


inline double x_svm_kernel::kernel_rbf (int i, int j) const
{
	double dot_ij = dot (x[i], x[j]);
	return exp (-sigma * (x_square[i] - dot_ij + x_square[j] - dot_ij));
}


inline double x_svm_kernel::kernel_rbf_ext (const x_svm_node * px, const x_svm_node * py) const
{
	double dot_ij = dot (px, py), dot_ii = dot (px, px), dot_jj = dot (py, py);
	return exp (-sigma * (dot_ii - dot_ij + dot_jj - dot_ij));
}
