/*
 * Copyright (C) 2022 - Florindo Smilari (spherIC)
 */
package ch.spherIC.recurvebowsight.logic;

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


public class TrajectoryCalculator {

    public static final int IDX_T = 0;
    public static final int IDX_VX = 1;
    public static final int IDX_VY = 2;
    public static final int IDX_X = 3;
    public static final int IDX_Y = 4;

    public static final Double GRAVITY = Double.valueOf(9.81);

    private static final Double CELSIUS_TO_KELVIN = 273.15;
    private static final Double R_L = 287.058;
    private static final Double R_D = 461.0;
    private static final Double PRESS_AIR = 101325.0;
    private static final Double E_T0 = 611.2;
    private static final Double T0 = 17.62;
    private static final Double T1 = 243.12;

    private final Double arrowDiameter;
    private final Double arrowCw;
    private final Double arrowWeight;
    private final Double v0;
    private final Double distance;
    private final Double deltaHeight;
    private final Double airTemperature;
    private final Double relativeHumidity;
    private final Double deltaTime;
    private final Double calculationPrecision;
    private final TrajectoryCalculation listener;

    private Double[][] flightCurve;
    private Double startAngle;
    private Double[] maxHCurve;


    /**
     * Konstruktor.
     *
     * @param arrowDiameter
     * @param arrowCw
     * @param arrowWeight
     * @param v0
     * @param distance
     * @param deltaHeight
     * @param airTemperature
     * @param relativeHumidity
     * @param deltaTime
     * @param calculationPrecision
     * @param listener
     */
    public TrajectoryCalculator(final Double arrowDiameter,
                                final Double arrowCw,
                                final Double arrowWeight,
                                final Double v0,
                                final Double distance,
                                final Double deltaHeight,
                                final Double airTemperature,
                                final Double relativeHumidity,
                                final Double deltaTime,
                                final Double calculationPrecision,
                                final TrajectoryCalculation listener) {
        super();
        this.arrowDiameter = arrowDiameter;
        this.arrowCw = arrowCw;
        this.arrowWeight = arrowWeight;
        this.v0 = v0;
        this.distance = distance;
        this.deltaHeight = deltaHeight;
        this.airTemperature = airTemperature;
        this.relativeHumidity = relativeHumidity;
        this.deltaTime = deltaTime;
        this.calculationPrecision = calculationPrecision;
        this.listener = listener;
    }


    public Double[][] calculateTrajectory(final Double startAngle, final Double frictionCoefficient) {

        List<Double[]> results = new ArrayList<Double[]>();
        Double angle = Math.toRadians(startAngle);
        Double vxOld = Math.cos(angle) * this.v0;
        Double vyOld = Math.sin(angle) * this.v0;
        Double vxNew = 0d;
        Double vyNew = 0d;
        Double xNew = 0d;
        Double yNew = 0d;
        Double xOld = 0d;
        Double yOld = 0d;
        Double totTime = 0d;
        Double k = frictionCoefficient; //calculateFrictionCoeficient(this.arrowCw, this.arrowDiameter, this.airTemperature, this.relativeHumidity);
        Double m = this.arrowWeight;
        Double[] r1 = new Double[5];

        r1[TrajectoryCalculator.IDX_T] = totTime;
        r1[TrajectoryCalculator.IDX_VX] = vxOld;
        r1[TrajectoryCalculator.IDX_VY] = vyOld;
        r1[TrajectoryCalculator.IDX_X] = xOld;
        r1[TrajectoryCalculator.IDX_Y] = yOld;
        results.add(r1);

        while (xNew <= this.distance) {

            vxNew = vxOld + this.deltaTime * -(k / m) * Math.sqrt(vxOld * vxOld + vyOld * vyOld) * vxOld;
            vyNew = vyOld + this.deltaTime * (-(k / m) * Math.sqrt(vxOld * vxOld + vyOld * vyOld) * vyOld - TrajectoryCalculator.GRAVITY);

            xNew = xOld + this.deltaTime * ((vxOld + vxNew) / 2);
            yNew = yOld + this.deltaTime * ((vyOld + vyNew) / 2);

            vxOld = vxNew;
            vyOld = vyNew;

            xOld = xNew;
            yOld = yNew;

            totTime += this.deltaTime;

            r1 = new Double[5];
            r1[TrajectoryCalculator.IDX_T] = totTime;
            r1[TrajectoryCalculator.IDX_VX] = vxOld;
            r1[TrajectoryCalculator.IDX_VY] = vyOld;
            r1[TrajectoryCalculator.IDX_X] = xOld;
            r1[TrajectoryCalculator.IDX_Y] = yOld;
            results.add(r1);
        }

        Double[] rLow = results.get(results.size() - 2);
        Double[] rHigh = results.get(results.size() - 1);
        if (rLow[IDX_X] < this.distance && rHigh[IDX_X] > this.distance) {
            Double deltaT1 = rHigh[IDX_T] - rLow[IDX_T];
            Double deltaVx1 = rHigh[IDX_VX] - rLow[IDX_VX];
            Double deltaVy1 = rHigh[IDX_VY] - rLow[IDX_VY];
            Double deltaX1 = rHigh[IDX_X] - rLow[IDX_X];
            Double deltaY1 = rHigh[IDX_Y] - rLow[IDX_Y];

            Double deltaX2 = this.distance - rLow[IDX_X];
            Double deltaY2 = deltaY1 * deltaX2 / deltaX1;
            Double deltaVx2 = deltaVx1 / deltaX1 * deltaX2;
            Double deltaVy2 = deltaVy1 / deltaY1 * deltaY2;
            Double deltaT2 = deltaT1 / deltaX1 * deltaX2;

            results.get(results.size() - 1)[IDX_T] = results.get(results.size() - 2)[IDX_T] + deltaT2;
            results.get(results.size() - 1)[IDX_VX] = results.get(results.size() - 2)[IDX_VX] + deltaVx2;
            results.get(results.size() - 1)[IDX_VY] = results.get(results.size() - 2)[IDX_VY] + deltaVy2;
            results.get(results.size() - 1)[IDX_X] = results.get(results.size() - 2)[IDX_X] + deltaX2;
            results.get(results.size() - 1)[IDX_Y] = results.get(results.size() - 2)[IDX_Y] + deltaY2;
        }


        return results.toArray(new Double[][]{});
    }


