/*
 * Copyright (c) 2005-2010 KOM � Multimedia Communications Lab
 *
 * This file is part of PeerfactSim.KOM.
 * 
 * PeerfactSim.KOM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 * 
 * PeerfactSim.KOM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with PeerfactSim.KOM.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package de.tud.kom.p2psim.impl.energy.components;


import java.util.Comparator;
import java.util.LinkedList;

import de.tud.kom.p2psim.api.energy.ComponentType;
import de.tud.kom.p2psim.api.energy.EnergyEventListener;
import de.tud.kom.p2psim.api.energy.EnergyState;
import de.tud.kom.p2psim.impl.energy.DefaultEnergyState;
import de.tudarmstadt.maki.simonstrator.api.Time;


/**
 * Component for devices that provide thrust, such as electrical motors for UAV propulsion. 
 * Is configured by {@link MotorCharacteristic}s with a given thrust (N) and a given current (A). 
 * Values in between given characteristics are calculated by linear interpolation.
 * 
 * @author Julian Zobel
 * @version 1.0, 05.03.2019
 */
public class StatelessActuatorComponent implements ActuatorComponent {

	public enum componentState {OFF, ON}
	
	private componentState state;
	
	private EnergyEventListener energyModel;
	
	private long lastEnergyConsumationTime;	
	
	private double volts;
	private final double uJconversionFactor = 1000000;
	private final int numberOfActuators;
	
	private double thrust;
	private double amps;
	private EnergyState energyState;
	private LinkedList<MotorCharacteristic> characteristics = new LinkedList<>();
	
		
	public StatelessActuatorComponent(int numberOfActuators, double volt) {			
		this.volts = volt;		
		this.numberOfActuators = numberOfActuators;				
		this.state = componentState.OFF;
		this.lastEnergyConsumationTime = Time.getCurrentTime();
		
		thrust = 0;
		amps = 0;
		energyState = new DefaultEnergyState("OFF", 0);
	}
	
	/**
	 * Get the maximum thrust provided by this component.
	 * @return
	 */
	public double getMaxThrust() {
		return characteristics.getLast().getThrust() * numberOfActuators;
	}
	
	/**
	 * Set the new energy state and calculate the energy consumption from the last state
	 */
	private void setEnergyState() {	
		
		// set the new energy state
		EnergyState newState;
		
		if(state == componentState.ON) {
			newState = new DefaultEnergyState("Actuator", numberOfActuators * (amps * volts) * uJconversionFactor);
		}
		else {
			newState = new DefaultEnergyState("OFF", 0);
		}
				
		// calculate energy consumption for the previous state
		long timeSpentInState = Time.getCurrentTime() - lastEnergyConsumationTime;
		
		double cons =  calculateEnergyConsumation(energyState, timeSpentInState);
		
		energyModel.componentConsumedEnergy(this, cons);			
		
		// set new state
		energyState = newState;		
		lastEnergyConsumationTime = Time.getCurrentTime();
	}
	
	/**	
	 * Request a given amount of thrust to be provided from this component. If the amount is less than the minimum 
	 * or more than the maximum, the minimum or maximum thrust values, respectively, are enforced. 
	 *  
	 * @param targetThrust
	 * @return The amount of thrust this component now generates.
	 */
	public double requestThrust(double targetThrust) {
		
		if(targetThrust == 0 || targetThrust <= numberOfActuators * characteristics.getFirst().getThrust()) {
			setLoad(characteristics.getFirst());
		}
		else if(targetThrust >= numberOfActuators * characteristics.getLast().getThrust()) {
			setLoad(characteristics.getLast());
		}
		else {
			calculateAndSetThrustRelatedAmpereDraw(targetThrust);
		}
				
		return this.thrust;
	}
	
