package jforex.indicators;

import java.awt.Color;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.TimeZone;

import com.dukascopy.api.IBar;
import com.dukascopy.api.IConsole;
import com.dukascopy.api.IIndicators.MaType;
import com.dukascopy.api.Instrument;
import com.dukascopy.api.indicators.DoubleRangeDescription;
import com.dukascopy.api.indicators.IIndicator;
import com.dukascopy.api.indicators.IIndicatorContext;
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.IntegerRangeDescription;
import com.dukascopy.api.indicators.OptInputParameterInfo;
import com.dukascopy.api.indicators.OutputParameterInfo;

public class TwoInstrumentBBands implements IIndicator {
    private IndicatorInfo           indicatorInfo;

    // INPUTS
    private double[][][]            inPrice;

    private int                     CHART_IND  = 0;
    private int                     SECOND_IND = 1;

    private int                     OPEN       = 0;
    private int                     CLOSE      = 1;
    private int                     HIGH       = 2;
    private int                     LOW        = 3;

    // OPT INPUTS
    private OptInputParameterInfo[] optInputParameterInfos;
    private int                     appliedPrice;
    private int                     bBandsPeriod;
    private double                  deviation;
    private Instrument              secondInstrument = Instrument.GBPUSD;

    // OUTPUTS
    private OutputParameterInfo[]   outputParameterInfos;
    private double[][]              output;

    // CONTEXT
    private IConsole                console;

    private IIndicator              bbands;

    public int getLookback() {
        return bBandsPeriod - 1;
    }

    public int getLookforward() {
        return 0;
    }

    /**************
     * CALCULATE
     **************/
    public IndicatorResult calculate(int startIndex, int endIndex) {

        if (startIndex - getLookback() < 0) {
            startIndex -= startIndex - getLookback();
        }
        if (startIndex > endIndex) {
            return new IndicatorResult(0, 0);
        }
        int len = endIndex - startIndex + 1;
        
        
        int inLength = inPrice[CHART_IND][appliedPrice].length;
        double[] bbInput = new double[inLength];
        
        for(int i = 0; i < inLength; i++) {
            bbInput[i] = inPrice[CHART_IND][appliedPrice][i] / inPrice[SECOND_IND][appliedPrice][i];
        }
        bbands.setInputParameter(0, bbInput);

        bbands.setOptInputParameter(0, bBandsPeriod);
        bbands.setOptInputParameter(1, deviation);
        bbands.setOptInputParameter(2, deviation);
        bbands.setOptInputParameter(3, MaType.EMA.ordinal());

        bbands.setOutputParameter(0, output[0]);
        bbands.setOutputParameter(1, new double[output[0].length]);
        bbands.setOutputParameter(2, output[2]);

        bbands.calculate(startIndex, endIndex);
        
        for(int i = 0; i < output[0].length; i++) {
            output[1][i] = (output[0][i] + output[2][i])/2;
            output[3][i] = bbInput[i + getLookback()];
        }

        return new IndicatorResult(startIndex, len);
    }

    public void onStart(IIndicatorContext context) {
        this.console = context.getConsole();

        int[] priceValues = {OPEN, CLOSE, HIGH, LOW};
        String[] priceNames = {"OPEN", "CLOSE", "HIGH", "LOW"};

        int[] instrumentValues = new int[Instrument.values().length];
        String[] instrumentName = new String[Instrument.values().length];
        for (int i = 0; i < instrumentValues.length; i++) {
            instrumentValues[i] = i;
            instrumentName[i] = Instrument.values()[i].name();
        }


        inPrice = new double[2][][];

        optInputParameterInfos = new OptInputParameterInfo[] {
                new OptInputParameterInfo("Bbands applied price",
                                          OptInputParameterInfo.Type.OTHER,
                                          new IntegerListDescription(CLOSE, priceValues, priceNames)),

                new OptInputParameterInfo("Bbands period",
                                          OptInputParameterInfo.Type.OTHER,
                                          new IntegerRangeDescription(10, 2, 1000, 1)),

                new OptInputParameterInfo("Bbands deviation",
                                          OptInputParameterInfo.Type.OTHER,
                                          new DoubleRangeDescription(1.0, 0.0, 100.0, 0.01, 2)),

                new OptInputParameterInfo("Second indicator",
                                          OptInputParameterInfo.Type.OTHER,
                                          new IntegerListDescription(Instrument.EURGBP.ordinal(), instrumentValues, instrumentName)),
        };

        outputParameterInfos = new OutputParameterInfo[] {
                new OutputParameterInfo("Upper",
                                        OutputParameterInfo.Type.DOUBLE,
                                        OutputParameterInfo.DrawingStyle.LINE) {
                    {
                        this.setColor(Color.BLUE);
                    }
                },
                new OutputParameterInfo("Mid",
                                        OutputParameterInfo.Type.DOUBLE,
                                        OutputParameterInfo.DrawingStyle.LINE) {
                    {
                        this.setColor(Color.GRAY);
                    }
                },
                new OutputParameterInfo("Lower",
                                        OutputParameterInfo.Type.DOUBLE,
                                        OutputParameterInfo.DrawingStyle.LINE) {
                    {
                        this.setColor(Color.RED);
                    }
                },
                new OutputParameterInfo("Ratio",
                                        OutputParameterInfo.Type.DOUBLE,
                                        OutputParameterInfo.DrawingStyle.LINE) {
                    {
                        this.setColor(new Color(0,153,0));
                    }
                }
               };
        output = new double[outputParameterInfos.length][];

        indicatorInfo = new IndicatorInfo("2IND_BBANDS", "BBands from two indicators", "My indicators", false, false, false, 2, optInputParameterInfos.length, outputParameterInfos.length);

        bbands = context.getIndicatorsProvider().getIndicator("BBANDS");
    }