    public Double calculateFrictionCoeficient() {
        return calculateFrictionCoeficient(this.arrowCw, this.arrowDiameter, this.airTemperature, this.relativeHumidity);
    }

    public Double calculateFrictionCoeficient(final Double arrowCw, final Double arrowDiameter,
                                              final Double airTemperature, final Double relativeHumidity) {

        Double eW = TrajectoryCalculator.E_T0
                * Math.pow(Math.E, TrajectoryCalculator.T0 * airTemperature / (TrajectoryCalculator.T1 + airTemperature));

        Double rF = TrajectoryCalculator.R_L
                / (1 - relativeHumidity * eW / TrajectoryCalculator.PRESS_AIR * (1 - TrajectoryCalculator.R_L / TrajectoryCalculator.R_D));
        Double airDensity = TrajectoryCalculator.PRESS_AIR / (rF * (airTemperature + TrajectoryCalculator.CELSIUS_TO_KELVIN));

        Double friction = airDensity * arrowCw * (Math.pow(arrowDiameter, 2) / 4 * Math.PI) / 2;
        return friction;
    }


    public Double getArrowDiameter() {
        return this.arrowDiameter;
    }

    public Double getArrowCw() {
        return this.arrowCw;
    }

    public Double getArrowWeight() {
        return this.arrowWeight;
    }

    public Double getV0() {
        return this.v0;
    }

    public Double getDistance() {
        return this.distance;
    }

    public Double getDeltaHeight() {
        return this.deltaHeight;
    }

    public Double getAirTemperature() {
        return this.airTemperature;
    }

    public Double getRelativeHumidity() {
        return this.relativeHumidity;
    }

    public Double getDeltaTime() {
        return this.deltaTime;
    }

    public Double getCalculationPrecision() {
        return this.calculationPrecision;
    }

    public TrajectoryCalculation getListener() {
        return this.listener;
    }

    public Double[][] getFlightCurve() {
        return this.flightCurve;
    }

    public void setFlightCurve(final Double[][] flightCurve) {
        this.flightCurve = flightCurve;
    }

    public Double[] getMaxHCurve() {
        return this.maxHCurve;
    }

    public void setMaxHCurve(final Double[] maxHCurve) {
        this.maxHCurve = maxHCurve;
    }

    public Double getStartAngle() {
        return this.startAngle;
    }

    public void setStartAngle(final Double startAngle) {
        this.startAngle = startAngle;
    }

}
