package singlejartest;

import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.TimeZone;

import com.dukascopy.api.*;
import com.dukascopy.api.IIndicators.MaType;
import com.dukascopy.api.indicators.IIndicator;

/**
 * The strategy shows how to emulate range bar history in JForex-API 2.6.33.
 * Also it shows how to use range bar close prices with MA indicator.
 *
 */

@RequiresFullAccess
public class RangeBarsHistory2Fix4Live implements IStrategy {

	private IConsole console;
	private IHistory history;
	private IIndicators indicators;
	private SimpleDateFormat sdf;

	@Configurable("Instrument")
	public Instrument instrument = Instrument.EURUSD;
	@Configurable("Offer Side")
	public OfferSide offerSide = OfferSide.BID;
	@Configurable("Price range (pips)")
	public int priceRangePips = 2;
	@Configurable("MA TimePeriod")
	public int maTimePeriod = 5;
	@Configurable("MA Shift")
	public int maShift = 0;
	@Configurable("Log values")
	public boolean logValues = true;
	@Configurable("(config) History Chunk Size")
	public Period historyChunkSize = Period.ONE_MIN;

	public static DecimalFormat df = new DecimalFormat("0.00000");
	private PriceRange priceRange;
	
	@Override
	public void onStart(IContext context) throws JFException {

		this.history = context.getHistory();
		this.console = context.getConsole();
		this.indicators = context.getIndicators();
		log("start");
		priceRange = PriceRange.valueOf(priceRangePips);

		sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
		
		// make sure the instrument is subscribed (needed for running in standalone)
		context.setSubscribedInstruments(new HashSet<Instrument>() {{add(instrument);}});
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e1) {
			e1.printStackTrace(console.getErr());
		}
		
		// we build range bars starting from the last tick
		ITick tick = history.getLastTick(instrument);
		long histRangeBarStartTime = tick.getTime();

		// this much history price values we will need for indicator calculation
		int histBarCount = maShift + maTimePeriod;
		List<IRangeBar> rangeBars = null;
		try {
			rangeBars = getHistoryRangeBars(histBarCount, histRangeBarStartTime);
		} catch (JFException e) {
			console.getErr().println(e);
		}

		// log range bars (note that the last one will be the platform range bar with a bit different toString method
		for (IBar rangeBar : rangeBars) {
			log(rangeBar);
		}

		// get close prices
		double[] closePrices = new double[rangeBars.size()];
		for (int i = 0; i < rangeBars.size(); i++) {
			closePrices[i] = rangeBars.get(i).getClose();
		}

