/*
 * Copyright 2010 - 2011, Haiku.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Clemens Zeidler <haiku@clemens-zeidler.de>
 */
 
 
#include "ActiveSetSolver.h"
 
#include <algorithm>
#include <stdio.h>
 
#include "LayoutOptimizer.h"
 
 
// #define DEBUG_ACTIVE_SOLVER
 
#ifdef DEBUG_ACTIVE_SOLVER
#define TRACE(x...) printf(x)
#else
#define TRACE(x...) /* nothing */
#endif
 
 
using namespace LinearProgramming;
using namespace std;
 
 
EquationSystem::EquationSystem(int32 rows, int32 columns)
	:
	fRows(rows),
	fColumns(columns)
{
	fMatrix = allocate_matrix(fRows, fColumns);
	fB = new double[fColumns];
	// better init all values to prevent side cases where not all variables
	// needed to solve the problem, coping theses values to the results could
	// cause problems
	for (int i = 0; i < fColumns; i++)
		fB[i] = 0;
	zero_matrix(fMatrix, fRows, fColumns);
 
	fRowIndices = new int32[fRows];
	fColumnIndices = new int32[fColumns];
	for (int i = 0; i < fRows; i++)
		fRowIndices[i] = i;
	for (int i = 0; i < fColumns; i++)
		fColumnIndices[i] = i;
}
 
 
EquationSystem::~EquationSystem()
{
	free_matrix(fMatrix);
	delete[] fB;
	delete[] fRowIndices;
	delete[] fColumnIndices;
}
 
 
void
EquationSystem::SetRows(int32 rows)
{
	fRows = rows;
}
 
 
int32
EquationSystem::Rows()
{
	return fRows;
}
 
 
int32
EquationSystem::Columns()
{
	return fColumns;
}
 
 
bool
EquationSystem::InRange(int32 row, int32 column)
{
	if (row < 0 || row >= fRows)
		return false;
	if (column < 0 || column >= fColumns)
		return false;
	return true;
}
 
 
double&
EquationSystem::A(int32 row, int32 column)
{
	return fMatrix[fRowIndices[row]][fColumnIndices[column]];
}
 
 
double&
EquationSystem::B(int32 row)
{
	return fB[row];
}
 
 
void
EquationSystem::Results(double* results, int32 size)
{
	for (int i = 0; i < size; i++)
		results[i] = 0;
	for (int i = 0; i < fColumns; i++) {
		int32 index = fColumnIndices[i];
		if (index < fRows)
			results[index] = fB[i];
	}
}
 
 
void
EquationSystem::SwapColumn(int32 i, int32 j)
{
	swap(fColumnIndices[i], fColumnIndices[j]);
}
 
 
void
EquationSystem::SwapRow(int32 i, int32 j)
{
	swap(fRowIndices[i], fRowIndices[j]);
	swap(fB[i], fB[j]);
}
 
 
bool
EquationSystem::GaussianElimination()
{
	// basic solve
	for (int i = 0; i < fRows; i++) {
		// find none zero element
		int swapRow = -1;
		for (int r = i; r < fRows; r++) {
			double& value = fMatrix[fRowIndices[r]][fColumnIndices[i]];
			if (fuzzy_equals(value, 0))
				continue;
			swapRow = r;
			break;
		}
		if (swapRow == -1) {
			int swapColumn = -1;
			for (int c = i + 1; c < fColumns; c++) {
				double& value = fMatrix[fRowIndices[i]][fColumnIndices[c]];
				if (fuzzy_equals(value, 0))
					continue;
				swapRow = i;
				swapColumn = c;
				break;
			}
			if (swapColumn == -1) {
				TRACE("can't solve column %i\n", i);
				return false;
			}
			SwapColumn(i, swapColumn);
		}
		if (i != swapRow)
			SwapRow(i, swapRow);
 
		// normalize
		_EliminateColumn(i, i + 1, fRows - 1);
	}
	return true;
}
 
 
bool
EquationSystem::GaussJordan()
{
	if (!GaussianElimination())
		return false;
 
	for (int32 i = fRows - 1; i >= 0; i--)
		_EliminateColumn(i, 0, i - 1);
 
	return true;
}
 
 
void
EquationSystem::GaussJordan(int32 i)
{
	_EliminateColumn(i, 0, fRows - 1);
}
 
 
void
EquationSystem::RemoveLinearlyDependentRows()
{
	double** temp = allocate_matrix(fRows, fColumns);
	bool independentRows[fRows];
 
	// copy to temp
	copy_matrix(fMatrix, temp, fRows, fColumns);
	int nIndependent = compute_dependencies(temp, fRows, fColumns,
		independentRows);
	if (nIndependent == fRows)
		return;
 
	// remove the rows
	for (int i = 0; i < fRows; i++) {
		if (!independentRows[i]) {
			int lastDepRow = -1;
			for (int d = fRows - 1; d > i; d--) {
				if (independentRows[d]) {
					lastDepRow = d;
					break;
				}
			}
			if (lastDepRow < 0)
				break;
			SwapRow(i, lastDepRow);
			fRows--;
		}
	}
	fRows = nIndependent;
 
	free_matrix(temp);
}
 
 
void
EquationSystem::RemoveUnusedVariables()
{
	for (int c = 0; c < fColumns; c++) {
		bool used = false;
		for (int r = 0; r < fRows; r++) {
			if (!fuzzy_equals(fMatrix[r][fColumnIndices[c]], 0)) {
				used = true;
				break;
			}
		}
		if (used)
			continue;
 
		//MoveColumnRight(c, fColumns - 1);
		SwapColumn(c, fColumns - 1);
		fColumns--;
		c--;
	}
}
 
 
void
EquationSystem::MoveColumnRight(int32 i, int32 target)
{
	int32 index = fColumnIndices[i];
	for (int c = i; c < target; c++)
		fColumnIndices[c] = fColumnIndices[c + 1];
	fColumnIndices[target] = index;
}
 
 
void
EquationSystem::Print()
{
	for (int m = 0; m < fRows; m++) {
		for (int n = 0; n < fColumns; n++)
			printf("%.1f ", fMatrix[fRowIndices[m]][fColumnIndices[n]]);
		printf("= %.1f\n", fB[m]);
	}
}
 
 
void
EquationSystem::_EliminateColumn(int32 column, int32 startRow, int32 endRow)
{
	double value = fMatrix[fRowIndices[column]][fColumnIndices[column]];
	if (value != 1.) {
		for (int j = column; j < fColumns; j++)
			fMatrix[fRowIndices[column]][fColumnIndices[j]] /= value;
		fB[column] /= value;
	}
 
	for (int r = startRow; r < endRow + 1; r++) {
		if (r == column)
			continue;
		double q = -fMatrix[fRowIndices[r]][fColumnIndices[column]];
		// don't need to do anything, since matrix is typically sparse this
		// should save some work
		if (fuzzy_equals(q, 0))
			continue;
		for (int c = column; c < fColumns; c++)
			fMatrix[fRowIndices[r]][fColumnIndices[c]]
				+= fMatrix[fRowIndices[column]][fColumnIndices[c]] * q;
 
		fB[r] += fB[column] * q;
	}
}
 
 
namespace {
 
 
Constraint*
AddMinConstraint(LinearSpec* spec, Variable* var)
{
	return spec->AddConstraint(1, var, kEQ, 0, 5, 5);
}
 
 
Constraint*
AddMaxConstraint(LinearSpec* spec, Variable* var)
{
	static const double kHugeValue = 32000;
	return spec->AddConstraint(1, var, kEQ, kHugeValue, 5, 5);
}
 
};
 
 
ActiveSetSolver::ActiveSetSolver(LinearSpec* linearSpec)
	:
	SolverInterface(linearSpec),
 
	fVariables(linearSpec->UsedVariables()),
	fConstraints(linearSpec->Constraints())
{
 
}
 
 
ActiveSetSolver::~ActiveSetSolver()
{
 
}
 
 
/* Using algorithm found in:
Solving Inequalities and Proving Farkas's Lemma Made Easy
David Avis and Bohdan Kaluzny
The American Mathematical Monthly
Vol. 111, No. 2 (Feb., 2004), pp. 152-157 */
static bool
solve(EquationSystem& system)
{
	// basic solve
	if (!system.GaussJordan())
		return false;
 
	bool done = false;
	while (!done) {
		double smallestB = HUGE_VALF;
		int smallestBRow = -1;
		for (int row = 0; row < system.Rows(); row++) {
			if (system.B(row) > 0 || fuzzy_equals(system.B(row), 0))
				continue;
 
			double bValue = fabs(system.B(row));
			if (bValue < smallestB) {
				smallestB = bValue;
				smallestBRow = row;
			}
		}
		if (smallestBRow == -1) {
			done = true;
			break;
		}
 
		int negValueCol = -1;
		for (int col = system.Rows(); col < system.Columns(); col++) {
			double value = system.A(smallestBRow, col);
			if (value > 0 || fuzzy_equals(value, 0))
				continue;
			negValueCol = col;
			break;
		}
		if (negValueCol == -1) {
			TRACE("can't solve\n");
			return false;
		}
 
		system.SwapColumn(smallestBRow, negValueCol);
 
		// eliminate
		system.GaussJordan(smallestBRow);
	}
 
	return true;
}
 
 
static bool is_soft_inequality(Constraint* constraint)
{
	if (constraint->PenaltyNeg() <= 0. && constraint->PenaltyPos() <= 0.)
		return false;
	if (constraint->Op() != kEQ)
		return true;
	return false;
}
 
 
class SoftInequalityAdder {
public:
	SoftInequalityAdder(LinearSpec* linSpec, ConstraintList& allConstraints)
		:
		fLinearSpec(linSpec)
	{
		for (int32 c = 0; c < allConstraints.CountItems(); c++) {
			Constraint* constraint = allConstraints.ItemAt(c);
			if (!is_soft_inequality(constraint))
				continue;
	
			Variable* variable = fLinearSpec->AddVariable();
			variable->SetRange(0, 20000);
 
			Constraint* helperConst = fLinearSpec->AddConstraint(1, variable,
				kEQ, 0, constraint->PenaltyNeg(), constraint->PenaltyPos());
			fInequalitySoftConstraints.AddItem(helperConst);
			
			double coeff = -1;
			if (constraint->Op() == kGE)
				coeff = 1;
	
			Constraint* modifiedConstraint = new Constraint(constraint);
			allConstraints.AddItem(modifiedConstraint, c);
			allConstraints.RemoveItemAt(c + 1);
			fModifiedIneqConstraints.AddItem(modifiedConstraint);
			modifiedConstraint->LeftSide()->AddItem(
				new Summand(coeff, variable));
		}
		for (int32 c = 0; c < fInequalitySoftConstraints.CountItems(); c++)
			allConstraints.AddItem(fInequalitySoftConstraints.ItemAt(c));
	}
 
