package jforex.requests;
 
import com.dukascopy.api.indicators.*;
import com.dukascopy.api.IConsole;
import com.dukascopy.api.Instrument;
import com.dukascopy.api.OfferSide;
import com.dukascopy.api.IBar;
 
public class CandleType implements IIndicator {
    private IndicatorInfo indicatorInfo;
    private InputParameterInfo[] inputParameterInfos;
    private OptInputParameterInfo[] optInputParameterInfos;
    private OutputParameterInfo[] outputParameterInfos;
    private double[][][] inputs = new double[1][][];
     
    private IBar[] iBidBars;
     
    private int timePeriod = 2;
    private double[][] outputs = new double[5][];
     
    private IConsole console; // Think you use this for console output
     
    private double LONG_BODY_FACTOR;
    private double SMALL_BODY_FACTOR;
    private double LONG_WICK_FACTOR;
    private int BACKWARD;
     
    // private Instrument instrument;
    private Instrument instrument = Instrument.EURUSD; // workaround
    
    public void onStart(IIndicatorContext context) {
         console = context.getConsole(); // Used for console
         
        indicatorInfo = new IndicatorInfo("CandleType", "CandleType", "Trend", true, false, true, 2, 4, 5);
        inputParameterInfos = new InputParameterInfo[]{
                            new InputParameterInfo("Price", InputParameterInfo.Type.PRICE),
                            new InputParameterInfo("Bar", InputParameterInfo.Type.BAR) {
                                    {
                                         setInstrument(instrument);
                                         setOfferSide(OfferSide.BID);
                                    }
                                }
                             };
        
        optInputParameterInfos = new OptInputParameterInfo[] {
            new OptInputParameterInfo("Long Body Factor", OptInputParameterInfo.Type.OTHER, new DoubleRangeDescription(2.0, 1.1, 10.0, 0.1, 1)),
            new OptInputParameterInfo("Small Body Factor", OptInputParameterInfo.Type.OTHER, new DoubleRangeDescription(0.3, 0.1, 0.9, 0.1, 1)),
            new OptInputParameterInfo("Long Wick Factor", OptInputParameterInfo.Type.OTHER, new DoubleRangeDescription(2.0, 1.1, 10.0, 0.1, 1)),
            new OptInputParameterInfo("Backward", OptInputParameterInfo.Type.OTHER, new IntegerRangeDescription(15, 2, 100, 1)),
            };
        outputParameterInfos = new OutputParameterInfo[] {
            new OutputParameterInfo("DOJI", OutputParameterInfo.Type.DOUBLE, OutputParameterInfo.DrawingStyle.ARROW_SYMBOL_UP),//DOJI
            new OutputParameterInfo("SMALL", OutputParameterInfo.Type.DOUBLE, OutputParameterInfo.DrawingStyle.ARROW_SYMBOL_UP),//BODY_SMALL
            new OutputParameterInfo("LONG", OutputParameterInfo.Type.DOUBLE, OutputParameterInfo.DrawingStyle.ARROW_SYMBOL_UP),//BODY_LONG
            new OutputParameterInfo("WULONG", OutputParameterInfo.Type.DOUBLE, OutputParameterInfo.DrawingStyle.ARROW_SYMBOL_UP),//WICK_LONG_UPPER
            new OutputParameterInfo("WLLONG", OutputParameterInfo.Type.DOUBLE, OutputParameterInfo.DrawingStyle.ARROW_SYMBOL_UP)//WICK_LONG_LOWER
            };
        outputParameterInfos[0].setArrowSymbol('D');
        outputParameterInfos[1].setArrowSymbol('S');
        outputParameterInfos[2].setArrowSymbol('L');
        outputParameterInfos[3].setArrowSymbol('X');
        outputParameterInfos[4].setArrowSymbol('Y');
         
    }
 
    public IndicatorResult calculate(int startIndex, int endIndex) {
 
     if (startIndex - getLookback() < 0) { // Need some candles before we try to calculate anything
           startIndex -= startIndex - getLookback();
       }
     
       if (startIndex > endIndex) { // I think this stops it from calculating unless there is enough bars available.
            return new IndicatorResult(0, 0);
       }
          
       // Loops for iterating arrays go here
        int in, out;
        for (in = startIndex, out = 0; in <= endIndex; in++, out++) {

            //double[] array = inputs[0][1];

            //console.getOut().println("IBAR: " + iBidBars[i].getClose());
        	//Support: you should use i for input indices and j for output indices, we changed the variable names
        	//in order to make it straightforward
            if (isDoji(in))
                outputs[0][out] = iBidBars[in].getHigh();
            if (isBodySmall(in))
                outputs[1][out] = iBidBars[in].getHigh();
            if (isBodyLong(in))
                outputs[2][out] = iBidBars[in].getHigh();
            if (isWickLong(in, true))
                outputs[3][out] = iBidBars[in].getHigh();
            if (isWickLong(in, false))
                outputs[4][out] = iBidBars[in].getLow();

        }
        return new IndicatorResult(startIndex, out); // Returns final result 
         
    }
 
