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.AppliedPrice;
import com.dukascopy.api.IIndicators.MaType;
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.IntegerListDescription;
import com.dukascopy.api.indicators.IntegerRangeDescription;
import com.dukascopy.api.indicators.OptInputParameterInfo;
import com.dukascopy.api.indicators.OutputParameterInfo;

public class MATwoTimeFrame implements IIndicator {
    
    private IndicatorInfo           indicatorInfo;

    // INPUTS
    private InputParameterInfo[]    inputParameterInfos;
    private IBar[]                  mainBars;
    private IBar[]                  otherPeriodBars;
    
    private InputParameterInfo      inputParameterInfo;
    
    // OPT INPUTS
    private OptInputParameterInfo[] optInputParameterInfos;
    private MaType                  maType;
    private int                     maPeriod;
    private int                     selectedPrice = 1;

    // OUTPUTS
    private OutputParameterInfo[]   outputParameterInfos;
    private double[][]              output;
    private int                     HIGH       = 0;
    private int                     LOW        = 1;

    // CONTEXT
    private IConsole                console;
    private IIndicator              ma;

    public int getLookback() {
        return maPeriod - 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;
        
        ma.setOptInputParameter(0, maPeriod);
        ma.setOptInputParameter(1, maType.ordinal());
        
        if(otherPeriodBars.length - ma.getLookback() < 0) {
            return new IndicatorResult(startIndex, len);
        }
        double[] maOutput = new double[otherPeriodBars.length - ma.getLookback()];
        
        ma.setInputParameter(0, getMAInputs(otherPeriodBars));
        ma.setOutputParameter(0, maOutput);
        
        ma.calculate(0, otherPeriodBars.length - 1);
        
        int lb = ma.getLookback();
        int other_idx = otherPeriodBars.length - 1;
        int main_idx = mainBars.length - 1;
        
        OUTER:
        while(other_idx >= lb) {
            while(main_idx >= lb && otherPeriodBars[other_idx].getTime() <= mainBars[main_idx].getTime()) {
                output[0][main_idx - lb] = maOutput[other_idx - lb];
                main_idx--;
            }
            other_idx--;
        }
        
        return new IndicatorResult(startIndex, len);
    }
    
    public double[] getMAInputs(IBar[] bars) {
        double[] maInputs = new double[bars.length];
        
        for(int i = 0; i < bars.length; i++) {
            switch(selectedPrice) {
                case 0: 
                    maInputs[i] = bars[i].getOpen();
                    break;
                case 1: 
                    maInputs[i] = bars[i].getClose();
                    break;
                case 2: 
                    maInputs[i] = bars[i].getHigh();
                    break;
                case 3: 
                    maInputs[i] = bars[i].getLow();
                    break;
                case 4: 
                    maInputs[i] = (bars[i].getHigh() + bars[i].getLow())/2;
                    break;
                case 5: 
                    maInputs[i] = (bars[i].getHigh() + bars[i].getLow() + bars[i].getClose())/3;
                    break;
                case 6: 
                    maInputs[i] = (bars[i].getOpen() + bars[i].getHigh() + bars[i].getLow() + bars[i].getClose())/4;
                    break;
                case 7: 
                    maInputs[i] = (bars[i].getOpen() + bars[i].getClose())/2;
                    break;
                case 8: 
                    maInputs[i] = (bars[i].getOpen() + bars[i].getHigh() + bars[i].getLow())/3;
                    break;
            }
        }
        
        return maInputs;
    }
    
    
    /***********
     *  START  *
     ***********/
    
