package SFindicators;

import com.dukascopy.api.IBar;
import com.dukascopy.api.IIndicators;
import com.dukascopy.api.indicators.*;
import java.awt.Color;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;

/**
 * The <b>Starfish Moving Average Angle Indicator</b> displays the angle of the
 * MA(EMA, SMA...) indicator.
 * 
 * If we let Δ<i>x</i> and Δ<i>y</i> be the distances (along the x and y axes,
 * respectively) between two points on a curve, then the slope is given by the
 * below definition
 * <p>
 * <i>m</i> = Δ<i>y</i>/Δ<i>x</i>
 * <p>
 * 
 * <i>m</i> is the slope of a secant line to the curve. For a line, the secant
 * between any two points is the line itself. By moving the two points closer
 * together so that Δ<i>y</i> and Δ<i>x</i> decrease, the secant line more
 * closely approximates a tangent line to the curve, and as such the slope of
 * the secant approaches that of the tangent.
 * <p>
 * <b>MA Type:</b> This is the Moving Average Type that are available
 * <p>
 * <b>MA Time Period:</b> The time period used for the MA indicator
 * <p>
 * <b>startMAShift:</b> The starting point to calculate the angle. This is a
 * shift value to the left from the observation point. (startMAShift >
 * endMAShift)
 * <p>
 * <b>startMAShift:</b> The starting point to calculate the angle. This is a
 * shift value to the left from the observation point. (startMAShift >
 * endMAShift)
 * <p>
 * <b>endMAShift:</b> The ending point to calculate the angle. This is a shift
 * value to the left from the observation point. (startMAShift > endMAShift)
 * <p>
 * <b>ATR Time Period:</b>The time period used for the ATR indicator. The ATR
 * value is used to get Δ<i>y</i>. Here a short extract of the code. That should
 * explain the use of the ATR.
 * <p>
 * double y = (maOutput[j - endMAShift] - maOutput[j - startMAShift]) /
 * atrOutput[j];
 * <p>
 * http://en.wikipedia.org/wiki/Slope
 * <p>
 * http://en.wikipedia.org/wiki/Derivative
 * <p>
 * http://en.wikipedia.org/wiki/Tangent </ul>
 * <p>
 * 
 * @author Lawrence Reid (lawrence_reid@bluewin.ch)
 * @version 1.0.0 Status Released for testing
 *          <p>
 *          History:
 *          <ul>
 *          <li>1.0.0 - 15.12.2012 created strategy
 *          </ul>
 * 
 * 
 * */
public class SF_MA_Angle_V1_0_0 implements IIndicator {
    @SuppressWarnings("unused")
    private static final int OPEN = 0;
    @SuppressWarnings("unused")
    private static final int CLOSE = 1;
    @SuppressWarnings("unused")
    private static final int HIGH = 2;
    @SuppressWarnings("unused")
    private static final int LOW = 3;
    @SuppressWarnings("unused")
    private static final int VOLUME = 4;
    // if DEBUG is true, it displays lots of horrible messages :-)
    private static final boolean DEBUG = false;

    // ----------------- IndicatorInfo ----------------------------------------
    // name of the indicator. i.e SMA, BBANDS
    public static final String NAME = "SF_MA_Angle";
    // title of the indicator; this is Russian and means description
    public static final String TITLE = "this indicator displays the angle of ascent or decent in degrees based on the Moving Average";
    // name of the indicator group
    public static final String GROUPNAME = "Starfish indicators";
    // true if indicator should be drawn over candles/ticks
    public static final boolean OVERCHART = false;
    // true if indicator should be drawn over volume information
    public static final boolean OVERVOLUMES = false;
    // true if indicator has unstable period (like EMA or SAR). This will add
    // more candles in every call to stabilize function
    public static final boolean UNSTABLEPERIOD = false;
    // number of inputs that user should provide
    public static final int NUMBEROFINPUTS = 3;
    // number of optional inputs
    public static final int NUMBEROFOPTIONALINPUTS = 5;
    // number of outputs, that function returns
    public static final int NUMBEROFOUTPUTS = 1;
    // the output array holds the angle in degrees