    public IndicatorInfo getIndicatorInfo() {
        return indicatorInfo;
    }

    public InputParameterInfo getInputParameterInfo(int index) {
        if(index == 0) {
            return new InputParameterInfo("chart instrument prices", InputParameterInfo.Type.PRICE);
        } else if(index == 1) {
            return new InputParameterInfo("second instrument prices", InputParameterInfo.Type.PRICE) {
                {
                    setInstrument(secondInstrument);
                }
            };
        }
        return null;
    }

    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) {
        inPrice[index] = (double[][]) array;
    }

    public void setOptInputParameter(int index, Object value) {
        switch (index) {
            case 0:
                appliedPrice = (Integer) value;                
                break;
            case 1:
                bBandsPeriod = (Integer) value;                
                break;
            case 2:
                deviation = (Double) value;
                break;
            case 3:
                secondInstrument = Instrument.values()[(Integer) value];
                break;
        }
    }

    public void setOutputParameter(int index, Object array) {
        output[index] = (double[]) array;
    }

    // ________________________________
    // Support: Some helper methods for printing

    private void print(Object... o) {
        for (Object ob : o) {
            console.getOut().print(ob + "  ");
        }
        console.getOut().println();
    }

    private void print(Object o) {
        console.getOut().println(o);
    }

    private void print(double[] arr) {
        print(arrayToString(arr));
    }

    private void print(double[][] arr) {
        print(arrayToString(arr));
    }

    private void printIndicatorInfos(IIndicator ind) {
        for (int i = 0; i < ind.getIndicatorInfo().getNumberOfInputs(); i++) {
            print(ind.getIndicatorInfo().getName() + " Input " + ind.getInputParameterInfo(i).getName() + " " + ind.getInputParameterInfo(i).getType());
        }
        for (int i = 0; i < ind.getIndicatorInfo().getNumberOfOptionalInputs(); i++) {
            print(ind.getIndicatorInfo().getName() + " Opt Input " + ind.getOptInputParameterInfo(i).getName() + " " + ind.getOptInputParameterInfo(i).getType());
        }
        for (int i = 0; i < ind.getIndicatorInfo().getNumberOfOutputs(); i++) {
            print(ind.getIndicatorInfo().getName() + " Output " + ind.getOutputParameterInfo(i).getName() + " " + ind.getOutputParameterInfo(i).getType());
        }
    }

    public static String arrayToString(double[] arr) {
        String str = "";
        for (int r = 0; r < arr.length; r++) {
            str += "[" + r + "] " + (new DecimalFormat("#.#######")).format(arr[r]) + "; ";
        }
        return str;
    }

    public static String arrayToString(double[][] arr) {
        String str = "";
        if (arr == null)
            return "null";
        for (int r = 0; r < arr.length; r++) {
            for (int c = 0; c < arr[r].length; c++) {
                str += "[" + r + "][" + c + "] " + (new DecimalFormat("#.#######")).format(arr[r][c]);
            }
            str += "; ";
        }
        return str;
    }

    public String toDecimalToStr(double d) {
        return (new DecimalFormat("#.#######")).format(d);
    }

    public String dateToStr(long time) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") {
            {
                setTimeZone(TimeZone.getTimeZone("GMT"));
            }
        };
        return sdf.format(time);
    }

    // ________________________________

}