	~SoftInequalityAdder()
	{
		for (int32 c = 0; c < fModifiedIneqConstraints.CountItems(); c++)
			delete fModifiedIneqConstraints.ItemAt(c);
		for (int32 c = 0; c < fInequalitySoftConstraints.CountItems(); c++) {
			Constraint* con = fInequalitySoftConstraints.ItemAt(c);
			fLinearSpec->RemoveVariable(con->LeftSide()->ItemAt(0)->Var());
				// this also deletes the constraint
		}
	}
 
private:
	LinearSpec*		fLinearSpec;
	ConstraintList	fModifiedIneqConstraints;
	ConstraintList	fInequalitySoftConstraints;
};
 
 
ResultType
ActiveSetSolver::Solve()
{
	
	// make a copy of the original constraints and create soft inequality
	// constraints
	ConstraintList allConstraints(fConstraints);
	SoftInequalityAdder adder(fLinearSpec, allConstraints);
 
	int32 nConstraints = allConstraints.CountItems();
	int32 nVariables = fVariables.CountItems();
 
	if (nVariables > nConstraints) {
		TRACE("More variables then constraints! vars: %i, constraints: %i\n",
			(int)nVariables, (int)nConstraints);
		return kInfeasible;
	}
 
	/* First find an initial solution and then optimize it using the active set
	method. */
	EquationSystem system(nConstraints, nVariables + nConstraints);
 
	int32 slackIndex = nVariables;
	// setup constraint matrix and add slack variables if necessary
	int32 rowIndex = 0;
	for (int32 c = 0; c < nConstraints; c++) {
		Constraint* constraint = allConstraints.ItemAt(c);
		if (is_soft(constraint))
			continue;
		SummandList* leftSide = constraint->LeftSide();
		system.B(rowIndex) = constraint->RightSide();
		for (int32 sIndex = 0; sIndex < leftSide->CountItems(); sIndex++ ) {
			Summand* summand = leftSide->ItemAt(sIndex);
			double coefficient = summand->Coeff();
			int32 columnIndex = summand->VariableIndex();
			system.A(rowIndex, columnIndex) = coefficient;
		}
		if (constraint->Op() == kLE) {
			system.A(rowIndex, slackIndex) = 1;
			slackIndex++;
		} else if (constraint->Op() == kGE) {
			system.A(rowIndex, slackIndex) = -1;
			slackIndex++;
		}
		rowIndex++;
	}
 
	system.SetRows(rowIndex);
 
	system.RemoveLinearlyDependentRows();
	system.RemoveUnusedVariables();
 
	if (!solve(system))
		return kInfeasible;
 
	double results[nVariables + nConstraints];
	system.Results(results, nVariables + nConstraints);
	TRACE("base system solved\n");
 
	LayoutOptimizer optimizer(allConstraints, nVariables);
	if (!optimizer.Solve(results))
		return kInfeasible;
 
	// back to the variables
	for (int32 i = 0; i < nVariables; i++)
		fVariables.ItemAt(i)->SetValue(results[i]);
 
	for (int32 i = 0; i < nVariables; i++)
		TRACE("var %f\n", results[i]);
 
	return kOptimal;
}
 
 
bool
ActiveSetSolver::VariableAdded(Variable* variable)
{
	if (fVariableGEConstraints.AddItem(NULL) == false)
		return false;
	if (fVariableLEConstraints.AddItem(NULL) == false) {
		// clean up
		int32 count = fVariableGEConstraints.CountItems();
		fVariableGEConstraints.RemoveItemAt(count - 1);
		return false;	
	}
	return true;
}
 
 
bool
ActiveSetSolver::VariableRemoved(Variable* variable)
{
	fVariableGEConstraints.RemoveItemAt(variable->GlobalIndex());
	fVariableLEConstraints.RemoveItemAt(variable->GlobalIndex());
	return true;
}
 
 
bool
ActiveSetSolver::VariableRangeChanged(Variable* variable)
{
	double min = variable->Min();
	double max = variable->Max();
	int32 variableIndex = variable->GlobalIndex();
 
	Constraint* constraintGE = fVariableGEConstraints.ItemAt(variableIndex);
	Constraint* constraintLE = fVariableLEConstraints.ItemAt(variableIndex);
	if (constraintGE == NULL && min > -20000) {
		constraintGE = fLinearSpec->AddConstraint(1, variable, kGE, 0);
		if (constraintGE == NULL)
			return false;
		constraintGE->SetLabel("Var Min");
		fVariableGEConstraints.RemoveItemAt(variableIndex);
		fVariableGEConstraints.AddItem(constraintGE, variableIndex);
	}
	if (constraintLE == NULL && max < 20000) {
		constraintLE = fLinearSpec->AddConstraint(1, variable, kLE, 20000);
		if (constraintLE == NULL)
			return false;
		constraintLE->SetLabel("Var Max");
		fVariableLEConstraints.RemoveItemAt(variableIndex);
		fVariableLEConstraints.AddItem(constraintLE, variableIndex);
	}
 
	if (constraintGE)
		constraintGE->SetRightSide(min);
	if (constraintLE)
		constraintLE->SetRightSide(max);
	return true;
}
 
 
bool
ActiveSetSolver::ConstraintAdded(Constraint* constraint)
{
	return true;
}
 
 
bool
ActiveSetSolver::ConstraintRemoved(Constraint* constraint)
{
	return true;
}
 
 
bool
ActiveSetSolver::LeftSideChanged(Constraint* constraint)
{
	return true;
}
 
 
bool
ActiveSetSolver::RightSideChanged(Constraint* constraint)
{
	return true;
}
 
 
bool
ActiveSetSolver::OperatorChanged(Constraint* constraint)
{
	return true;
}
 
 
bool
ActiveSetSolver::PenaltiesChanged(Constraint* constraint)
{
	return true;
}
 
 
bool
ActiveSetSolver::SaveModel(const char* fileName)
{
	return false;
}
 
 
ResultType
ActiveSetSolver::FindMaxs(const VariableList* variables)
{
	return _FindWithConstraintsNoSoft(variables, AddMaxConstraint);
}
 
 
ResultType
ActiveSetSolver::FindMins(const VariableList* variables)
{
	return _FindWithConstraintsNoSoft(variables, AddMinConstraint);
}
 
 
void
ActiveSetSolver::GetRangeConstraints(const Variable* var,
	const Constraint** _min, const Constraint** _max) const
{
	int32 variableIndex = var->GlobalIndex();
 
	if (_min)
		*_min = fVariableGEConstraints.ItemAt(variableIndex);
	if (_max)
		*_max = fVariableLEConstraints.ItemAt(variableIndex);
}
 
 
void
ActiveSetSolver::_RemoveSoftConstraint(ConstraintList& list)
{
	ConstraintList allConstraints = fLinearSpec->Constraints();
	for (int i = 0; i < allConstraints.CountItems(); i++) {
		Constraint* constraint = allConstraints.ItemAt(i);
		// soft eq an ineq constraint?
		if (constraint->PenaltyNeg() <= 0. && constraint->PenaltyPos() <= 0.)
			continue;
 
		if (RemoveConstraint(constraint, false, false) == true)
			list.AddItem(constraint);
	}
}
 
 
void
ActiveSetSolver::_AddSoftConstraint(const ConstraintList& list)
{
	for (int i = 0; i < list.CountItems(); i++) {
		Constraint* constraint = list.ItemAt(i);
		// at least don't leak it
		if (AddConstraint(constraint, false) == false)
			delete constraint;
	}
}
 
 
ResultType
ActiveSetSolver::_FindWithConstraintsNoSoft(const VariableList* variables,
	AddConstraintFunc constraintFunc)
{
	ConstraintList softConstraints;
	_RemoveSoftConstraint(softConstraints);
 
	ConstraintList constraints;
	for (int32 i = 0; i < variables->CountItems(); i++)
		constraints.AddItem(constraintFunc(fLinearSpec, variables->ItemAt(i)));
 
	ResultType result = Solve();
	for (int32 i = 0; i < constraints.CountItems(); i++)
		fLinearSpec->RemoveConstraint(constraints.ItemAt(i));
 
	_AddSoftConstraint(softConstraints);
 
	if (result != kOptimal)
		TRACE("Could not solve the layout specification (%d). ", result);
 
	return result;
}

V654 The condition '!done' of loop is always true.