package jforex.indicators;

import java.awt.Color;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.TimeZone;

import com.dukascopy.api.IConsole;
import com.dukascopy.api.IIndicators;
import com.dukascopy.api.Instrument;
import com.dukascopy.api.OfferSide;
import com.dukascopy.api.Period;
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.IntegerRangeDescription;
import com.dukascopy.api.indicators.OptInputParameterInfo;
import com.dukascopy.api.indicators.OutputParameterInfo;

public class RealWoodieCCIIndicator implements IIndicator {
    
    // change this line according to the instrument that is being used
    private Instrument              instrument  = Instrument.EURUSD;
    
    
    private IndicatorInfo           indicatorInfo;

    // INPUTS
    private InputParameterInfo[]    inputParameterInfos;
    private double[][]              inPrice;
    private int                     OPEN        = 0;
    private int                     CLOSE       = 1;
    private int                     HIGH        = 2;
    private int                     LOW         = 3;

    // OPT INPUTS
    private OptInputParameterInfo[] optInputParameterInfos;

    // OUTPUTS
    private OutputParameterInfo[]   outputParameterInfos;
    private double[][]              output;

    // histogram
    private int                     TREND_UP   = 0;
    private int                     TREND_DOWN = 1;
    private int                     NO_TREND   = 2;
    private int                     TIME_BAR   = 3;

    //
    private int                     TREND_CCI  = 4;
    private int                     ENTRY_CCI  = 5;

    private int                     LSMA_1     = 6;
    private int                     LSMA_2     = 7;

    private int                     P100       = 8;
    private int                     N100       = 9;

    private int                     P200       = 10;
    private int                     N200       = 11;

    // CONTEXT
    private Period                  period;
    private IConsole                console;
    private OfferSide               offerSide;

    private IIndicator              cci;
    private IIndicator              ema;
    private IIndicator              typprice;

    // params

    private int                     cciTrendPeriod;
    private int                     cciTrendLookback;
    private int                     cciEntryPeriod;
    private int                     cciEntryLookback;
    private int                     trendPeriod;

    private int                     lsmaPeriod;

    private int                     trendUp     = 0;
    private int                     trendDown   = 0;

    public void onStart(IIndicatorContext context) {
        this.console = context.getConsole();
        this.period = context.getPeriod();
        this.offerSide = context.getOfferSide();

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

        inputParameterInfos = new InputParameterInfo[] {

        new InputParameterInfo("prices", InputParameterInfo.Type.PRICE) {
            {
                setOfferSide(offerSide);
                setInstrument(instrument);
                setPeriod(period);
            }
        },
        };

        optInputParameterInfos = new OptInputParameterInfo[] {
                new OptInputParameterInfo("CCI trend period",
                                          OptInputParameterInfo.Type.OTHER,
                                          new IntegerRangeDescription(14, 2, 100, 1)),
                new OptInputParameterInfo("CCI entry period",
                                          OptInputParameterInfo.Type.OTHER,
                                          new IntegerRangeDescription(14, 2, 100, 1)),
                new OptInputParameterInfo("Trend period",
                                          OptInputParameterInfo.Type.OTHER,
                                          new IntegerRangeDescription(5, 2, 100, 1)),
                new OptInputParameterInfo("LSMA period",
                                          OptInputParameterInfo.Type.OTHER,
                                          new IntegerRangeDescription(25, 2, 100, 1)),
        };

        outputParameterInfos = new OutputParameterInfo[] {

                new OutputParameterInfo("Trend up", OutputParameterInfo.Type.DOUBLE,
                        OutputParameterInfo.DrawingStyle.HISTOGRAM) {
                    {
                        this.setColor(Color.BLUE);
                    }
                },
                new OutputParameterInfo("Trend down", OutputParameterInfo.Type.DOUBLE,
                        OutputParameterInfo.DrawingStyle.HISTOGRAM) {
                    {
                        this.setColor(Color.RED);
                    }
                },
                new OutputParameterInfo("No trend", OutputParameterInfo.Type.DOUBLE,
                        OutputParameterInfo.DrawingStyle.HISTOGRAM) {
                    {
                        this.setColor(Color.GRAY);
                    }
                },
                new OutputParameterInfo("Time bar", OutputParameterInfo.Type.DOUBLE,
                        OutputParameterInfo.DrawingStyle.HISTOGRAM) {
                    {
                        this.setColor(Color.YELLOW);
                    }
                },

                new OutputParameterInfo("Trendn CCI", OutputParameterInfo.Type.DOUBLE,
                        OutputParameterInfo.DrawingStyle.LINE) {
                    {
                        this.setColor(Color.RED);
                    }
                }, new
                OutputParameterInfo("Entry CCI", OutputParameterInfo.Type.DOUBLE,
                        OutputParameterInfo.DrawingStyle.LINE) {
                    {
                        this.setColor(Color.PINK);
                    }
                },

                new OutputParameterInfo("Lsma 1", OutputParameterInfo.Type.DOUBLE,
                        OutputParameterInfo.DrawingStyle.DOTS) {
                    {
                        this.setColor(Color.GREEN);
                    }
                }, new
                 OutputParameterInfo("Lsma 2", OutputParameterInfo.Type.DOUBLE,
                         OutputParameterInfo.DrawingStyle.DOTS) {
                     {
                         this.setColor(Color.RED);
                     }
                 },
                 

                 new OutputParameterInfo("+100", OutputParameterInfo.Type.DOUBLE,
                         OutputParameterInfo.DrawingStyle.DASH_LINE) {
                     {
                         this.setColor(Color.GRAY);
                     }
                 }, new
                  OutputParameterInfo("-100", OutputParameterInfo.Type.DOUBLE,
                          OutputParameterInfo.DrawingStyle.DASH_LINE) {
                      {
                          this.setColor(Color.GRAY);
                      }
                  },
                  

                  new OutputParameterInfo("+200", OutputParameterInfo.Type.DOUBLE,
                          OutputParameterInfo.DrawingStyle.DASH_LINE) {
                      {
                          this.setColor(Color.GRAY);
                      }
                  }, new
                   OutputParameterInfo("-200", OutputParameterInfo.Type.DOUBLE,
                           OutputParameterInfo.DrawingStyle.DASH_LINE) {
                       {
                           this.setColor(Color.GRAY);
                       }
                   },

        };

        output = new double[outputParameterInfos.length][];
        cci = context.getIndicatorsProvider().getIndicator("CCI");

        indicatorInfo = new IndicatorInfo("REAL_WOODIE_CCI", "Real Woodie CCI filter", "My indicators", false, false, false, inputParameterInfos.length, optInputParameterInfos.length, outputParameterInfos.length);
    }