		double ma = getMa(maTimePeriod, maShift, closePrices);
		print("MA value with period " + maTimePeriod + " and shift " + maShift + ": " + df.format(ma));

	}

	/**
	 * Returns historical range bars starting from histEndTime.
	 * 
	 * @param count range bar count
	 * @param histEndTime end time of the latest range bar
	 * @return
	 * @throws JFException
	 */
	private List<IRangeBar> getHistoryRangeBars(int count, long histEndTime) throws JFException {

		long chunkInterval = historyChunkSize.getInterval();
		List<IRangeBar> rangeBars = new ArrayList<IRangeBar>();
		MockRangeBar rangeBar = null;
		ITick tick;
		double range = instrument.getPipValue() * priceRange.getPipCount();
		double maxMove;

		while (rangeBars.size() < count) {
			//read ticks in chunks
			List<ITick> ticks = history.getTicks(instrument, histEndTime - chunkInterval, histEndTime);
			for (int i = ticks.size() - 1; i >= 0; i--) {
				tick = ticks.get(i);
				
				double price = offerSide == OfferSide.BID ? tick.getBid() : tick.getAsk();
				double volume = offerSide == OfferSide.BID ? tick.getBidVolume() : tick.getAskVolume();				
				
				maxMove = rangeBar == null 
						? 0 
						: Math.max(Math.max(Math.abs(rangeBar.open - price), Math.abs(rangeBar.high - price)),Math.abs(rangeBar.low - price));
				//last price broke the range - create new range bar 
				if (rangeBar == null || maxMove > range) {
					if (rangeBar != null) {
						//store previous one
						rangeBars.add(rangeBar);
					}
					rangeBar = new MockRangeBar(price, volume, tick.getTime());
					continue;
				}

				if (rangeBar.high < price) {
					rangeBar.high = price;
				}
				if (rangeBar.low > price) {
					rangeBar.low = price;
				}
				rangeBar.close = price;
				rangeBar.vol += volume;
				rangeBar.startTime = tick.getTime();
				rangeBar.tickCount++;

				if (rangeBars.size() >= count) {
					break;
				}
			}
			if (rangeBars.size() >= count) {
				break;
			}
			histEndTime -= chunkInterval;
		}

		//reverse such that the latest range bar is the last one
		Collections.reverse(rangeBars);
		return rangeBars;

	}
	
	/**
	 * 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
	 * @param priceArr
	 * @return
	 */
	private double getMa(int timePeriod, int shift, double [] priceArr ) {
		IIndicator maIndicator = indicators.getIndicator("MA");
		 
		//set optional inputs
		maIndicator.setOptInputParameter(0, timePeriod);
		maIndicator.setOptInputParameter(1, MaType.SMA.ordinal());
		 
		//set inputs
		maIndicator.setInputParameter(0, priceArr);
		 
		//set outputs
		double [] resultArr = new double [priceArr.length];
		maIndicator.setOutputParameter(0, resultArr);
		 
		//calculate
		maIndicator.calculate(0, priceArr.length - 1);
		print("ma result array: " + arrayToString(resultArr));
		
		int index = resultArr.length - shift - 1 - maIndicator.getLookback();
		double result = resultArr[index];
		return result;
	}

	
	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;
	}

	@Override
	public void onTick(Instrument instrument, ITick tick) throws JFException {}

	@Override
	public void onBar(Instrument instrument, Period period, IBar askBar, IBar bidBar) throws JFException {}

	@Override
	public void onMessage(IMessage message) throws JFException {}

	@Override
	public void onAccount(IAccount account) throws JFException {}

	@Override
	public void onStop() throws JFException {}
	
	/**
	 * There is no IRangeBar interface in JForex-API 2.6.33, so we take it from JForex-API 2.6.38
	 *
	 */
	public interface IRangeBar extends IBar {
	    long getEndTime();
	    long getFormedElementsCount();
	}

	/**
	 * Custom implementation of a IRangeBar, which allows us to create range bars ourselves.
	 *
	 */
	public class MockRangeBar implements IRangeBar {

		public double open;
		public double close;
		public double low;
		public double high;
		public double vol;
		public long startTime;
		public long endTime;
		public int tickCount;

		@SuppressWarnings("serial")
		private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS") {
			{
				setTimeZone(TimeZone.getTimeZone("GMT"));
			}
		};
		private DecimalFormat df = new DecimalFormat("0.00000");

		public MockRangeBar() {

		}

		public MockRangeBar(double price, double volume, long time) {
				open = close = low = high = price;
				vol = volume;
				startTime = time;
				endTime = time;
				tickCount = 1;
		}

		public MockRangeBar(double open, double close, double low, double high, double vol, long startTime, long endTime, int tickCount) {
			this.open = open;
			this.close = close;
			this.low = low;
			this.high = high;
			this.vol = vol;
			this.startTime = startTime;
			this.endTime = endTime;
			this.tickCount = tickCount;
		}

		@Override
		public double getOpen() {
			return open;
		}

		@Override
		public double getClose() {
			return close;
		}

		@Override
		public double getLow() {
			return low;
		}

		@Override
		public double getHigh() {
			return high;
		}

		@Override
		public double getVolume() {
			return vol;
		}

		@Override
		public long getTime() {
			return startTime;
		}

		@Override
		public String toString() {

			StringBuilder str = new StringBuilder();
			str.append("StartTime: ").append(sdf.format(startTime)).append(" EndTime: ").append(sdf.format(endTime)).append(" O: ")
					.append(df.format(open)).append(" C: ").append(df.format(close)).append(" H: ").append(df.format(high)).append(" L: ")
					.append(df.format(low)).append(" V: ").append(df.format(vol)).append(" TickCount: ").append(tickCount);
			return str.toString();
		}

		@Override    
		public long getEndTime() {
			return endTime;
		}

		@Override
		public long getFormedElementsCount() {
			return tickCount;
		}

	}

}
