package org.chargecar.prize.policies;

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

import org.chargecar.prize.battery.BatteryModel;
import org.chargecar.prize.util.PointFeatures;
import org.chargecar.prize.util.PowerFlowException;
import org.chargecar.prize.util.PowerFlows;
import org.chargecar.prize.util.TripFeatures;

/**
 * The strategy of this policy is to trickle the cap at the average current demand.
 * Credits: This policy is an increasingly distant descendant of SpeedPolicy by Alex Styler.
 * 
 * @author Will Snook
 */

public class AdaptiveTrickle1 implements Policy {
    private BatteryModel modelCap;
    private BatteryModel modelBatt;
    private String name = "Adaptive Trickle v1";
    private String driver = "";
    private List<Double> accelTail;
    private List<Double> accelHist;
    
    
    public void beginTrip(TripFeatures tripFeatures, BatteryModel batteryClone, BatteryModel capacitorClone) {
	modelCap = capacitorClone;
	modelBatt = batteryClone;	
	driver = tripFeatures.getDriver();
	accelTail = new ArrayList<Double>();
	accelHist = new ArrayList<Double>();
    }
    
    public PowerFlows calculatePowerFlows(PointFeatures pf) {
	double demand = pf.getPowerDemand();
	int period = pf.getPeriodMS();
	double capMin = modelCap.getMinCurrent(period);
	double capMax = modelCap.getMaxCurrent(period);
	double trickle = -10545.0;  // Normal trickle rate
	double target = 1.00;       // Default target charge level for capacitor
	double knee = 0.0;          // Threshold below which the target charge level will be used
	
	// Track the average acceleration
	// Adjust trickle charging parameters based on the driver
	int window = 30;
	if(driver.equals("alik")) {
	    window = 20;
	} else if(driver.equals("arnold")) {
	    window = 14;
	} else if(driver.equals("illah")) {
	    // The adaptive algorithm does badly for Illah. This is the optimal window, but a non-adaptive trickle does better.
	    window = 30;
	} else if(driver.equals("mike")) {
	    window = 34;
	} else if(driver.equals("nikolay")) {
	    window = 43;
	} else if(driver.equals("thor")) {
	    // The adaptive algorithm also does badly for thor. This is the optimal window though.
	    window = 70;
	}
	accelTail.add(demand);
    	if(accelTail.size()>window){
    	    accelTail.remove(0);
	}	    
	double accelTotal = 0.0;
	for(int i=0; i<accelTail.size(); i++){
	    accelTotal += Math.min(-0.000000001,accelTail.get(i));
	}
	accelHist.add(accelTotal / accelTail.size());	
	
	// Adjust trickle charging parameters based on the driver
	if(driver.equals("alik")) {
	    trickle = accelHist.get(accelHist.size()-1);
	} else if(driver.equals("arnold")) {
	    trickle = accelHist.get(accelHist.size()-1);
	} else if(driver.equals("illah")) {
	    trickle = -7390.0;
	    knee = -330.0;
	    target = 0.98;
	} else if(driver.equals("mike")) {
	    trickle = accelHist.get(accelHist.size()-1);
	} else if(driver.equals("nikolay")) {
	    trickle = accelHist.get(accelHist.size()-1);
	} else if(driver.equals("thor")) {
	    trickle = -6700.0;
	}
	double capTgt;
	// Lower the target charge level at very low acceleration...
	if(knee <= demand && demand <= 0) {
	    capTgt = Math.max(0, capMin + ((capMax-capMin)*target));  // capMax adjusted for the target charge level
	} else {
	    capTgt = capMax;
	}
	
	double cap2motr = 0.0;
	double batt2cap = 0.0;
	double batt2motr = 0.0;
	if (demand < capMin) {
	    // This is acceleration where the cap can't handle the demand alone
	    cap2motr = capMin;
	    batt2motr = demand - capMin;
	    batt2cap = Math.max(Math.min(0, trickle - batt2motr), -capTgt);
	} else if(capMin <= demand && demand <= capTgt) {
	    // This is acceleration or regen where the cap can handle the demand on its own
	    cap2motr = demand;
	    batt2motr = 0;
	    if(demand < 0) {
		// accel
		batt2cap = Math.max(-capTgt, trickle);		
	    } else {
		// regen
		batt2cap = Math.max(Math.min(-capTgt+demand, 0), trickle);
	    }
	} else {
	    // This is regen where the cap cannot take all of the demand
	    cap2motr = capTgt;
	    batt2motr = demand - capTgt;
	    batt2cap = 0;
	}

	try {
	    modelCap.drawCurrent(cap2motr - batt2cap, pf);
	    modelBatt.drawCurrent(batt2motr + batt2cap, pf);
	} catch (PowerFlowException e) {
	}
		
	return new PowerFlows(batt2motr, cap2motr, batt2cap);
    }
    
    public void endTrip() {
	modelCap = null;
	modelBatt = null;
    }
    
    public void loadState() {
    }
    
    public String getName() {
	return name;
    }
}