	/**
	 * 
	 * @param targetThrust
	 * @return The power consumption for the target thrust in Watt
	 */
	public double estimatePowerConsumptionWatt(double targetThrust) {
		if(targetThrust == 0 || targetThrust <= numberOfActuators * characteristics.getFirst().getThrust()) {
			// not allowed
			return Double.NaN;
		}
		else if(targetThrust > numberOfActuators * characteristics.getLast().getThrust()) {
			// not allowed
			return Double.NaN;
		}
		else {			
			double amps = approximateAmpereDraw(targetThrust);
			
			//System.out.println(amps);
			
			return numberOfActuators * amps * volts;
		}
	}
	
	
	/**
	 * Given an amount of thrust between the minimum and maximum values, the required current 
	 * to provide this amount of thrust is calculated by linear interpolation by the nearest lower
	 * and upper {@link MotorCharacteristic}s. 
	 * 
	 * @param targetThrust
	 * @return the approximated ampere draw
	 */
	private double approximateAmpereDraw(double targetThrust) {
		
		MotorCharacteristic lower = null, upper = null;
		
		// find the lower and upper bounding characteristics
		for (MotorCharacteristic ch : characteristics) {
			//
			if(ch.getThrust() * numberOfActuators == targetThrust) {
				return ch.getCurrent();
			}
			else {
				// list is sorted, lower bound is the biggest that is lower
				if(ch.getThrust() * numberOfActuators < targetThrust) {
					lower = ch;
				}
				// the first that is greater is used as upper bound
				else if(ch.getThrust() * numberOfActuators > targetThrust) {
					upper = ch;
					break;
				}
			}
		}
		
		if(upper == null || lower == null) {
			throw new UnsupportedOperationException("Lower or upper bounds cannot be null");
		}
		
		if(upper.getThrust() * numberOfActuators < targetThrust || lower.getThrust() * numberOfActuators > targetThrust) {
			throw new UnsupportedOperationException("Lower or upper bound do not match");
		}
		
		/*
		 * Calculate the approximated current with the upper and lower bounds:
		 * Amp_approx = Amp_lower + (T_target - T_lower)/(T_upper - T_lower) * (Amp_upper - Amp_lower)
		 */		
		double delta = (targetThrust - (lower.getThrust() * numberOfActuators))/(numberOfActuators * (upper.getThrust() - lower.getThrust()));		
		return lower.getCurrent() + delta * (upper.getCurrent() - lower.getCurrent());
	}
	
	/**
	 * Approximates the ampere draw required forthe requested thrust
	 * 
	 * Target thrust should be strictly within the possible thrust limits
	 * 
	 */
	private void calculateAndSetThrustRelatedAmpereDraw(double targetThrust) {				
		double calculatedAmps = approximateAmpereDraw(targetThrust);			
		setLoad(targetThrust, calculatedAmps);
	}
	
	private void setLoad(double thrust, double amps) {
		this.thrust = thrust;
		this.amps = amps;
		setEnergyState();
	}
	
	private void setLoad(MotorCharacteristic ch) {
		this.thrust = ch.getThrust();
		this.amps = ch.getCurrent();
		setEnergyState();
	}
	
	/**
	 * Add a {@link MotorCharacteristic} for this motor. 
	 * 
	 * @param c
	 */
	public void addChar(MotorCharacteristic c) {
		
		characteristics.add(c);
	
		// sort the characteristics starting from low to high thrust
		characteristics.sort(new Comparator<MotorCharacteristic>() {
			@Override
			public int compare(MotorCharacteristic o1,
					MotorCharacteristic o2) {
				return (int) (o1.getThrust() - o2.getThrust());
			}
		});
		
	}
	
		
	@Override
	public void eventOccurred(Object content, int type) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public ComponentType getType() {	
		return ComponentType.ACTUATOR;
	}

	@Override
	public boolean turnOff() {
		this.thrust = 0;
		this.amps = 0;
		this.state = componentState.OFF;
		setEnergyState();
		
		return true;
	}

	@Override
	public boolean turnOn() {
		if (isAvailable()) {
			if(this.state != componentState.ON) {
				this.state = componentState.ON;
				requestThrust(0);
			}
			return true;
		}
		return false;
	}
	
	public boolean isAvailable() {
		if (energyModel.componentCanBeActivated(this)) {	
			return true;
		}
		return false;
	}

	@Override
	public boolean isOn() {
		if(this.state != componentState.OFF && isAvailable()) {			
			return true;			
		}
		return false;
	}
	
	@Override
	public void setEnergyEventListener(EnergyEventListener listener) {
		energyModel = listener;		
	}

	public EnergyState getCurrentState() {
		return energyState;
	}
	
	
	
}