    public void onStart(IIndicatorContext context) {
        
        this.console = context.getConsole();
        
        int[] priceValues = {0, 1, 2, 3, 4, 5, 6, 7, 8};
        String[] priceNames = {"Open", "Close", "High", "Low", "HL/2", "HLC/3", "OHLC/4", "OC/2", "OHL/3"};

        int[] maTypeValues = new int[MaType.values().length];
        String[] maTypeNames = new String[MaType.values().length];
        for (int i = 0; i < maTypeValues.length; i++) {
            maTypeValues[i] = i;
            maTypeNames[i] = MaType.values()[i].name();
        }
        
        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";
        
        inputParameterInfo = new InputParameterInfo("Other period bars", InputParameterInfo.Type.BAR);
        inputParameterInfos = new InputParameterInfo[] {
                new InputParameterInfo("Main", InputParameterInfo.Type.BAR),
                inputParameterInfo,
        };
        optInputParameterInfos = new OptInputParameterInfo[] {
                new OptInputParameterInfo("Price",
                                          OptInputParameterInfo.Type.OTHER, 
                                          new IntegerListDescription(selectedPrice, priceValues, priceNames)),
                new OptInputParameterInfo("MA Type", 
                                          OptInputParameterInfo.Type.OTHER, 
                                          new IntegerListDescription(AppliedPrice.CLOSE.ordinal(), maTypeValues, maTypeNames)),                
               new OptInputParameterInfo("MA time period",
                                         OptInputParameterInfo.Type.OTHER,
                                         new IntegerRangeDescription(10, 1, 2000, 1)),
                new OptInputParameterInfo("Other period",
                                          OptInputParameterInfo.Type.OTHER, 
                                          new IntegerListDescription(2, periodValues, periodNames)),
        };
        outputParameterInfos = new OutputParameterInfo[] {
                new OutputParameterInfo("Main",
                                        OutputParameterInfo.Type.DOUBLE,
                                        OutputParameterInfo.DrawingStyle.LINE) {
                    {
                        this.setColor(Color.BLUE);
                    }
                },
               };

        output = new double[outputParameterInfos.length][];
        ma = context.getIndicatorsProvider().getIndicator("MA");
        
        indicatorInfo = new IndicatorInfo("MA_OTHER_PERIOD", "MA with other time period", "My indicators", true, 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 (index == 0) {
            mainBars = (IBar []) array;
        } else if (index == 1) {
            otherPeriodBars = (IBar []) array;
        }
    }

    public void setOptInputParameter(int index, Object value) {
        
        switch (index) {
            case 0:
                selectedPrice = (Integer) value;
                break;
            case 1:
                maType = MaType.values()[(Integer) value];
                break;
            case 2:
                maPeriod = (Integer) value;
                break;
            case 3:
                int period = ((Integer) value).intValue();
                switch (period) {
                    case 0:
                        inputParameterInfo.setPeriod(Period.ONE_MIN);
                        break;
                    case 1:
                        inputParameterInfo.setPeriod(Period.FIVE_MINS);
                        break;
                    case 2:
                        inputParameterInfo.setPeriod(Period.TEN_MINS);
                        break;
                    case 3:
                        inputParameterInfo.setPeriod(Period.FIFTEEN_MINS);
                        break;
                    case 4:
                        inputParameterInfo.setPeriod(Period.THIRTY_MINS);
                        break;
                    case 5:
                        inputParameterInfo.setPeriod(Period.ONE_HOUR);
                        break;
                    case 6:
                        inputParameterInfo.setPeriod(Period.FOUR_HOURS);
                        break;
                    case 7:
                        inputParameterInfo.setPeriod(Period.DAILY_SUNDAY_IN_MONDAY);
                        break;
                    case 8:
                        inputParameterInfo.setPeriod(Period.WEEKLY);
                        break;
                    case 9:
                        inputParameterInfo.setPeriod(Period.MONTHLY);
                        break;
                    default:
                        inputParameterInfo.setPeriod(Period.DAILY_SUNDAY_IN_MONDAY);
                }
                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 + "  ");
            if (ob instanceof double[]) {
                print((double[]) ob);
            } else if (ob instanceof double[]) {
                print((double[][]) ob);
            } else if (ob instanceof Long) {
                print2(toStr((Long) ob));
            } else if (ob instanceof IBar) {
                print((IBar) ob);
            } else {
                print2(ob);
            }
            print2(" ");
        }
        console.getOut().println();
    }

    private void print(Object o) {
        console.getOut().println(o);
    }

    private void print2(Object o) {
        console.getOut().print(o);
    }

    private void print(double[] arr) {
        print(toStr(arr));
    }

    private void print(double[][] arr) {
        print(toStr(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 toStr(double[] arr) {
        String str = "";
        for (int r = 0; r < arr.length; r++) {
            str += "[" + r + "] " + (new DecimalFormat("#.#######")).format(arr[r]) + "; ";
        }
        return str;
    }

    public static String toStr(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 toStr(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);
    }

    // ________________________________

}