package jforex.indicators.test;

import java.text.DecimalFormat;

import com.dukascopy.api.*;
import com.dukascopy.api.IIndicators.MaType;
import com.dukascopy.api.indicators.IIndicator;

@RequiresFullAccess
public class InputsManagerStrategy2 implements IStrategy {

	@Configurable("Instrument")
	public Instrument instrument = Instrument.EURUSD;
	@Configurable("Offer Side")
	public OfferSide offerSide = OfferSide.BID;
	@Configurable("MA Type")
	public MaType maType = MaType.SMA;
	@Configurable("MA TimePeriod")
	public int maTimePeriod = 3;
	@Configurable("MA Shift")
	public int maShift = 0;
	@Configurable("Log values")
	public boolean logValues = true;

	private IConsole console;
	private IIndicators indicators;
	private Period period = Period.TEN_SECS;

	//initial price array
	double[][] inputs = { { 1.4000, 1.4001, 1.4002, 1.4003, 1.4004, 1.4005, 1.4006, 1.4007, 1.4008, 1.4009 },
			{ 1.5000, 1.5001, 1.5002, 1.5003, 1.5004, 1.5005, 1.5006, 1.5007, 1.5008, 1.5009 },
			{ 1.6000, 1.6001, 1.6002, 1.6003, 1.6004, 1.6005, 1.6006, 1.6007, 1.6008, 1.6009 },
			{ 1.7000, 1.7001, 1.7002, 1.7003, 1.7004, 1.7005, 1.7006, 1.7007, 1.7008, 1.7009 },
			{ 25.000, 25.001, 25.002, 25.003, 25.004, 25.005, 25.006, 25.007, 25.008, 25.009 } };

	public static DecimalFormat df = new DecimalFormat("0.00000");
	private InputManager inputsManager;

	@Override
	public void onStart(IContext context) throws JFException {

		console = context.getConsole();
		indicators = context.getIndicators();

		//inputs manager will keep an array of size 5 * (10+100)
		inputsManager = new InputManager(inputs, 100);
		
		log("5 close prices with shift 3: " + arrayToString(inputsManager.getPrices(3, 5, InputManager.CLOSE)));
	}

	@Override
	public void onTick(Instrument instrument, ITick tick) throws JFException {
		if (instrument != this.instrument) {
			return;
		}
		inputsManager.updateLatestInput(tick);
		// log last 3 close prices
		log("Last 3 close prices: " + arrayToString(inputsManager.getPrices(0, 3, InputManager.CLOSE)));
	}

	@Override
	public void onBar(Instrument instrument, Period period, IBar askBar, IBar bidBar) throws JFException {
		if(instrument != this.instrument || period != this.period){
			return;
		}
		double price = bidBar.getClose();
		//on new bar all prices are the same - they match the previous close price and volume is 0
		inputsManager.addInput(price, price, price, price, 0);
		
		//log last 3 close prices
		log("last 3 close prices: " + arrayToString(inputsManager.getPrices(0, 3, InputManager.CLOSE)));
		
		double ma = getMa(this.maTimePeriod, this.maShift);
		print("ma: " + ma + " on bar: " + bidBar);
	}

	/**
	 * Calculates MA over an array, see more:
	 * http://www.dukascopy.com/wiki/index.php?title=Strategy_API:_Indicators#Calculate_indicator_on_array
	 * 
	 * @param timePeriod
	 * @param shift
	 * @return
	 */
	private double getMa(int timePeriod, int shift) {
		IIndicator maIndicator = indicators.getIndicator("MA");

		double result = 0;
		int outputCount = shift + timePeriod;

		// set optional inputs
		maIndicator.setOptInputParameter(0, timePeriod);
		maIndicator.setOptInputParameter(1, maType.ordinal()); 

		// set inputs
		// Note that we pass the whole 100+10 array
		double [] priceArr = inputsManager.getAllPrices(InputManager.CLOSE);
		maIndicator.setInputParameter(0, priceArr);

		// set outputs
		double[] resultArr = new double[outputCount];
		maIndicator.setOutputParameter(0, resultArr);
		
		// calculate
		// Note that we specify the range of values that we will calculate on
		maIndicator.calculate(inputsManager.getLastPriceIndex() - outputCount + 1, inputsManager.getLastPriceIndex());
		
		log("ma result array: " + arrayToString(resultArr));
		log("Moving Indicator Lookback = " + maIndicator.getLookback());
		log("resultArr.length = " + resultArr.length);
		int index = resultArr.length - shift - 1 - maIndicator.getLookback();
		log("Retreiving MA from resultArr[" + index + "]");
		result = resultArr[index];

		return result;
	}