    private IndicatorInfo indicatorInfo;
    private InputParameterInfo[] inputParameterInfos;
    private OptInputParameterInfo[] optInputParameterInfos;
    private OutputParameterInfo[] outputParameterInfos;
    private IIndicatorContext context;

    // Bars includes on bar(time candle, range, renko...)
    private IBar[][] inputsBarArray = new IBar[1][];
    // Price includes 5 arrays: open, close, high, low, volume
    private double[][][] inputsPriceArray = new double[1][][];
    // price array depending on AppliedPrice
    private double[][] inputsDoubleArray = new double[1][];

    // the number of outputs to be displayed up, down, ma
    private double[][] outputs = new double[NUMBEROFOUTPUTS][];
    private IIndicator atrIndicator;
    private int atrPeriod = 10;
    private IIndicator maIndicator;
    private int maPeriod = 10;
    private int startMAShift = 3;
    private int endMAShift = 0;

    @Override
    public void onStart(IIndicatorContext context) {
        this.context = context;
        context.getConsole().getOut()
                .println("Indicator [" + NAME + "] started. (" + TITLE + ")");
        indicatorInfo = new IndicatorInfo(NAME, TITLE, GROUPNAME, OVERCHART,
                OVERVOLUMES, UNSTABLEPERIOD, NUMBEROFINPUTS,
                NUMBEROFOPTIONALINPUTS, NUMBEROFOUTPUTS);

        inputParameterInfos = new InputParameterInfo[] {
                new InputParameterInfo("Price bar", InputParameterInfo.Type.BAR),
                new InputParameterInfo("Price OCHLV",
                        InputParameterInfo.Type.PRICE),
                new InputParameterInfo("Price in double",
                        InputParameterInfo.Type.DOUBLE) {
                    {
                        setAppliedPrice(IIndicators.AppliedPrice.CLOSE);

                    }
                } };
        int[] maValues = new int[IIndicators.MaType.values().length];
        String[] maNames = new String[IIndicators.MaType.values().length];
        for (int i = 0; i < maValues.length; i++) {
            maValues[i] = i;
            maNames[i] = IIndicators.MaType.values()[i].name();
        }

        optInputParameterInfos = new OptInputParameterInfo[] {
                new OptInputParameterInfo("MA Type",
                        OptInputParameterInfo.Type.OTHER,
                        new IntegerListDescription(
                                IIndicators.MaType.EMA.ordinal(), maValues,
                                maNames)),
                new OptInputParameterInfo("MA Time Period",
                        OptInputParameterInfo.Type.OTHER,
                        new IntegerRangeDescription(maPeriod, 2, 2000, 1)),
                new OptInputParameterInfo(
                        "MA Shift x1(left most starting point)",
                        OptInputParameterInfo.Type.OTHER,
                        new IntegerRangeDescription(startMAShift, 1, 2000, 1)),
                new OptInputParameterInfo("MA Shift x2(right most end point)",
                        OptInputParameterInfo.Type.OTHER,
                        new IntegerRangeDescription(endMAShift, 0, 2000, 1)),
                new OptInputParameterInfo("ATR Time Period",
                        OptInputParameterInfo.Type.OTHER,
                        new IntegerRangeDescription(atrPeriod, 2, 2000, 1))

        };

        // Check number of optional number of inputs

        outputParameterInfos = new OutputParameterInfo[] { new OutputParameterInfo(
                "Angle", OutputParameterInfo.Type.DOUBLE,
                OutputParameterInfo.DrawingStyle.HISTOGRAM) {
            {
                setHistogramTwoColor(true);
                setColor(Color.GREEN);
                setColor2(Color.RED);
            }

        } };

        IIndicatorsProvider indicatorsProvider = context
                .getIndicatorsProvider();
        atrIndicator = indicatorsProvider.getIndicator("ATR");
        maIndicator = indicatorsProvider.getIndicator("MA");

    }

