package ec.app.regression.testcases;

import java.util.ArrayList;
import java.util.List;

import library.semantics.TestCase;
import ec.EvolutionState;
import ec.util.MersenneTwisterFast;
import ec.util.Parameter;

public abstract class FunctionFactory extends TestCaseFactory {

    protected static final String FROM = "testCases.from";
    protected static final String TO = "testCases.to";
    protected static final String COUNT = "testCases.count";

    protected boolean randomizedPoints = false;

    public boolean isRandomizedPoints() {
        return randomizedPoints;
    }

    protected void setRandomizedPoints(final boolean randomizedPoints) {
        this.randomizedPoints = randomizedPoints;
    }

    /**
     * Computes function, from which test cases are generated.
     *
     * @param x
     * @return y
     */
    protected abstract double function(Double... x);

    /**
     * Gets arity of the function.
     *
     * @return
     */
    protected abstract int arity();

    @Override
    public List<TestCase<Double>> generateTraining(final EvolutionState state) {
        this.setRandomizedPoints(false);
        return this.compute(state);
    }

    @Override
    public List<TestCase<Double>> generateTest(final EvolutionState state) {
        this.setRandomizedPoints(true);
        return this.compute(state);
    }

    @Override
    public List<TestCase<Double>> compute(final EvolutionState state) {
        double from = state.parameters.getDouble(new Parameter(FROM), null);
        double to = state.parameters.getDouble(new Parameter(TO), null);
        int count = state.parameters.getInt(new Parameter(COUNT), null);

        double[][] points = new double[this.arity()][];

        if (!this.randomizedPoints) {
            for (int i = 0; i < points.length; ++i) {
                points[i] = this.getChebyshevNodes(count, from, to);
            }


            /*for (double x = from; x <= to; x += step) {
                TestCase<Double> _case = new TestCase<Double>();

                _case.setArguments(x);
                _case.setValue(this.function(x));

                cases.add(_case);
            }*/
        } else {
            for (int i = 0; i < points.length; ++i) {
                points[i] = this.getRandomPoints(count, from, to);
            }
        }

        List<TestCase<Double>> cases = new ArrayList<TestCase<Double>>();
        Double[] arguments = new Double[this.arity()];
        int[] indexes = new int[this.arity()];
        boolean overflow = false;

        while (!overflow) {

            assert indexes.length == arguments.length;
            assert indexes.length == points.length;

            for (int i = 0; i < indexes.length; ++i) {
                arguments[i] = points[i][indexes[i]];
            }

            TestCase<Double> _case = new TestCase<Double>();

            _case.setArguments(arguments.clone());
            _case.setValue(this.function(arguments));

            cases.add(_case);


            for (int i = 0; i < indexes.length; ++i) {
                if (++indexes[i] >= points[i].length) {
                    indexes[i] = 0;
                    overflow = i >= indexes.length - 1;
                } else {
                    break;
                }
            }
        }

        assert cases.size() == Math.pow(count, this.arity());

        return cases;
    }

    private double[] getChebyshevNodes(int count, double from, double to) {
        assert to > from;
        assert count > 0;

        double[] points = new double[count];
        double count2 = count + count;
        for (int i = 0; i < count; ++i) {
            points[i] = 0.5 * (from + to + (to - from) * Math.cos((i + i + 1) * Math.PI / count2));
            assert from <= points[i] && points[i] <= to;
            assert i == 0 || points[i - 1] != points[i];
        }

        return points;
    }

    private double[] getRandomPoints(int count, double from, double to) {
        assert to > from;
        assert count > 0;

        //make pseudo-random generator deterministic
        MersenneTwisterFast random = new MersenneTwisterFast(0);

        double[] points = new double[count];
        double count2 = count + count;
        for (int i = 0; i < count; ++i) {
            points[i] = 0.5 * random.nextDouble() * (to - from) + from;
            assert from <= points[i] && points[i] <= to;
        }

        return points;
    }

    public abstract String getSymbolicRepresentation();
}
