﻿/*
 * 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 (R1)",
                        OutputParameterInfo.Type.DOUBLE,
                        OutputParameterInfo.DrawingStyle.LINE),
                createOutputParameterInfo("Support (S1)",
                        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 = "R1";
            break;
        case 2:
            lineCode = "S1";
            break;
        default:
            throw new IllegalArgumentException("Illegal outputIdx - "
                    + outputIdx);
        }
        return lineCode;
    }
	
}