    public IndicatorInfo getIndicatorInfo() {
        return indicatorInfo;
    }
 
    public InputParameterInfo getInputParameterInfo(int index) {
        if (index <= inputParameterInfos.length) {
            return inputParameterInfos[index];
        }
        return null;
    }
 
    //Support: Increase back to 1000 if you really need that much values, otherwise it affects the performance
    public int getLookback() {
        return 100;
    }
 
    public int getLookforward() {
        return 0;
    }
 
    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) {
             
             
             switch (index) {
                case 0:
                    inputs[index] = (double[][]) array;           
                    break;
                case 1:
                    iBidBars = (IBar[]) array;         
                    break;                                       
                default:
                    throw new ArrayIndexOutOfBoundsException(index);
            }
    }
 
    public void setOutputParameter(int index, Object array) {
        outputs[index] = (double[]) array;
    }
     
    public void setOptInputParameter(int index, Object value) {                                   
            switch (index) {
                case 0:
                    LONG_BODY_FACTOR = (Double) value;               
                    break;
                case 1:
                    SMALL_BODY_FACTOR = (Double) value;                   
                    break;           
                case 2:
                    LONG_WICK_FACTOR = (Double) value;                                               
                    break;
                case 3:
                     BACKWARD = (Integer) value;                                   
                     break;                           
                default:
                    throw new ArrayIndexOutOfBoundsException(index);
            }
        }
     
    public boolean isWhite(int shift) {
        double o = iBidBars[shift].getOpen();
        double c = iBidBars[shift].getClose();
 
        boolean retVal = o < c;
        return retVal;
    }
     
    public boolean isBlack(int shift){
        double o = iBidBars[shift].getOpen();
        double c = iBidBars[shift].getClose();
 
        boolean retVal = o > c;
        return retVal;
    }
     
    public boolean isDoji(int shift)  {
        double o = iBidBars[shift].getOpen();
        double c = iBidBars[shift].getClose();
 
        boolean retVal = o == c;
        return retVal;
    }
     
    public boolean isBodyLong(int shift) {
        double candleSize = candleSize(shift);
        double avgCandleSize = avgCandleSize(shift);
        double avgCandleSizeLongBodyFactor = avgCandleSize * LONG_BODY_FACTOR;
 
        boolean retVal = candleSize > avgCandleSizeLongBodyFactor;
        return retVal;
    }
     
    public boolean isBodySmall(int shift) {
        double candleSize = candleSize(shift);
        double avgCandleSize = avgCandleSize(shift);
        double avgCandleSizeSmallBodyFactor = avgCandleSize * SMALL_BODY_FACTOR;
 
        boolean retVal = candleSize < avgCandleSizeSmallBodyFactor;
        return retVal;
    }
     
    public boolean isWickLong(int shift, boolean upper) {
        double wickSize = wickSize(shift, upper);
        double avgWickSize = avgWickSize(shift, upper);
        double avgWickSizeLongWickFactor = avgWickSize * LONG_WICK_FACTOR;
 
        boolean retVal = wickSize > avgWickSizeLongWickFactor;
        return retVal;
    }
     
    public double candleSize(int shift) {
        double o = iBidBars[shift].getOpen();
        double c = iBidBars[shift].getClose();
 
        double retVal = Math.abs(o - c);
        return retVal;
    }
     
    public double avgCandleSize(int shift) {
        double retVal = 0;
        for (int i = shift-BACKWARD; i < shift && i < iBidBars.length; i++) {
            retVal += candleSize(i);
        }
        retVal = retVal / BACKWARD;
        return retVal;
    }
     
    public double wickSize(int shift, boolean upper) {
        double retVal = 0;
        double o = iBidBars[shift].getOpen();
        double c = iBidBars[shift].getClose();
        double h = iBidBars[shift].getHigh();
        double l = iBidBars[shift].getLow();
 
        // we need to branch based on which of the two wicks need to be calculated.
        retVal = upper ? h - Math.max(o, c) : Math.min(o, c) - l;
        return retVal;
    }
     
    public double avgWickSize(int shift, boolean upper) {
        double retVal = 0;
        //Support: addded range check shift + i < iBidBars.length - there was another ArrayIndexOutOfBoundsException
        for (int i = 0; i < BACKWARD && shift + i < iBidBars.length; i++) {
            retVal += wickSize(shift + i, upper);
        }
        retVal = retVal / BACKWARD;
        return retVal;
    }
     
}