    @Override
    public IndicatorResult calculate(int startIndex, int endIndex) {
        if (startIndex - getLookback() < 0) {
            startIndex -= startIndex - getLookback();
        }

        // calculating ATR
        double[] atrOutput = new double[endIndex - startIndex + 1];
        IndicatorResult atrResult;

        if (inputsPriceArray[0][0].length - atrIndicator.getLookback() > 0) {

            atrIndicator.setInputParameter(0, inputsPriceArray[0]);
            atrIndicator.setOutputParameter(0, atrOutput);
            atrResult = atrIndicator.calculate(startIndex, endIndex);
            print("ATR calculate result: " + toString(atrResult));
        } else {
            context.getConsole()
                    .getOut()
                    .println(
                            "Error: Loopback[" + atrIndicator.getLookback()
                                    + "] of ATR is larger than input array "
                                    + inputsPriceArray[0][0].length);
            return new IndicatorResult(0, 0);
        }
        // calculating MA
        double[] maOutput = new double[endIndex - startIndex + 1];
        IndicatorResult maResult;

        if (inputsDoubleArray[0].length - maIndicator.getLookback() > 0) {
            // maOutput = new double[inputLength - maIndicator.getLookback()];
            maIndicator.setInputParameter(0, inputsDoubleArray[0]);
            maIndicator.setOutputParameter(0, maOutput);

            maResult = maIndicator.calculate(startIndex, endIndex);
            print("MA calculate result: " + toString(maResult));
        } else {
            context.getConsole()
                    .getOut()
                    .println(
                            "Error: Loopback[" + maIndicator.getLookback()
                                    + "] of MA is larger than input array "
                                    + inputsPriceArray[0].length);
            return new IndicatorResult(0, 0);
        }

        int i, j;

        // here we add [startMAShift] to startIndex and j=startMAShift if we
        // want to look into the past of MA
        for (i = startIndex + startMAShift, j = 0 + startMAShift; i <= endIndex; i++, j++) {
            // double value = 0;
            // for (int k = timePeriod; k > 0; k--) {
            // value += inputsDoubleArray[0][i - k];
            // }
            // value /= timePeriod;

            DecimalFormat dfPrint2 = new DecimalFormat("#,##0.##");
            @SuppressWarnings("unused")
            DecimalFormat dfPrint3 = new DecimalFormat("#,##0.###");
            DecimalFormat dfPrint4 = new DecimalFormat("#,##0.####");
            @SuppressWarnings("unused")
            DecimalFormat dfPrint5 = new DecimalFormat("#,##0.#####");
            DecimalFormat dfPrint6 = new DecimalFormat("#,##0.######");

            double y = (maOutput[j - endMAShift] - maOutput[j - startMAShift])
                    / atrOutput[j];
            double x = startMAShift - endMAShift;
            double angleInDegrees = Math.toDegrees(Math.atan(y / x));

            outputs[0][j] = round(angleInDegrees, 1);

            if (i == endIndex) {
                print(strDateWithTimeZone(inputsBarArray[0][i].getTime())
                        + " Angle variables y[" + dfPrint4.format(y) + "] x["
                        + dfPrint2.format(x) + "] Angle ["
                        + dfPrint2.format(angleInDegrees) + "] atr ["
                        + dfPrint6.format(atrOutput[j]) + "]  ma ["
                        + dfPrint6.format(maOutput[j]) + "] ma - startMAShift "
                        + dfPrint6.format(maOutput[j - startMAShift]));
            }

        }
        return new IndicatorResult(startIndex, j);
    }

    @Override
    public IndicatorInfo getIndicatorInfo() {
        return indicatorInfo;
    }

    @Override
    public InputParameterInfo getInputParameterInfo(int index) {
        if (index <= inputParameterInfos.length) {
            return inputParameterInfos[index];
        }
        return null;
    }