    private int cciShift = 1; // just to get rid of magic numbers

    public int getLookback() {
        return Math.max(lsmaPeriod, Math.max(cciTrendLookback + cciShift, cciEntryLookback + cciShift));
    }

    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 cciStartIndex = startIndex - cciShift;
        int cciLen = len + 1;

        // ENTRY CCI
        cci.setInputParameter(0, inPrice);
        cci.setOptInputParameter(0, cciEntryPeriod);
        double[] cciEntry = new double[cciLen];
        cci.setOutputParameter(0, cciEntry);
        cci.calculate(cciStartIndex, endIndex);
        System.arraycopy(cciEntry, cciShift, output[ENTRY_CCI], 0, len);

        // TREND CCI
        cci.setInputParameter(0, inPrice);
        cci.setOptInputParameter(0, cciTrendPeriod);
        double[] cciTrend = new double[cciLen];
        cci.setOutputParameter(0, cciTrend);
        cci.calculate(cciStartIndex, endIndex);
        System.arraycopy(cciTrend, cciShift, output[TREND_CCI], 0, len);

        for (int i = cciShift, outIndex = 0; i < cciLen; i++, outIndex++) {

            output[TREND_UP][outIndex] = 0;
            output[TREND_DOWN][outIndex] = 0;
            output[NO_TREND][outIndex] = 0;
            output[TIME_BAR][outIndex] = 0;

            if (cciTrend[i] > 0 && cciTrend[i - cciShift] < 0) {
                if (trendDown > trendPeriod)
                    trendUp = 0;
            }
            if (cciTrend[i] > 0) {
                if (trendUp < trendPeriod) {
                    output[NO_TREND][outIndex] = cciTrend[i];
                    trendUp++;
                }
                if (trendUp == trendPeriod) {
                    output[TIME_BAR][outIndex] = cciTrend[i];
                    trendUp++;
                }
                if (trendUp > trendPeriod) {
                    output[TREND_UP][outIndex] = cciTrend[i];
                }
            }
            if (cciTrend[i] < 0 && cciTrend[i - cciShift] > 0) {
                if (trendUp > trendPeriod)
                    trendDown = 0;
            }
            if (cciTrend[i] < 0) {

                if (trendDown < trendPeriod) {
                    output[NO_TREND][outIndex] = cciTrend[i];
                    trendDown++;
                }
                if (trendDown == trendPeriod) {
                    output[TIME_BAR][outIndex] = cciTrend[i];
                    trendDown++;
                }
                if (trendDown > trendPeriod) {
                    output[TREND_DOWN][outIndex] = cciTrend[i];
                }
            }
            
            output[P100][outIndex] = 100;
            output[N100][outIndex] = -100;
            output[P200][outIndex] = 200;
            output[N200][outIndex] = -200;
        }

        // ---- Color Middle Line LSMA
        double lengthvar, wt;

        int length = lsmaPeriod;

        for (int shift = startIndex, outIndex = 0; outIndex < len; shift++, outIndex++) {
            double sum1 = 0;
            int start = shift - (length - 1);

            lengthvar = length + 1;
            lengthvar /= 3;

            for (int i = 0; i < length; i++) {

                double tmp = 0;
                double price = inPrice[CLOSE][start + i];

                tmp = ((i + 1 - lengthvar) * price);
                sum1 += tmp;
            }

            wt = sum1 * 6 / (length * (length + 1));

            output[LSMA_1][outIndex] = 0.001;
            output[LSMA_2][outIndex] = -0.001;

            if (wt > inPrice[CLOSE][shift]) {
                output[LSMA_1][outIndex] = Double.NaN;
            }
            if (wt < inPrice[CLOSE][shift]) {
                output[LSMA_2][outIndex] = Double.NaN;
            }
        }

        return new IndicatorResult(startIndex, len);
    }

    public IndicatorInfo getIndicatorInfo() {
        return indicatorInfo;
    }

    public InputParameterInfo getInputParameterInfo(int index) {
        if (index <= inputParameterInfos.length) {
            return inputParameterInfos[index];
        }
        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 = (double[][]) array;
    }

    public void setOptInputParameter(int index, Object value) {
        switch (index) {
            case 0:
                cciTrendPeriod = (Integer) value;
                cciTrendLookback = cciTrendPeriod - 1;
                break;
            case 1:
                cciEntryPeriod = (Integer) value;
                cciEntryLookback = cciEntryPeriod - 1;
                break;
            case 2:
                trendPeriod = (Integer) value;
                break;
            case 3:
                lsmaPeriod = (Integer) value;
                break;
            /*
             * case 4: lsmaPeriod = (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);
    }

    // ________________________________

}

