/*
 * Copyright 2009 Dukascopy (Suisse) SA. All rights reserved.
 * DUKASCOPY PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package newjforex;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.GeneralPath;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import com.dukascopy.api.Filter;
import com.dukascopy.api.IBar;
import com.dukascopy.api.Period;
import com.dukascopy.api.Unit;
import com.dukascopy.api.indicators.BooleanOptInputDescription;
import com.dukascopy.api.indicators.IDrawingIndicator;
import com.dukascopy.api.indicators.IIndicator;
import com.dukascopy.api.indicators.IIndicatorContext;
import com.dukascopy.api.indicators.IIndicatorDrawingSupport;
import com.dukascopy.api.indicators.IndicatorInfo;
import com.dukascopy.api.indicators.IndicatorResult;
import com.dukascopy.api.indicators.InputParameterInfo;
import com.dukascopy.api.indicators.IntegerListDescription;
import com.dukascopy.api.indicators.OptInputParameterInfo;
import com.dukascopy.api.indicators.OutputParameterInfo;
import com.dukascopy.api.indicators.OutputParameterInfo.DrawingStyle;
import com.dukascopy.api.indicators.OutputParameterInfo.Type;

public class MyIndicator implements IIndicator, IDrawingIndicator {
	private IndicatorInfo indicatorInfo;
	private InputParameterInfo[] inputParameterInfos;
	private OutputParameterInfo[] outputParameterInfos;
	private OptInputParameterInfo[] optInputParameterInfos;

	private IBar[][] inputs = new IBar[2][];

	private double[][] outputs = new double[8][];
	private InputParameterInfo hourlyInput;
	private DecimalFormat decimalFormat;

	private final GeneralPath generalPath = new GeneralPath();
	private List<Point> tmpHandlesPoints = new ArrayList<Point>();

	private boolean showHistoricalLevels = false;

	private IIndicatorContext context = null;

	public void onStart(IIndicatorContext context) {
		this.context = context;

		indicatorInfo = new IndicatorInfo("TPIVOT", "MyPivot", "My indicators",
				true, false, true, 2, 2, 3);
		indicatorInfo.setSparceIndicator(true);
		indicatorInfo.setRecalculateAll(true);

		hourlyInput = new InputParameterInfo("Input data",
				InputParameterInfo.Type.BAR);
		hourlyInput.setPeriod(Period.ONE_HOUR);
		hourlyInput.setFilter(Filter.WEEKENDS);
		inputParameterInfos = new InputParameterInfo[] {
				new InputParameterInfo("Main Input data",
						InputParameterInfo.Type.BAR), hourlyInput };
		
		int[] periodValues = new int[10];
		String[] periodNames = new String[10];
		periodValues[0] = 0;
		periodNames[0] = "1 Min";
		periodValues[1] = 1;
		periodNames[1] = "5 Mins";
		periodValues[2] = 2;
		periodNames[2] = "10 Mins";
		periodValues[3] = 3;
		periodNames[3] = "15 Mins";
		periodValues[4] = 4;
		periodNames[4] = "30 Mins";
		periodValues[5] = 5;
		periodNames[5] = "Hourly";
		periodValues[6] = 6;
		periodNames[6] = "4 Hours";
		periodValues[7] = 7;
		periodNames[7] = "Daily";
		periodValues[8] = 8;
		periodNames[8] = "Weekly";
		periodValues[9] = 9;
		periodNames[9] = "Monthly";

		optInputParameterInfos = new OptInputParameterInfo[] {
				new OptInputParameterInfo(
						"Period",
						OptInputParameterInfo.Type.OTHER,
						new IntegerListDescription(5, periodValues, periodNames)),
				new OptInputParameterInfo("Show historical levels",
						OptInputParameterInfo.Type.OTHER,
						new BooleanOptInputDescription(showHistoricalLevels)) };

		outputParameterInfos = new OutputParameterInfo[] {
				createOutputParameterInfo("Central Point (P)",
						OutputParameterInfo.Type.DOUBLE,
						OutputParameterInfo.DrawingStyle.LINE),
				createOutputParameterInfo("Resistance (PR1)",
						OutputParameterInfo.Type.DOUBLE,
						OutputParameterInfo.DrawingStyle.LINE),
				createOutputParameterInfo("Support (PS1)",
						OutputParameterInfo.Type.DOUBLE,
						OutputParameterInfo.DrawingStyle.LINE), };

		decimalFormat = new DecimalFormat("0.00000");
	}

	private OutputParameterInfo createOutputParameterInfo(String name,
			Type type, DrawingStyle drawingStyle) {
		return new OutputParameterInfo(name, type, drawingStyle, false) {
			{
				setDrawnByIndicator(true);
			}
		};
	}

	public IndicatorResult calculate(int startIndex, int endIndex) {
		if (startIndex > endIndex) {
			return new IndicatorResult(0, 0);
		}

		int i, j;

		IBar previousBar = null;
		int previousTimeIndex = -1;

		for (i = startIndex, j = 0; i <= endIndex; i++, j++) {
			// Inputs: 0 open, 1 close, 2 high, 3 low, 4 volume
			int index = j;

			/*
			 * Find the same time in inputs[i] as from inputs[0] by index
			 */
			int timeIndex = getTimeIndex(inputs[0][index].getTime(), inputs[1]);

			if (previousBar != null && timeIndex > -1
					&& timeIndex != previousTimeIndex) {
				// P
				double P = ((previousBar.getHigh() + previousBar.getLow()) / 2);
				double PR1 = P + 0.01;
				double PS1 = P - 0.01;

				outputs[0][index] = P;
				// R1
				outputs[1][index] = PR1;
				// S1
				outputs[2][index] = PS1;

			} else {
				outputs[0][index] = Double.NaN;
				outputs[1][index] = Double.NaN;
				outputs[2][index] = Double.NaN;

			}

			if (timeIndex > -1 && timeIndex != previousTimeIndex) {
				previousBar = inputs[1][timeIndex];
				previousTimeIndex = timeIndex;
			}

		}

		return new IndicatorResult(startIndex, i);
	}

	private int getTimeIndex(long time, IBar[] target) {
		if (target == null) {
			return -1;
		}

		int first = 0;
		int upto = target.length;

		while (first < upto) {
			int mid = (first + upto) / 2;

			IBar data = target[mid];

			if (data.getTime() == time) {
				return mid;
			} else if (time < data.getTime()) {
				upto = mid;
			} else if (time > data.getTime()) {
				first = mid + 1;
			}
		}

		if (context != null && Unit.Week.equals(context.getPeriod().getUnit())) {
			/*
			 * Special case for weeks, because week candles has different start
			 * times than sometimes don't match month candles start times
			 */
			for (int i = 1; i < target.length; i++) {
				IBar previousBar = target[i - 1];
				IBar trg = target[i];

				if (time == previousBar.getTime()) {
					return i - 1;
				} else if (time == trg.getTime()) {
					return i;
				} else if (previousBar.getTime() < time
						&& time < trg.getTime()
						&& Math.abs(trg.getTime() - time) < context.getPeriod()
								.getInterval()) {
					return i;
				}

				if (previousBar.getTime() > time) {
					break;
				}
			}
		}

		return -1;
	}

	public IndicatorInfo getIndicatorInfo() {
		return indicatorInfo;
	}

	public InputParameterInfo getInputParameterInfo(int index) {
		if (index <= inputParameterInfos.length) {
			return inputParameterInfos[index];
		}
		return null;
	}

	public int getLookback() {
		return 0;
	}

	public int getLookforward() {
		return 0;
	}

	public OptInputParameterInfo getOptInputParameterInfo(int index) {
		if (index <= optInputParameterInfos.length) {
			return optInputParameterInfos[index];
		}
		return null;
	}

	public OutputParameterInfo getOutputParameterInfo(int index) {
		if (index <= outputParameterInfos.length) {
			return outputParameterInfos[index];
		}
		return null;
	}

	public void setInputParameter(int index, Object array) {
		inputs[index] = (IBar[]) array;
	}

	public void setOptInputParameter(int index, Object value) {
		if (index == 0) {
			int period = ((Integer) value).intValue();
			switch (period) {
			case 0:
				hourlyInput.setPeriod(Period.ONE_MIN);
				break;
			case 1:
				hourlyInput.setPeriod(Period.FIVE_MINS);
				break;
			case 2:
				hourlyInput.setPeriod(Period.TEN_MINS);
				break;
			case 3:
				hourlyInput.setPeriod(Period.FIFTEEN_MINS);
				break;
			case 4:
				hourlyInput.setPeriod(Period.THIRTY_MINS);
				break;
			case 5:
				hourlyInput.setPeriod(Period.ONE_HOUR);
				break;
			case 6:
				hourlyInput.setPeriod(Period.FOUR_HOURS);
				break;
			case 7:
				hourlyInput.setPeriod(Period.DAILY_SUNDAY_IN_MONDAY);
				break;
			case 8:
				hourlyInput.setPeriod(Period.WEEKLY);
				break;
			case 9:
				hourlyInput.setPeriod(Period.MONTHLY);
				break;
			default:
				hourlyInput.setPeriod(Period.DAILY_SUNDAY_IN_MONDAY);
			}
		} else if (index == 1) {
			showHistoricalLevels = Boolean.valueOf(String.valueOf(value))
					.booleanValue();
		}
	}

	public void setOutputParameter(int index, Object array) {
		outputs[index] = (double[]) array;
	}

	@Override
	public Point drawOutput(Graphics g, int outputIdx, Object values,
			Color color, Stroke stroke,
			IIndicatorDrawingSupport indicatorDrawingSupport,
			List<Shape> shapes, Map<Color, List<Point>> handles) {
		tmpHandlesPoints.clear();

		if (values != null) {
			Graphics2D g2 = (Graphics2D) g;
			generalPath.reset();

			double[] output = (double[]) values;

			g2.setColor(color);
			g2.setStroke(stroke);

			int spaceBetweenTwoSeparators = calculateMinSpaceBetweenTwoSeparators(
					output, indicatorDrawingSupport);
			int fontSize = calculateFontSize(spaceBetweenTwoSeparators,
					(int) indicatorDrawingSupport.getCandleWidthInPixels());
			boolean drawValues = canDrawValues(fontSize);

			if (outputIdx == 7) {
				if (drawValues) {
					drawSeparators(output, indicatorDrawingSupport,
							generalPath, spaceBetweenTwoSeparators);
				}
			} else {
				drawPivotLevels(g2, outputIdx, output, indicatorDrawingSupport,
						generalPath, fontSize, drawValues,
						spaceBetweenTwoSeparators);
			}

			g2.draw(generalPath);

			shapes.add((Shape) generalPath.clone()); // cloning path, so when
														// checking for
														// intersection each
														// indicator has its own
														// path
			handles.put(color, new ArrayList<Point>(tmpHandlesPoints));
		}

		return null;
	}

	private void drawPivotLevels(Graphics2D g2, int outputIdx, double[] output,
			IIndicatorDrawingSupport indicatorDrawingSupport,
			GeneralPath generalPath, int fontSize, boolean drawValues,
			int spaceBetweenTwoSeparators) {
		g2.setFont(new Font(g2.getFont().getName(), g2.getFont().getStyle(),
				fontSize));

		int maxX = indicatorDrawingSupport.getChartWidth()
				+ spaceBetweenTwoSeparators; // JFOREX-2432
		int minX = -spaceBetweenTwoSeparators;

		Integer previousX = null;

		for (int i = output.length - 1; i >= 0; i--) {
			double d = output[i];

			if (Double.isNaN(d)) {
				continue;
			}

			int x = (int) indicatorDrawingSupport.getMiddleOfCandle(i);
			int y = (int) indicatorDrawingSupport.getYForValue(d);

			if (previousX == null) {
				previousX = new Integer(x + spaceBetweenTwoSeparators);
			}

			if ((minX <= previousX.intValue() && previousX.intValue() <= maxX)
					|| (minX <= x && x <= maxX)) {
				generalPath.moveTo(previousX.intValue(), y);
				generalPath.lineTo(x, y);

				if (drawValues) {
					String valueStr = decimalFormat.format(d);
					String lineCode = getLineCodeText(outputIdx);
					String result = lineCode + ": " + valueStr;

					int lineCodeX = x + 1;
					g2.drawString(result, lineCodeX, y - 2);
				}

				if (!showHistoricalLevels) {
					break;
				}
			} else if (x > maxX || previousX.intValue() > maxX) {
				/*
				 * Means that the last actual period is out of screen, so don't
				 * draw anything in case if showHistoricalLevels == false
				 */
				if (!showHistoricalLevels) {
					break;
				}
			}

			previousX = Integer.valueOf(x);
		}
	}

	private int calculateMinSpaceBetweenTwoSeparators(double[] output,
			IIndicatorDrawingSupport indicatorDrawingSupport) {
		int space1 = calculateMinSpaceBetweenTwoSeparators(output,
				output.length - 1, indicatorDrawingSupport);
		int space2 = calculateMinSpaceBetweenTwoSeparators(output,
				output.length / 2, indicatorDrawingSupport);
		int space3 = calculateMinSpaceBetweenTwoSeparators(output,
				output.length / 3, indicatorDrawingSupport);

		return Math.max(space3, Math.max(space1, space2));
	}

	private int calculateMinSpaceBetweenTwoSeparators(double[] output,
			int startIndex, IIndicatorDrawingSupport indicatorDrawingSupport) {
		Integer x1 = null;
		Integer x2 = null;
		Double previousValue = null;

		for (int i = startIndex; i > -1; i--) {
			double d = output[i];
			if (!Double.isNaN(d) && previousValue != null
					&& previousValue.doubleValue() != d) {
				x1 = new Integer(
						(int) indicatorDrawingSupport.getMiddleOfCandle(i));

				if (x2 != null) {
					int dif = x2.intValue() - x1.intValue();
					return dif;
				}
				x2 = new Integer(x1.intValue());
			}
			previousValue = new Double(d);
		}

		return -1;
	}

	private boolean canDrawValues(int fontSize) {
		final int MIN_FONT_SIZE = 4;

		if (fontSize <= MIN_FONT_SIZE) {
			return false;
		}
		return true;
	}

	private int calculateFontSize(int spaceBetweenTwoSeparators,
			int candleWidthInPixels) {

		final int MAX_FONT_SIZE = 12;
		final int DIVISION_COEF = 7;

		spaceBetweenTwoSeparators /= DIVISION_COEF;
		spaceBetweenTwoSeparators = spaceBetweenTwoSeparators < 0 ? candleWidthInPixels
				: spaceBetweenTwoSeparators;

		return spaceBetweenTwoSeparators > MAX_FONT_SIZE ? MAX_FONT_SIZE
				: spaceBetweenTwoSeparators;
	}

	private void drawSeparators(double[] output,
			IIndicatorDrawingSupport indicatorDrawingSupport,
			GeneralPath generalPath, int spaceBetweenTwoSeparators) {
		int maxWidth = indicatorDrawingSupport.getChartWidth()
				+ spaceBetweenTwoSeparators;
		int maxHeight = indicatorDrawingSupport.getChartHeight();

		Integer firstDrawnX = null;

		for (int i = output.length - 1; i >= 0; i--) {
			double d = output[i];

			if (!Double.isNaN(d)) {
				int x = (int) indicatorDrawingSupport.getMiddleOfCandle(i);

				if (firstDrawnX == null) {
					firstDrawnX = new Integer(x);
				}

				drawSeparator(generalPath, x, maxWidth, maxHeight);

				if (!showHistoricalLevels) {
					/*
					 * Don't draw separators further if user don't want them
					 */
					break;
				}
			}
		}

		drawSeparator(generalPath,
				(firstDrawnX == null ? 0 : firstDrawnX.intValue())
						+ spaceBetweenTwoSeparators, maxWidth, maxHeight);
	}

	private void drawSeparator(GeneralPath generalPath, int x, int maxWidth,
			int maxHeight) {
		if (0 <= x && x <= maxWidth) {
			generalPath.moveTo(x, 0);
			generalPath.lineTo(x, maxHeight);

			tmpHandlesPoints.add(new Point(x, 5));
			tmpHandlesPoints.add(new Point(x, maxHeight / 2));
			tmpHandlesPoints.add(new Point(x, maxHeight - 5));
		}
	}

	private String getLineCodeText(int outputIdx) {
		String lineCode = "";
		switch (outputIdx) {
		case 0:
			lineCode = "P";
			break;
		case 1:
			lineCode = "PR1";
			break;
		case 2:
			lineCode = "PS1";
			break;
		default:
			throw new IllegalArgumentException("Illegal outputIdx - "
					+ outputIdx);
		}
		return lineCode;
	}

}
