package com.henrypump.poc;

import de.vandermeer.asciitable.v2.RenderedTable;
import de.vandermeer.asciitable.v2.V2_AsciiTable;
import de.vandermeer.asciitable.v2.render.V2_AsciiTableRenderer;
import de.vandermeer.asciitable.v2.render.WidthAbsoluteEven;
import de.vandermeer.asciitable.v2.themes.V2_E_TableThemes;
import static java.lang.Math.abs;
import static java.lang.Math.pow;
import static java.lang.Math.sqrt;

/**
 * Created by patrickjmcd on 1/31/17.
 */
public class Card {
    // Card Points
    private int maxPoints = 1500;
    private double[] surfacePosition = new double[maxPoints];
    private double[] downholePosition = new double[maxPoints];
    private double[] surfaceLoad = new double[maxPoints];
    private double[] downholeLoad = new double[maxPoints];
    private int numPointsUsed;

    //Card
    private long strokeNumber;
    private LPPair surfacePositionMax;
    private LPPair surfacePositionMin;
    private LPPair surfaceLoadMax;
    private LPPair surfaceLoadMin;
    private LPPair downholePositionMax;
    private LPPair downholePositionMin;
    private LPPair downholeLoadMax;
    private LPPair downholeLoadMin;

    private LPPair topCorner;
    private LPPair bottomCorner;

    private double surfaceStrokeLength;
    private double downholeNetStrokeLength;
    private double downholeGrossStrokeLength;
    private double downholeAdjustedGrossStrokeLength;
    private double downholeLoadSpan;
    private double fluidLoad;
    private double pumpIntakePressure;
    private double fluidLevel;
    private double fillageEstimated;
    private double fillageCalculated;
    private double tubingMovement;
    private double strokeSpeed;

    private double structuralLoading;

    private double polishedRodHorsepower;
    private double pumpHorsepower;
    private double fluidBBLMoved;
    private double waterBBLMoved;
    private double oilBBLMoved;
    private double gasMCFMoved;

    private long strokeStartTime;

    Card(long strokeNumber){
        this.strokeNumber = strokeNumber;
        strokeStartTime = System.currentTimeMillis();
    }

    public void setSurfacePosition(int i, double position){
        this.surfacePosition[i] = position;
    };

    public void setDownholePosition(int i, double position){
        this.downholePosition[i] = position;
    };

    public void setSurfaceLoad(int i, double load){
        this.surfaceLoad[i] = load;
    };

    public void setDownholeLoad(int i, double load){
        this.downholeLoad[i] = load;
    };

    public double[] getSurfacePosition() {
        return this.surfacePosition;
    }

    public double[] getDownholePosition() {
        return this.downholePosition;
    }

    public double[] getSurfaceLoad() {
        return this.surfaceLoad;
    }

    public double[] getDownholeLoad() {
        return this.downholeLoad;
    }

    public LPPair getSurfacePositionMax() {
        return this.surfacePositionMax;
    }

    public long getStrokeNumber() {
        return strokeNumber;
    }

    public double getSurfaceStrokeLength() {
        return surfaceStrokeLength;
    }

    public double getDownholeNetStrokeLength() {
        return downholeNetStrokeLength;
    }

    public double getDownholeGrossStrokeLength() {
        return downholeGrossStrokeLength;
    }

    public double getDownholeAdjustedGrossStrokeLength() {
        return downholeAdjustedGrossStrokeLength;
    }

    public double getDownholeLoadSpan() {
        return downholeLoadSpan;
    }

    public double getFluidLoad() {
        return fluidLoad;
    }

    public double getFillageEstimated() {
        return fillageEstimated;
    }

    public double getFillageCalculated() {
        return fillageCalculated;
    }

    public double getTubingMovement() {
        return tubingMovement;
    }

    public double getStructuralLoading() {
        return structuralLoading;
    }

    public double getPumpIntakePressure() {
        return pumpIntakePressure;
    }

    public double getFluidLevel() {
        return fluidLevel;
    }

    public double getStrokeSpeed() {
        return strokeSpeed;
    }

    public double getPolishedRodHorsepower() {
        return polishedRodHorsepower;
    }

