package com.henrypump.poc;

/**
 * Created by patrickjmcd on 2/2/17.
 */

import com.mongodb.*;

import com.mongodb.client.AggregateIterable;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.model.*;

import org.bson.BSON;
import org.bson.Document;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.time.ZonedDateTime;

import com.mongodb.client.MongoCursor;
import static com.mongodb.client.model.Filters.*;

import javax.print.Doc;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
public class Database {
    private String pocDatabase = "poc";
    private MongoClient mongoClient;
    private MongoDatabase database;
    private MongoCollection<Document> cardCollection, wellDataCollection, gaugeOffCollection, wellTestCollection,
            fluidShotsCollection, runStatusCollection, wellConfigCollection, setpointCollection;

    public void initCollections(){
        cardCollection = database.getCollection("cards");
        cardCollection.createIndex(Indexes.ascending("timestamp", "strokeNumber"));

        wellDataCollection = database.getCollection("wellData");
        wellDataCollection.createIndex(Indexes.ascending("timestamp", "tagName"));

        gaugeOffCollection = database.getCollection("gaugeOff");
        gaugeOffCollection.createIndex(Indexes.ascending("timestamp", "tagName"));


        wellTestCollection = database.getCollection("wellTests");
        wellTestCollection.createIndex(Indexes.ascending("testStartTime"));

        fluidShotsCollection = database.getCollection("fluidShots");
        fluidShotsCollection.createIndex(Indexes.ascending("timestamp"));

        runStatusCollection = database.getCollection("runStatus");
        runStatusCollection.createIndex(Indexes.ascending("timestamp"));

        wellConfigCollection = database.getCollection("wellConfiguration");
        wellConfigCollection.createIndex(Indexes.ascending("timestamp"));

        setpointCollection = database.getCollection("setpoints");
        setpointCollection.createIndex(Indexes.ascending("name"));
    }


    Database(){
        mongoClient = new MongoClient();
        database = mongoClient.getDatabase(pocDatabase);

        initCollections();


    }

    Database(String dbHostname){
        mongoClient = new MongoClient(dbHostname);
        database = mongoClient.getDatabase(pocDatabase);

        initCollections();
    }

    public void close(){
        mongoClient.close();
    }

    public long getLastStrokeNum(){

        long lastStroke = 0;

        MongoCollection<Document> collection = database.getCollection("cards");
        MongoCursor<Document> cursor = collection.aggregate(
                Arrays.asList(
                        Aggregates.group("strokeNumber", Accumulators.last("lastStroke", "$strokeNumber"))
                )
        ).iterator();

        try {
            while (cursor.hasNext()) {
                long docStroke = cursor.next().getLong("lastStroke");
                if (docStroke  > lastStroke) { lastStroke = docStroke; }
            }
        } finally {
            cursor.close();
        }
        System.out.println("Found last stroke to be " + lastStroke);
        return lastStroke;
    }

    public long newCard(Card inpCard){

        List<Double> s_p = new ArrayList<Double>();
        List<Double> s_l = new ArrayList<Double>();
        List<Double> d_p = new ArrayList<Double>();
        List<Double> d_l = new ArrayList<Double>();
        int numPointsScan = inpCard.getNumPointsUsed();
        double[] sp = inpCard.getSurfacePosition();
        double[] sl = inpCard.getSurfaceLoad();
        double[] dp = inpCard.getDownholePosition();
        double[] dl = inpCard.getDownholeLoad();
        for(int i = 0; i < numPointsScan; i++ ) {
            s_p.add(sp[i]);
            s_l.add(sl[i]);
            d_p.add(dp[i]);
            d_l.add(dl[i]);
        }

        Document doc = new Document("strokeNumber", inpCard.getStrokeNumber())
                .append("surface_position", s_p)
                .append("surface_load", s_l)
                .append("downhole_position", d_p)
                .append("downhole_load", d_l)
                .append("timestamp", Date.from(ZonedDateTime.now().toInstant()));
        cardCollection.insertOne(doc);
        System.out.println("Stored stroke number " + inpCard.getStrokeNumber() + " as " + doc.getObjectId("_id"));
        return cardCollection.count();
    }