    @Override
    public int getLookback() {
        return Math.max(this.maPeriod, this.atrPeriod);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.dukascopy.api.indicators.IIndicator#getLookforward()
     */
    @Override
    public int getLookforward() {
        return 0;
    }

    @Override
    public OptInputParameterInfo getOptInputParameterInfo(int index) {
        if (index <= optInputParameterInfos.length) {
            return optInputParameterInfos[index];
        }
        return null;
    }

    @Override
    public OutputParameterInfo getOutputParameterInfo(int index) {
        if (index <= outputParameterInfos.length) {
            return outputParameterInfos[index];
        }
        return null;
    }

    @Override
    public void setInputParameter(int index, Object array) {
        switch (index) {
        case 0:
            inputsBarArray[0] = (IBar[]) array;
            break;
        case 1:
            inputsPriceArray[0] = (double[][]) array;
            break;
        case 2:
            inputsDoubleArray[0] = (double[]) array;
            break;
        default:
            throw new ArrayIndexOutOfBoundsException(
                    "setInputParameter(). Invalid index: " + index);
        }
    }

    @Override
    public void setOptInputParameter(int index, Object value) {
        switch (index) {

        case 0:
            int maType = (Integer) value;
            maIndicator.setOptInputParameter(1,
                    IIndicators.MaType.values()[maType].ordinal());
                 
                    break;
        case 1:
            maPeriod = (Integer) value;
            maIndicator.setOptInputParameter(0, maPeriod);
            break;
        case 2:
            startMAShift = (Integer) value;
            break;
        case 3:
            endMAShift = (Integer) value;
            break;
        case 4:
            atrPeriod = (Integer) value;
            atrIndicator.setOptInputParameter(0, atrPeriod);
            break;

        default:
            throw new ArrayIndexOutOfBoundsException(
                    "setOptInputParameter(). Invalid index: " + index);
        }

        // Validation of parameters
        if (startMAShift <= endMAShift) {
            print("Error: startMAShift<=endMAShift");
            startMAShift = 4;
            endMAShift = 0;
        }

    }

    @Override
    public void setOutputParameter(int index, Object array) {
        outputs[index] = (double[]) array;
    }

    public void print(Object... str) {
        if (DEBUG)
            context.getConsole().getOut().println(multiargToString("", str));
    }

    static DecimalFormat dfPrint = new DecimalFormat("#,##0.##########");

    public String multiargToString(String delim, Object... str) {
        StringBuilder builder = new StringBuilder();
        for (Object object : str) {
            if (object instanceof Number) {
                Number number = (Number) object;
                if (Math.abs(number.doubleValue()) > 1E-6) {
                    builder.append(delim + dfPrint.format(object));
                } else {
                    builder.append(delim + object);
                }
            } else {
                builder.append(delim + object);
            }
        }
        return builder.substring(delim.length());
    }

    /**
     * converts the time from long format to a nice date time string
     * 
     * @param input
     * @return
     */
    private String strDateWithTimeZone(long input) {
        Date date = new Date(input);
        Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
        SimpleDateFormat sdf = new SimpleDateFormat(
                "yyyy/MMM/dd hh:mm:ss.SSS z");
        sdf.setCalendar(cal);
        cal.setTime(date);
        return sdf.format(date);

    }

    /**
     * returns the values of the indicatorResult parameter in a string
     * 
     * @param indicatorResult
     * @return
     */
    private String toString(IndicatorResult indicatorResult) {
        return String.format("first index[%d] lastindex[%d] Elementcount[%d] ",
                indicatorResult.getFirstValueIndex(),
                indicatorResult.getLastValueIndex(),
                indicatorResult.getNumberOfElements());

    }

    /**
     * This method rounds a double to a certain number of decimal places
     * 
     * @param value
     * @param places
     * @return
     */
    public static double round(double value, int places) {
        if (places < 0)
            throw new IllegalArgumentException();

        long factor = (long) Math.pow(10, places);
        value = value * factor;
        long tmp = Math.round(value);
        return (double) tmp / factor;
    }
}