    public double getPumpHorsepower() {
        return pumpHorsepower;
    }

    public LPPair getSurfaceLoadMax() {
        return surfaceLoadMax;
    }

    public LPPair getSurfaceLoadMin() {
        return surfaceLoadMin;
    }

    public LPPair getDownholeLoadMax() {
        return downholeLoadMax;
    }

    public LPPair getDownholeLoadMin() {
        return downholeLoadMin;
    }

    public double getFluidBBLMoved() {
        return fluidBBLMoved;
    }

    public double getWaterBBLMoved() {
        return waterBBLMoved;
    }

    public double getOilBBLMoved() {
        return oilBBLMoved;
    }

    public double getGasMCFMoved() {
        return gasMCFMoved;
    }

    public int getNumPointsUsed() {
        return numPointsUsed;
    }

    public void setNumPointsUsed(int numPointsUsed) {
        this.numPointsUsed = numPointsUsed;
    }

    private void calculateSPM(){
        long now  = System.currentTimeMillis();
        long strokeMillis = now - strokeStartTime;
        strokeSpeed = 60000.0 / (double)(now - strokeStartTime);
    }

    private static double lineResolve(double x1, double x2, double y1, double y2, double xTest)
    {
        double line_m = (y2 - y1) / (x2 - x1);
        double line_b = y1 - line_m * x1;
        double yTest = line_m * xTest + line_b;
        return yTest;
    }

    private static LPPair positionMax(double[] positionArr, double[] loadArr, int arrSize) {
        double maxPos = positionArr[0];
        double loadAtMaxP = Double.MIN_VALUE;

        for(int i = 0; i < arrSize; i++) {
            maxPos = Math.max(maxPos, positionArr[i]);
            if (maxPos == positionArr[i]) loadAtMaxP = loadArr[i];
        }
        return new LPPair(maxPos, loadAtMaxP);
    }

    private static LPPair positionMin(double[] positionArr, double[] loadArr, int arrSize) {
        double minPos = positionArr[0];
        double loadAtMinP = Double.MAX_VALUE;

        for(int i = 0; i < arrSize; i++) {
            minPos = Math.min(minPos, positionArr[i]);
            if (minPos == positionArr[i]) loadAtMinP = loadArr[i];
        }
        return new LPPair(minPos, loadAtMinP);
    }

    private static LPPair loadMax(double[] positionArr, double[] loadArr, int arrSize) {
        double maxLoad = loadArr[0];
        double posAtMaxL = Double.MIN_VALUE;

        for(int i = 0; i < arrSize; i++) {
            maxLoad = Math.max(maxLoad, loadArr[i]);
            if (maxLoad == positionArr[i]) posAtMaxL = positionArr[i];
        }
        return new LPPair(posAtMaxL, maxLoad);
    }

    private static LPPair loadMin(double[] positionArr, double[] loadArr, int arrSize) {
        double minLoad = loadArr[0];
        double posAtMinL = Double.MAX_VALUE;

        for(int i = 0; i < arrSize; i++) {
            minLoad = Math.min(minLoad, loadArr[i]);
            if (minLoad == positionArr[i]) posAtMinL = positionArr[i];
        }
        return new LPPair(posAtMinL, minLoad);
    }

    private double distanceToLine(double pos, double load)
    {
        double x1 = downholePositionMin.getPosition();
        double x2 = downholePositionMax.getPosition();
        double y1 = downholeLoadMin.getLoad();
        double y2 = downholeLoadMax.getLoad();

        return abs((y2-y1)*pos - (x2-x1)*load + x2*y1 - y2*x1) / sqrt(pow(y2-y1, 2) + pow(x2-x1,2));
    };

