classdef Card < handle
    properties
        numPointsUsed = 0;
        strokeNumber;
        strokeStartTime;
        
        surfacePosition={};
        surfaceLoad={};
        downholePosition={};
        downholeLoad={};
        
        surfacePositionMax=LPPair(0,0);
        surfacePositionMin=LPPair(0,0);
        surfaceLoadMax=LPPair(0,0);
        surfaceLoadMin=LPPair(0,0);
        
        downholePositionMax=LPPair(0,0);
        downholePositionMin=LPPair(0,0);
        downholeLoadMax=LPPair(0,0);
        downholeLoadMin=LPPair(0,0);
        
        topCorner=LPPair(0,0);
        bottomCorner=LPPair(0,0);
        
        surfaceStrokeLength;
        downholeNetStrokeLength;
        downholeGrossStrokeLength;
		downholeAdjustedGrossStrokeLength;
		downholeLoadSpan;
		fluidLoad;
		pumpIntakePressure;
		fluidLevel;
		fillageEstimated;
		fillageCalculated;
		tubingMovement;
		strokeSpeed;

		structuralLoading;

		polishedRodHorsepower;
		pumpHorsepower;
		fluidBblMoved;
		fluidBblMovedAdjusted;
		waterBblMoved;
		oilBblMoved;
		gasMcfMoved;
    end
    
    methods
        function obj=Card(strokeNumber)
            % Initialization Function
            obj.strokeNumber = strokeNumber;
            obj.strokeStartTime = now;
        end
        
        function push(obj, s_pos, s_load, d_pos, d_load)
            obj.surfacePosition = [obj.surfacePosition s_pos];
            obj.surfaceLoad = [obj.surfaceLoad s_load];
            obj.downholePosition = [obj.downholePosition d_pos];
            obj.downholeLoad = [obj.downholeLoad d_load];
        end
        
        function calcStrokeData(obj, numSlices, fluidGradient, rodDepth, ...
                anchorDepth, tubingCSA, pumpArea, frictionEstimate, ...
                structuralRating, kFactor, waterBBLRatio, oilBBLRatio, ...
                gasMCFRatio)
            calculateSPM();
            obj.surfacePositionMax = obj.positionMax(obj.surfacePosition, obj.surfaceLoad, obj.numPointsUsed);
            obj.surfaceLoadMax = obj.loadMax(obj.surfacePosition, obj.surfaceLoad, obj.numPointsUsed);
            obj.surfacePositionMin = obj.positionMin(obj.surfacePosition, obj.surfaceLoad, obj.numPointsUsed);
            obj.surfaceLoadMin = obj.loadMin(obj.surfacePosition, obj.surfaceLoad, obj.numPointsUsed);

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

            obj.surfaceStrokeLength = obj.surfacePositionMax.position - obj.surfacePositionMin.position;
            obj.downholeGrossStrokeLength = obj.downholePositionMax.position - obj.downholePositionMin.position;
            obj.downholeLoadSpan = obj.downholeLoadMax.load - obj.downholeLoadMin.load;

            dxSurf = (obj.surfacePositionMax.position - obj.surfacePositionMin.position) / double(numSlices);
            dxDown = (obj.downholePositionMax.position - obj.downholePositionMin.position) / double(numSlices);

            obj.pumpHorsepower = 0.0;
            obj.polishedRodHorsepower = 0.0;
            dhDistanceTop = 0.0;
            dhDistanceBottom = 0.0;

            for i = 1:numSlices+1
                suPosTarget = obj.surfacePositionMin.position + (double(i) * dxSurf);
                dhPosTarget = obj.downholePositionMin.position + (double(i) * dxDown);
                suLoadAtTargetTop = 0.0;
                suLoadAtTargetBottom = 0.0;
                dhLoadAtTargetTop = 0.0;
                dhLoadAtTargetBottom = 0.0;

                for j = 1:obj.numPointsUsed
                    if (obj.downholePosition(j) <= dhPosTarget && obj.downholePosition(j+1) > dhPosTarget)
                        dhLoadAtTargetTop = lineResolve(obj.downholePosition(j), obj.downholePosition(j+1), obj.downholeLoad(j), obj.downholeLoad(j+1), dhPosTarget);
                    end

                    if (obj.downholePosition(j) > dhPosTarget && obj.downholePosition(j+1) >= dhPosTarget)
                        dhLoadAtTargetBottom = lineResolve(obj.downholePosition(j), obj.downholePosition(j+1), obj.downholeLoad(j), obj.downholeLoad(j+1), dhPosTarget);
                    end

                    if (obj.surfacePosition(j) <= suPosTarget && obj.surfacePosition(j+1) > suPosTarget)
                        suLoadAtTargetTop = lineResolve(obj.surfacePosition(j), obj.surfacePosition(j+1), obj.surfaceLoad(j), obj.surfaceLoad(j+1), suPosTarget);
                    end

                    if (obj.surfacePosition(j) > suPosTarget && obj.surfacePosition(j+1) >= suPosTarget)
                        suLoadAtTargetBottom = lineResolve(obj.surfacePosition(j), obj.surfacePosition(j+1), obj.surfaceLoad(j), obj.surfaceLoad(j+1), suPosTarget);
                    end
                end

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

                tDistance = obj.distanceToLine(dhPosTarget, dhLoadAtTargetTop);
                bDistance = obj.distanceToLine(dhPosTarget, dhLoadAtTargetBottom);

                if (tDistance > dhDistanceTop)
                    dhDistanceTop = tDistance;
                    obj.topCorner = LPPair(dhPosTarget, dhLoadAtTargetTop);
                end

                if (bDistance > dhDistanceBottom)
                    dhDistanceBottom = bDistance;
                    obj.bottomCorner = LPPair(dhPosTarget, dhLoadAtTargetBottom);
                end
            end

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


            if (obj.fillageEstimated > 100)
                obj.fillageEstimated = 100.0;
            end

            if (obj.fillageEstimated < 0.0)
                obj.fillageEstimated = 0.0;
            end

            if (obj.fillageCalculated > 100)
                obj.fillageCalculated = 100.0;
            end

            if (obj.fillageCalculated < 0.0)
                obj.fillageCalculated = 0.0;
            end

            obj.fluidLoad = obj.downholeLoadSpan - frictionEstimate;
            obj.pumpIntakePressure = fluidGradient * rodDepth - (obj.fluidLoad / pumpArea);
            obj.fluidLevel = obj.pumpIntakePressure / fluidGradient;
            obj.tubingMovement = 12 * (rodDepth - anchorDepth) * obj.fluidLoad / (30500000 * tubingCSA);
            obj.structuralLoading = (obj.surfaceLoadMax.load / (structuralRating * 100)) * 100;
        end
        
        function calculateSPM(obj)
            obj.strokeSpeed = 60000.0 / (now - obj.strokeStartTime);
        end
        
        function dist = distanceToLine(pos, load)
            % Finds the distance between a point and a line between the 
            % Max Position at Max Load and Min Position at Min Load
            x1 = obj.downholePositionMin.position;
            x2 = obj.downholePositionMax.position;
            y1 = obj.downholeLoadMin.load;
            y2 = obj.downholeLoadMax.load;

            dist = abs((y2-y1)* pos - (x2-x1)* load + x2*y1 - y2*x1) / sqrt(power(y2-y1, 2) + power(x2-x1,2));
        end
        
        function foundPoint = positionMax(positionArr, loadArr, arrSize)
            maxPos = positionArr(1);
            loadAtMaxP = -inf;
            for i = 1:arrSize
                maxPos = max(maxPos, positionArr(i));
                if (maxPos == positionArr(i))
                    loadAtMaxP = loadArr(i);
                end
            end
            foundPoint = LPPair(maxPos, loadAtMaxP);
        end


        function foundPoint = positionMin(positionArr, loadArr, arrSize)
            minPosition = positionArr(1);
            loadAtMinP = inf;
            for i = 1:arrSize
                minPosition = min(minPosition, positionArr(i));
                if (minPosition == positionArr(i)) 
                    loadAtMinP = loadArr(i);
                end
            end
            foundPoint = LPPair(minPosition, loadAtMinP);
        end

        function foundPoint = loadMax(positionArr, loadArr, arrSize)
            maxLoad = loadArr(1);
            posAtMaxL = -inf;
            for i = 1:arrSize
                maxLoad = max(maxLoad, loadArr(i));
                if (maxLoad == positionArr(i)) 
                    posAtMaxL = positionArr(i);
                end
            end
            foundPoint = LPPair(posAtMaxL, maxLoad);
        end

        function foundPoint = loadMin(positionArr, loadArr, arrSize)
            minLoad = loadArr(1);
            posAtMinL = inf;
            for i = 1:arrSize
                minLoad = min(minLoad, loadArr(i));
                if (minLoad == positionArr(i)) 
                    posAtMinL = positionArr(i);
                end
            end
            foundPoint = LPPair(posAtMinL, minLoad);
        end
        
    end
    
    methods(Static)
        function yTest = lineResolve(x1, x2, y1, y2, xTest)
            % Uses the standard line equation to find the y value given x
            line_m = (y2 - y1) / (x2 - x1);
            line_b = y1 - line_m * x1;
            yTest = line_m * xTest + line_b;
        end
    end
    
    
end
