package lt.ktu.gmj.methods;

import lt.ktu.gmj.Result;
import lt.ktu.gmj.ResultLogger;
import lt.ktu.gmj.Task;

import java.util.*;
import java.awt.*;
import java.awt.List;
import java.lang.*;
import java.math.*;

public class ExkorNew extends IPMethodBase {

	private double e = 0.00005;

	private double FindSoliution(Point p1, Point p2, double cn) {
		double x1 = p1.x;
		double x2 = p2.x;

		double v1 = p1.y - cn;
		double v2 = p2.y - cn;

		double xp = ((x2 - x1) * Math.sqrt(v1 * v2) - v1 * x2 + v2 * x1)
				/ (v2 - v1);
		double xn = ((x2 - x1) * Math.sqrt(v1 * v2) + v1 * x2 - v2 * x1)
				/ (v2 - v1);

		if (x1 < xp && xp < x2) {
			return xp;
		} else if (x1 < xn && xn < x2) {
			return xn;
		} else {
			return x1 + (x2 - x1) / 2;
		}
	}

	private double R(double x_, Point p, double minY, double cn) {
		return minY - Math.pow(x_ - p.x, 2) / (p.y - cn);
	}

	private int ArgMinR(ArrayList<Double> soliutions,
			ArgumentCollection arguments, double cn) {
		double min = Double.MAX_VALUE;
		int index = -1;

		for (int i = 0; i < soliutions.size() - 1; i++) {
			double r = R(soliutions.get(i), arguments.get(i),
					arguments.getMin().y, cn);
			if (r < min) {
				min = r;
				index = i;
			}
		}

		return index;
	}

	void CopyTo(double[] array, double[] destination) {
		for (int i = 0; i < array.length; i++) {
			destination[i] = array[i];
		}
	}

	@Override
	public Result run(ResultLogger resultlogger, Task task) {
		double[] arg = new double[task.domain().dimensions().length];
		for (int i = 0; i < arg.length; i++) {
			arg[i] = task.domain().min[i];
		}

		for (int param = 0; param < arg.length; param++) {
			ArgumentCollection arguments = new ArgumentCollection();

			double step = (task.domain().max[param] - task.domain().min[param])
					/ initialPoints;
			for (double x = task.domain().min[param]; x <= task.domain().max[param]; x += step) {
				double[] newArg = new double[arg.length];
				CopyTo(arg, newArg);
				newArg[param] = x;

				lt.ktu.gmj.Point pt = new lt.ktu.gmj.Point(task.domain());
				pt.x = newArg;

				arguments.Add(new Point(x, task.f(pt)));
			}

			ArrayList<Double> soliutions = new ArrayList<Double>();
			for (int i = 0; i < arguments.getCount() - 1; i++) {
				soliutions.add(FindSoliution(arguments.get(i),
						arguments.get(i + 1), arguments.getMin().y - e));
			}

			int iteration = 0;
			int index = -1;
			while (iteration < iterations) {
				index = ArgMinR(soliutions, arguments, arguments.getMin().y - e);

				double[] newArg = new double[arg.length];
				CopyTo(arg, newArg);
				newArg[param] = soliutions.get(index);

				lt.ktu.gmj.Point pt = new lt.ktu.gmj.Point(task.domain());
				pt.x = newArg;

				arguments.Insert(index + 1, new Point(soliutions.get(index),
						task.f(pt)));

				Result result = new Result();
				result.iteration = iteration;
				result.point = pt;
				result.value = arguments.get(index + 1).y;

				resultlogger.log(result);

				soliutions.set(
						index,
						FindSoliution(arguments.get(index),
								arguments.get(index + 1), arguments.getMin().y
										- e));

				soliutions.add(
						index + 1,
						FindSoliution(arguments.get(index + 1),
								arguments.get(index + 2), arguments.getMin().y
										- e));

				iteration++;
			}

			arg[param] = arguments.getMin().x;
		}

		Result min = resultlogger.logAt(0);
		for (int i = 0; i < iterations; i++) {
			Result result = resultlogger.logAt(i);
			if (min.value > result.value) {
				min = result;
			}
		}

		return min;
	}

	private class ArgumentCollection {
		private ArrayList<Point> list = new ArrayList<Point>();
		private Point min = new Point(Double.MAX_VALUE, Double.MAX_VALUE);

		public Point get(int index) {
			return list.get(index);
		}

		public Point getMin() {
			return min;
		}

		public int getCount() {
			return list.size();
		}

		private void Update() {
			min = new Point(Double.MAX_VALUE, Double.MAX_VALUE);
			for (int i = 0; i < list.size(); i++) {
				if (list.get(i).y < min.y) {
					min = list.get(i);
				}
			}
		}

		public void Add(Point item) {
			list.add(item);
			Update();
		}

		public void Insert(int index, Point item) {
			list.add(index, item);
			Update();
		}
	}

	private class Point {
		public double x;
		public double y;

		public Point(double x, double y) {
			this.x = x;
			this.y = y;
		}
	}
}