    public long newMeasurement(Measurement inpMeasurement){
        Document doc = new Document("tagname", inpMeasurement.getTagName())
                .append("currentValue", inpMeasurement.getCurrentValue())
                .append("maxDailyValue", inpMeasurement.getDailyMax())
                .append("minDailyValue", inpMeasurement.getDailyMin())
                .append("dailyAverage", inpMeasurement.getAverage())
                .append("dailyTotal", inpMeasurement.getTotal())
                .append("numMeasurements", inpMeasurement.getNumMeasurements())
                .append("timestamp", Date.from(ZonedDateTime.now().toInstant()));
        wellDataCollection.insertOne(doc);
        System.out.println("Stored " + inpMeasurement.getCurrentValue() + " for " + inpMeasurement.getTagName());
        return wellDataCollection.count();
    }

    public Document getLastStoredMeasurement(String tagName){
        MongoCursor<Document> cursor = wellDataCollection.find(eq("tagname", tagName)).sort(Sorts.descending("timestamp")).limit(1).iterator();
        Document lastStoredDoc = new Document();
        try {
            while (cursor.hasNext()) {
                lastStoredDoc = cursor.next();
            }
        } finally {
            cursor.close();
        }
        return lastStoredDoc;
    }

    public long newDailyTotal(Measurement inpMeasurement){
        Document doc = new Document("tagname", inpMeasurement.getTagName())
                .append("currentValue", inpMeasurement.getCurrentValue())
                .append("maxDailyValue", inpMeasurement.getDailyMax())
                .append("minDailyValue", inpMeasurement.getDailyMin())
                .append("dailyAverage", inpMeasurement.getAverage())
                .append("dailyTotal", inpMeasurement.getTotal())
                .append("timestamp", Date.from(ZonedDateTime.now().toInstant()));
        gaugeOffCollection.insertOne(doc);
        return gaugeOffCollection.count();

    }

    public double getPreviousDailyTotal(ZonedDateTime inpDateTime){
        String isoInpDateTime = inpDateTime.toString();
        MongoCursor<Document> cursor = wellTestCollection.find(and(eq("tagname", "Fluid Produced"), lte("timestamp", isoInpDateTime)))
                .sort(Sorts.descending("timestamp")).limit(1).iterator();
        double lastTotal = -1.0;
        try {
            while (cursor.hasNext()) {
                lastTotal = cursor.next().getDouble("dailyTotal");
            }
        } finally {
            cursor.close();
        }
        return lastTotal;
    };

    public long newWellTest(WellTest inp){
        Document doc = new Document("testStartTime", Date.from(inp.getTestStart().toInstant()))
                .append("testHours", inp.getTestHours())
                .append("testTotalBBL", inp.getTotalFluidBBL())
                .append("testOilBBL", inp.getTestOilBBL())
                .append("testWaterBBL", inp.getTestWaterBBL())
                .append("testGasMCF", inp.getTestGasMCF())
                .append("kFactor", inp.getkFactor())
                .append("oilRatio", inp.getOilRatio())
                .append("waterRatio", inp.getWaterRatio())
                .append("gasMCFRatio", inp.getGasMCFRatio());
        wellTestCollection.insertOne(doc);
        return wellTestCollection.count();
    }

    public Document getPreviousWellTest(ZonedDateTime inpDateTime){
        MongoCursor<Document> cursor = wellTestCollection.find(lte("testStartTime", Date.from(inpDateTime.toInstant())))
                .sort(Sorts.descending("testStartTime")).limit(1).iterator();
        Document lastTest = new Document("kFactor", (Double) 1.0);
        try {
            while (cursor.hasNext()) {
                lastTest = cursor.next();
            }
        } finally {
            cursor.close();
        }
        return lastTest;
    };

    public double getLatestKFactor(){
        MongoCursor<Document> cursor = wellTestCollection.find().sort(Sorts.descending("testStartTime")).limit(1).iterator();
        double kFactor = 1.0;
        try {
            while (cursor.hasNext()) {
                kFactor = cursor.next().getDouble("kFactor");
            }
        } finally {
            cursor.close();
        }
        return kFactor;
    }