    void calcStrokeData(int numSlices, double fluidGradient, double rodDepth, double anchorDepth, double tubingCSA,
                        double pumpArea, double frictionEstimate, double structuralRating,
                        double waterBBLRatio, double oilBBLRatio, double gasMCFRatio)
    {
        calculateSPM();
        surfacePositionMax = positionMax(surfacePosition, surfaceLoad, numPointsUsed);
        surfaceLoadMax = loadMax(surfacePosition, surfaceLoad, numPointsUsed);
        surfacePositionMin = positionMin(surfacePosition, surfaceLoad, numPointsUsed);
        surfaceLoadMin = loadMin(surfacePosition, surfaceLoad, numPointsUsed);

        downholePositionMax = positionMax(downholePosition, downholeLoad, numPointsUsed);
        downholeLoadMax = loadMax(downholePosition, downholeLoad, numPointsUsed);
        downholePositionMin = positionMin(downholePosition, downholeLoad, numPointsUsed);
        downholeLoadMin = loadMin(downholePosition, downholeLoad, numPointsUsed);

        surfaceStrokeLength = surfacePositionMax.getPosition() - surfacePositionMin.getPosition();
        downholeGrossStrokeLength = downholePositionMax.getPosition() - downholePositionMin.getPosition();
        downholeLoadSpan = downholeLoadMax.getLoad() - downholeLoadMin.getLoad();

        double dxSurf = (surfacePositionMax.getPosition() - surfacePositionMin.getPosition()) / (float) numSlices;
        double dxDown = (downholePositionMax.getPosition() - downholePositionMin.getPosition()) / (float) numSlices;

        pumpHorsepower = 0.0;
        polishedRodHorsepower = 0.0;
        double dhDistanceTop = 0.0;
        double dhDistanceBottom = 0.0;

        for (int i = 1; i < numSlices+1; i++)
        {
            double suPosTarget = surfacePositionMin.getPosition() + ((double) i * dxSurf);
            double dhPosTarget = downholePositionMin.getPosition() + ((double) i * dxDown);
            double suLoadAtTargetTop = 0.0;
            double suLoadAtTargetBottom = 0.0;
            double dhLoadAtTargetTop = 0.0;
            double dhLoadAtTargetBottom = 0.0;

            for(int j = 0; j < numPointsUsed - 1; j++)
            {
                if (downholePosition[j] <= dhPosTarget && downholePosition[j+1] > dhPosTarget){
                    dhLoadAtTargetTop = lineResolve(downholePosition[j], downholePosition[j+1], downholeLoad[j], downholeLoad[j+1], dhPosTarget);
                }

                if (downholePosition[j] > dhPosTarget && downholePosition[j+1] >= dhPosTarget){
                    dhLoadAtTargetBottom = lineResolve(downholePosition[j], downholePosition[j+1], downholeLoad[j], downholeLoad[j+1], dhPosTarget);
                }

                if (surfacePosition[j] <= suPosTarget && surfacePosition[j+1] > suPosTarget){
                    suLoadAtTargetTop = lineResolve(surfacePosition[j], surfacePosition[j+1], surfaceLoad[j], surfaceLoad[j+1], suPosTarget);
                }

                if (surfacePosition[j] > suPosTarget && surfacePosition[j+1] >= suPosTarget){
                    suLoadAtTargetBottom = lineResolve(surfacePosition[j], surfacePosition[j+1], surfaceLoad[j], surfaceLoad[j+1], suPosTarget);
                }
            }

            polishedRodHorsepower += (dxSurf / 12.0) * (suLoadAtTargetTop - suLoadAtTargetBottom) * (strokeSpeed / 33000.0);
            pumpHorsepower += (dxDown / 12.0) * (dhLoadAtTargetTop - dhLoadAtTargetBottom) * (strokeSpeed / 33000.0);

            double tDistance = distanceToLine(dhPosTarget, dhLoadAtTargetTop);
            double bDistance = distanceToLine(dhPosTarget, dhLoadAtTargetBottom);

            if (tDistance > dhDistanceTop)
            {
                dhDistanceTop = tDistance;
                topCorner = new LPPair(dhPosTarget, dhLoadAtTargetTop);
            }

            if (bDistance > dhDistanceBottom)
            {
                dhDistanceBottom = bDistance;
                bottomCorner = new LPPair(dhPosTarget, dhLoadAtTargetBottom);
            }
        }

        downholeAdjustedGrossStrokeLength = downholePositionMax.getPosition() - topCorner.getPosition();
        downholeNetStrokeLength = bottomCorner.getPosition() - downholePositionMin.getPosition();
        fillageCalculated = (downholeNetStrokeLength / downholeAdjustedGrossStrokeLength) * 100.0;
        fillageEstimated =(downholeNetStrokeLength / downholeGrossStrokeLength) * 100.0;
        fluidBBLMoved = downholeNetStrokeLength * pumpArea * 0.00010307;
        oilBBLMoved = fluidBBLMoved * oilBBLRatio;
        waterBBLMoved = fluidBBLMoved * waterBBLRatio;
        gasMCFMoved = fluidBBLMoved * gasMCFRatio;


        if (fillageEstimated > 100)
            fillageEstimated = 100.0;

        if (fillageCalculated > 100)
            fillageCalculated = 100.0;

        fluidLoad = downholeLoadSpan - frictionEstimate;
        pumpIntakePressure = fluidGradient * rodDepth - (fluidLoad / pumpArea);
        //printf("PIP = %f * %f - (%f / %f) = %f\n", fluidGradient, rodDepth, fluidLoad,  pumpArea, pumpIntakePressure);
        fluidLevel = pumpIntakePressure / fluidGradient;
        tubingMovement = 12 * (rodDepth - anchorDepth) * fluidLoad / (30500000 * tubingCSA);
        structuralLoading = (surfaceLoadMax.getLoad() / (structuralRating * 100)) * 100;
    };

