#include "transformers.h"

#include <limits>
#include <math.h>


MinMaxScaler::MinMaxScaler(data_t new_min, data_t new_max) {
	if (new_min >= new_max)
		throw std::invalid_argument("Minumum should be less than maximum");
	_new_min = new_min;
	_new_max = new_max;
}

void
MinMaxScaler::_fit(const Matrix<data_t>& data, const vector<label_t>&) {
	_fit(data);
}

void
MinMaxScaler::_fit(const Matrix<data_t>& data) {
	_min = data.min(0);
	_max = data.max(0);
}

Matrix<data_t>
MinMaxScaler::_transform(const Matrix<data_t>& data) const {
	Matrix<data_t> result(data.n_rows(), data.n_cols());
	for (size_t i = 0; i < data.n_rows(); i++) {
		result.set_row(_transform(data(i)), i);
	}
	return result;
}


vector<data_t>
MinMaxScaler::_transform(const vector<data_t>& sample) const {
	vector<data_t>result(sample.size());
	for (size_t i = 0; i < sample.size(); i++) {
		data_t xstd;
		if (fabs(_max[i] - _min[i]) < SCALER_EPSILON)
			xstd = _max[i]; //??
		else
			xstd = (sample[i] - _min[i]) / (_max[i] - _min[i]);
		result[i] = xstd * (_new_max - _new_min) + _new_min;
	}
	return result;
}



void
Weight::_fit(const Matrix<data_t>& data) {
    if(data.n_cols() != _weights.size()) {
        throw std::invalid_argument("Wrong number of features");;
    }
}

void
Weight::_fit(const Matrix<data_t>& data, const vector<label_t>&) {
    _fit(data);
}


Matrix<data_t>
Weight::_transform(const Matrix<data_t>& data) const {
    Matrix<data_t> result(data.n_rows(), data.n_cols());
    for(size_t i = 0; i < data.n_rows(); i++) {
        result.set_row(_transform(data(i)), i);
    }
    return result;
}

vector<data_t>
Weight::_transform(const vector<data_t>& sample) const {
    vector<data_t> result(sample.size());
    for(size_t i = 0; i < sample.size(); i++)
        result[i] = _weights[i] * sample[i];
    return result;
}

void 
PolynomialFeatures::_fit(const Matrix<data_t>& data, const vector<label_t>&) {
	_fit(data);
}

void 
PolynomialFeatures::_fit(const Matrix<data_t>& data) {
	//_n_out_features = 0;
	//for (size_t i = 0; i < (_degree + 1); i++) {
	//	//binnomial coef
	//	_n_out_features += static_cast<size_t>(common::factorial(static_cast<double>(i + data.n_cols() - 1)) /
	//		(common::factorial(static_cast<double>(i)) * common::factorial(static_cast<double>(data.n_cols() - 1))));
	//}
	//if (!_include_bias) _n_out_features--;

	_features_comb.clear();
	std::vector<size_t> range(data.n_cols());
	for (size_t i = 0; i < data.n_cols(); i++) range[i] = i;
	if (_include_bias)
		_features_comb.push_back(vector<size_t>());
	for (size_t i = 0; i < _degree + 1; i++) {
		auto comb = common::combinations_with_rep(range, i);
		_features_comb.insert(_features_comb.end(), comb.begin(), comb.end());
	}
	_n_out_features = _features_comb.size();
}

Matrix<data_t> 
PolynomialFeatures::_transform(const Matrix<data_t>& data) const {
	Matrix<data_t> result(data.n_rows(), _n_out_features);
	for (size_t i = 0; i < data.n_rows(); i++) {
		result.set_row(_transform(data(i)), i);
	}
	return result;
}


vector<data_t> 
PolynomialFeatures::_transform(const vector<data_t>& sample) const {
	vector<data_t> res(_n_out_features, 1.0);
	for (size_t i = 0; i < _n_out_features; i++) {
		for (size_t j : _features_comb[i]) {
			res[i] *= sample[j];
		}
	}
	return res;
}