    public long newFluidShot(FluidShot inp){
        String df = ZonedDateTime.now().toString();
        Document doc = new Document("timestamp", Date.from(ZonedDateTime.now().toInstant()))
                .append("fluidLevel", inp.getFluidLevel())
                .append("pumpIntakePressure", inp.getPumpIntakePressure())
                .append("frictionEstimate", inp.getFrictionEstimate());
        fluidShotsCollection.insertOne(doc);
        return fluidShotsCollection.count();
    }

    public double getLatestFrictionEstimate(){
        MongoCursor<Document> cursor = fluidShotsCollection.find().sort(Sorts.descending("timestamp")).limit(1).iterator();
        double frictionEstimate = -1;
        try {
            while (cursor.hasNext()) {
                frictionEstimate = cursor.next().getDouble("frictionEstimate");
            }
        } finally {
            cursor.close();
        }
        return frictionEstimate;
    }

    public long newRunStatus(String runStatus, String initiator){
        Document doc = new Document("status", runStatus)
                .append("initiator", initiator)
                .append("timestamp", Date.from(ZonedDateTime.now().toInstant()));
        runStatusCollection.insertOne(doc);
        return runStatusCollection.count();
    }

    public long storeWellSetup(Well thisWell){
        ArrayList<Document> taperArr = new ArrayList<Document>();
        for (int i = 1; i <= thisWell.getNumTapers(); i++){
            Document tap = new Document("length", thisWell.getRodLength(i))
                    .append("diameter", thisWell.getRodDiameter(i))
                    .append("material", thisWell.getRodMaterial(i))
                    .append("dampingFactor", thisWell.getDampingFactor(i));
            taperArr.add(tap);
        }


        Document doc = new Document("timestamp", Date.from(ZonedDateTime.now().toInstant()))
                .append("wellName", thisWell.getWellName())
                .append("deltaT", thisWell.getDt())
                .append("pumpDiameter", thisWell.getPumpDiameter())
                .append("fluidGradient", thisWell.getFluidGradient())
                .append("tubingID", thisWell.getTubingID())
                .append("tubingOD", thisWell.getTubingOD())
                .append("tubingAnchorDepth", thisWell.getTubingAnchorDepth())
                .append("structuralRating", thisWell.getStructuralRating())
                .append("stuffingBoxFriction", thisWell.getSbfriction())
                .append("tubingHeadPressure", thisWell.getTubingHeadPressure())
                .append("tapers", taperArr)
                .append("storedBy", "poc");

        wellConfigCollection.insertOne(doc);
        return wellConfigCollection.count();
    }

    public Document getLatestWellConfiguration(){
        MongoCursor<Document> cursor = wellConfigCollection.find().sort(Sorts.descending("timestamp")).limit(1).iterator();
        Document lastConfig = new Document();
        try {
            while (cursor.hasNext()) {
                lastConfig = cursor.next();
            }
        } finally {
            cursor.close();
        }
        return lastConfig;
    }

    public long storeSetpoint(String setpointName, double setpointValue){
        Document newSetpoint = new Document("name", setpointName)
                .append("value", setpointValue)
                .append("lastStored", Date.from(ZonedDateTime.now().toInstant()))
                .append("storedBy", "poc");
        setpointCollection.updateOne(eq("name", setpointName), new Document("$set", newSetpoint), (new UpdateOptions()).upsert(true));
        return setpointCollection.count();

    }

    public Double getSetpoint(String setpointName){
        Document setpointDoc = setpointCollection.find(eq("name", setpointName)).first();
        double setpoint;
        try {
            setpoint = setpointDoc.getDouble("value");
            String storedBy = setpointDoc.getString("storedBy");
            System.out.println("Setpoint retrieved from database: [" + setpointName + " = " + setpoint + " stored by " + storedBy + "]");
        } catch (ClassCastException e){
            setpoint = Double.parseDouble(setpointDoc.get("value").toString());
            String storedBy = setpointDoc.getString("storedBy");
            System.out.println("Setpoint retrieved from database: [" + setpointName + " = " + setpoint + " stored by " + storedBy + "]");
        } catch (NullPointerException e){
            setpoint = Double.MIN_VALUE;
            System.out.println("Could not find a value for " + setpointName + " in the database.");
        }
        return setpoint;
    }

}