    public void printCard(String printType, boolean printDataTableToo){
        if(printType.toLowerCase().equals("table")) {
            V2_AsciiTable pointsTable = new V2_AsciiTable();
            pointsTable.addRule();
            pointsTable.addRow("Surface Position", "Surface Load", "Downhole Position", "Downhole Load");
            pointsTable.addRule();
            for (int i = 0; i < numPointsUsed; i++) {
                pointsTable.addRow(surfacePosition[i], surfaceLoad[i], downholePosition[i], downholeLoad[i]);
            }
            pointsTable.addRule();
            V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
            rend.setTheme(V2_E_TableThemes.UTF_LIGHT.get());
            rend.setWidth(new WidthAbsoluteEven(100));
            RenderedTable rt = rend.render(pointsTable);
            System.out.println(rt);
            System.out.println();
        } else if (printType.toLowerCase().equals("csv")){
            System.out.println("surf_pos,surf_load,down_pos,down_load");
            for (int i = 0; i < numPointsUsed; i++) {
                System.out.printf("%f,%f,%f,%f\n", surfacePosition[i], surfaceLoad[i], downholePosition[i], downholeLoad[i]);
            }
            System.out.println();
        }

        if(printDataTableToo){
            V2_AsciiTable dataTable = new V2_AsciiTable();
            dataTable.addRule();
            dataTable.addRow("Card " + strokeNumber, "Value");
            dataTable.addStrongRule();
            dataTable.addRow("Fill % (Est.)", fillageEstimated);
            dataTable.addRow("Fill % (Calc.)", fillageCalculated);
            dataTable.addRule();
            dataTable.addRow("Surf. Stroke Length", surfaceStrokeLength);
            dataTable.addRow("Down. Gross Stroke", downholeGrossStrokeLength);
            dataTable.addRow("Down. Net Stroke", downholeNetStrokeLength);
            dataTable.addRow("Tubing Movement", tubingMovement);
            dataTable.addRule();
            dataTable.addRow("Polished Rod HP", polishedRodHorsepower);
            dataTable.addRow("Pump HP", pumpHorsepower);
            dataTable.addRule();
            dataTable.addRow("Stroke Speed", strokeSpeed);
            dataTable.addRule();
            dataTable.addRow("Fluid Load", fluidLoad);
            dataTable.addRow("Pump Intake Pressure", pumpIntakePressure);
            dataTable.addRow("Fluid Level", fluidLevel);
            dataTable.addRule();
            dataTable.addRow("Structural Loading", structuralLoading);
            dataTable.addRule();
            V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
            rend.setTheme(V2_E_TableThemes.UTF_LIGHT.get());
            rend.setWidth(new WidthAbsoluteEven(50));
            RenderedTable rt = rend.render(dataTable);
            System.out.println(rt);
            System.out.println();
        }
    }
}