	@Override
	public void onMessage(IMessage message) throws JFException {
	}

	@Override
	public void onAccount(IAccount account) throws JFException {
	}

	@Override
	public void onStop() throws JFException {
	}

	private void print(Object o) {
		console.getOut().println(o);
	}
	
	private void log(Object o) {
		if(logValues) print(o);
	}

	public static String arrayToString(double[] arr) {
		String str = "";
		for (int r = 0; r < arr.length; r++) {
			str += "[" + r + "] " + df.format(arr[r]) + "; ";
		}
		return str;
	}
}

class InputManager {

	private double[][] inputs;
	private int curPos;
	private final int buffer;// = 100;

	public static final int PRICE_INPUT_SIZE = 5;

	// Indices in price input array (according to the order in input type PRICE)
	public static final int OPEN = 0;
	public static final int CLOSE = 1;
	public static final int HIGH = 2;
	public static final int LOW = 3;
	public static final int VOLUME = 4;

	public InputManager(double[][] priceInputs, int bufferSize) {

		buffer = bufferSize;
		inputs = new double[PRICE_INPUT_SIZE][priceInputs[0].length + buffer];
		for (int i = 0; i < PRICE_INPUT_SIZE; i++) {
			System.arraycopy(priceInputs[i], 0, inputs[i], 0, priceInputs[i].length);
		}
		curPos = priceInputs[0].length - 1;
	}

	// add input at the end of the array
	public void addInput(double open, double close, double high, double low, double volume) {
		curPos++;

		if (curPos >= inputs[0].length) {
			resetBuffers();
		}
		
		inputs[OPEN][curPos] = open;
		inputs[CLOSE][curPos] = close;
		inputs[HIGH][curPos] = high;
		inputs[LOW][curPos] = low;
		inputs[VOLUME][curPos] = volume;
		
	}

	public void updateLatestInput(ITick tick) {
		inputs[CLOSE][curPos] = tick.getBid();
		inputs[HIGH][curPos] = inputs[HIGH][curPos] < tick.getBid() ? tick.getBid() : inputs[HIGH][curPos];
		inputs[LOW][curPos] = inputs[LOW][curPos] > tick.getBid() ? tick.getBid() : inputs[LOW][curPos];
		inputs[VOLUME][curPos] += tick.getBidVolume();
	}

	private void resetBuffers() {
		double[][] emptyBufferArray = new double[PRICE_INPUT_SIZE][buffer];

		for (int i = 0; i < PRICE_INPUT_SIZE; i++) {
			System.arraycopy(inputs[i], buffer, inputs[i], 0, inputs[i].length - buffer);
			System.arraycopy(emptyBufferArray[i], 0, inputs[i], inputs[i].length - buffer, buffer);
		}

		curPos = inputs[0].length - buffer;
	}

	public double[] getPrices(int shift, int count, int priceIndex) {
		double[] resultArray = new double[count];
		System.arraycopy(inputs[priceIndex], curPos - count - shift + 1, resultArray, 0, count);
		return resultArray;
	}

	public double getPrice(int priceIndex) {
		return getPrice(0, priceIndex);
	}

	public double getPrice(int shift, int priceIndex) {
		return inputs[priceIndex][curPos - shift];
	}

	// for indicator calculations
	public double[] getAllPrices(int priceIndex) {
		return inputs[priceIndex];
	}

	public int getLastPriceIndex() {
		return curPos;
	}

	public int getFirstPriceIndex() {
		return 0;
	}

}
