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.AppliedPrice;
import com.dukascopy.api.Instrument;
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 CCIMACDDI implements IIndicator {
    
    public static final int OPEN = 0;
    public static final int CLOSE = 1;
    public static final int MIN = 2;
    public static final int MAX = 3;
    public static final int AVE = 4;
        
    private IndicatorInfo           indicatorInfo;

    // INPUTS
    private InputParameterInfo[]    inputParameterInfos;
    private double[][]              inPrice;

    // OPT INPUTS
    private OptInputParameterInfo[] optInputParameterInfos;    
    private int                     appliedPrice;
    private int                     fastPeriod;
    private int                     slowPeriod;
    private int                     signalPeriod;
    private int                     CCIper;

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


    // CONTEXT
    private IConsole                console;

    // private Instrument instrument;
    private Instrument              instrument = Instrument.EURUSD; // workaround
    
    private int lookback2 = 1;

    private IIndicator              macd;
    private IIndicator              cci;
    private IIndicator              dxp;
    private IIndicator              dxn;
    
    public int getLookback() {
        macd.setOptInputParameter(0, fastPeriod);
        macd.setOptInputParameter(1, slowPeriod);
        macd.setOptInputParameter(2, signalPeriod);
        cci.setOptInputParameter(0,CCIper);
        dxp.setOptInputParameter(0, 14);
        dxn.setOptInputParameter(0, 14);
        return Math.max(dxp.getLookback() + lookback2, Math.max(cci.getLookback() + lookback2, macd.getLookback() + lookback2));
    }

    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;
        
        macd.setInputParameter(0, inPrice[appliedPrice]);
        cci.setInputParameter(0, inPrice);
        dxp.setInputParameter(0, inPrice);
        dxn.setInputParameter(0, inPrice);
                
        double[][] macdOut = new double[3][len + lookback2];
        macd.setOutputParameter(0, macdOut[0]);
        macd.setOutputParameter(1, macdOut[1]);
        macd.setOutputParameter(2, macdOut[2]);
        double[] cciOut = new double[len + lookback2];
        cci.setOutputParameter(0, cciOut);
        double[] dxip = new double[len + lookback2];
        dxp.setOutputParameter(0, dxip);
        double[] dxin = new double[len + lookback2];
        dxn.setOutputParameter(0, dxin);
        
        macd.calculate(startIndex - lookback2, endIndex);
        cci.calculate(startIndex - lookback2, endIndex);        
        dxp.calculate(startIndex - lookback2, endIndex);
        dxn.calculate(startIndex - lookback2, endIndex);
        
        double[] cciTemp = new double[len + lookback2];
        double[] macdTemp = new double[len + lookback2];
        double[] dxiTemp = new double[len + lookback2];
        for (int i = lookback2, outIndex = 0; outIndex < len; outIndex++, i++) {

            if(cciOut[i] > 100) cciTemp[outIndex] = 1;
            else if(cciOut[i] < -100) cciTemp[outIndex] = -1;
                 else if (outIndex!=0) cciTemp[outIndex] = cciTemp[outIndex-1];
                      //else  cciTemp[outIndex] = 0;
            
            if(macdOut[2][i] > 0) macdTemp[outIndex] = 1;
            else if(macdOut[2][i] < 0) macdTemp[outIndex] = -1;
                 else macdTemp[outIndex] = 0;
            
            if(dxip[i]>dxin[i]) dxiTemp[outIndex] = 1;
            else if(dxip[i]<dxin[i]) dxiTemp[outIndex] = -1;
                 else dxiTemp[outIndex] = 0;            
            
            if(cciTemp[outIndex]==1 && macdTemp[outIndex]==1 && dxiTemp[outIndex]==1) {
                output[0][outIndex] = 0.8;
                output[1][outIndex] = 0.0;
            } 
            else if(cciTemp[outIndex]==-1 && macdTemp[outIndex]==-1 && dxiTemp[outIndex]==-1) {
                    output[0][outIndex] = 0.0;
                    output[1][outIndex] = 0.8;
                 }
                 else if (outIndex!=0)  {
                         output[0][outIndex] = output[0][outIndex-1];
                         output[1][outIndex] = output[1][outIndex-1];
                      }
                      //else  {output[0][outIndex] = 0.0; output[1][outIndex] = 0.0;}
        }
        
        return new IndicatorResult(startIndex, len);
    }

    public void onStart(IIndicatorContext context) {
        
        this.console = context.getConsole();
        
        int[] priceValues = {OPEN, CLOSE, MIN, MAX, AVE};
        String[] priceNames = {"Open", "Close", "High", "Low", "Average"};

        inputParameterInfos = new InputParameterInfo[] {

                new InputParameterInfo("price", InputParameterInfo.Type.PRICE)

        };

        optInputParameterInfos = new OptInputParameterInfo[] {
                new OptInputParameterInfo("Applied price", 
                                          OptInputParameterInfo.Type.OTHER, 
                                          new IntegerListDescription(AppliedPrice.CLOSE.ordinal(), priceValues, priceNames)),
                new OptInputParameterInfo("Fast period",
                                          OptInputParameterInfo.Type.OTHER,
                                          new IntegerRangeDescription(12, 2, 100, 1)),
                new OptInputParameterInfo("Slow period",
                                          OptInputParameterInfo.Type.OTHER,
                                          new IntegerRangeDescription(26, 2, 1000, 1)),
               new OptInputParameterInfo("Signal period",
                                         OptInputParameterInfo.Type.OTHER,
                                         new IntegerRangeDescription(9, 2, 1000, 1)),
               new OptInputParameterInfo("CCI period",
                                         OptInputParameterInfo.Type.OTHER,
                                         new IntegerRangeDescription(14, 2, 1000, 1)),
        };

        outputParameterInfos = new OutputParameterInfo[] {
                new OutputParameterInfo("Histogram Up",
                                        OutputParameterInfo.Type.DOUBLE,
                                        OutputParameterInfo.DrawingStyle.HISTOGRAM) {
                    {
                        this.setColor(Color.BLUE);
                    }
                },
                new OutputParameterInfo("Histogram Down",
                                        OutputParameterInfo.Type.DOUBLE,
                                        OutputParameterInfo.DrawingStyle.HISTOGRAM) {
                    {
                        this.setColor(Color.RED);
                    }
                }
               };

        output = new double[outputParameterInfos.length][];
        inPrice = new double[inputParameterInfos.length][];
        
        macd = context.getIndicatorsProvider().getIndicator("MACD");
        cci = context.getIndicatorsProvider().getIndicator("CCI");
        dxp = context.getIndicatorsProvider().getIndicator("PLUS_DI");
        dxn = context.getIndicatorsProvider().getIndicator("MINUS_DI");
                
        indicatorInfo = new IndicatorInfo("CCIMACDDI", "CCIMACDDI colored", "My indicators", false, false, false, inputParameterInfos.length, optInputParameterInfos.length, outputParameterInfos.length);
    }

    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) {
        if (array instanceof double[][]) {
            inPrice = (double[][]) array;
        }
    }

    public void setOptInputParameter(int index, Object value) {
        switch (index) {
            case 0:
                appliedPrice = (Integer) value;
                break;
            case 1:
                fastPeriod = (Integer) value;                
                break;
            case 2:
                slowPeriod = (Integer) value;
                break;
            case 3:
                signalPeriod = (Integer) value;                
                break;
            case 4:
                CCIper = (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);
    }

    // ________________________________

}