package support.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.Instrument;
import com.dukascopy.api.indicators.IDrawingIndicator;
import com.dukascopy.api.indicators.IIndicator;
import com.dukascopy.api.indicators.IIndicatorContext;
import com.dukascopy.api.indicators.IIndicatorDrawingSupport;
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;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.Stroke;
import java.util.List;
import java.util.Map;

public class LevelSensorInd implements IIndicator, IDrawingIndicator {

    private IndicatorInfo indicatorInfo;
    // INPUTS
    private InputParameterInfo[] inputParameterInfos;
    private IBar[] inBar;
    // OPT INPUTS
    private OptInputParameterInfo[] optInputParameterInfos;
    private int period = 100;
    
    // OUTPUTS
    private OutputParameterInfo[] outputParameterInfos;
    private Object[] output;
    private int HIGH = 0;
    private int LOW = 1;
    private int CLOSE = 2;
    // CONTEXT
    private IConsole console;
    // private Instrument pInstrument;
    private Instrument instrument = Instrument.EURUSD; // workaround
    private IIndicator atr;

    class Line {

        double price;
        double length;

        public Line(double price, double length) {
            this.price = price;
            this.length = length;
        }
    }

    public int getLookback() {
        return 0;
    }

    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;
        
        
        double high = Double.MIN_VALUE;
        double low = Double.MAX_VALUE;
        
        for(int i = endIndex - (period - 1); i < endIndex; i++) {
            high = Math.max(high, Math.max(inBar[i].getClose(), inBar[i].getOpen()));
            low = Math.min(low, Math.min(inBar[i].getClose(), inBar[i].getOpen()));
        }
        
        double step = (high - low) / 100;
        int[] barCount = new int[100];
        for(int i = 0; i < 100; i++) {
            barCount[i] = 0;
        }
        
        for(int i = endIndex - (period - 1); i < endIndex; i++) {
            double barLow = Math.min(inBar[i].getClose(), inBar[i].getOpen());
            double barHigh = Math.max(inBar[i].getClose(), inBar[i].getOpen());
            int lowIdx = Math.min((int) Math.round((barLow - low) / step), 99);
            int highIdx = Math.min((int) Math.round((barHigh - low) / step), 99);
            
            for(int j = lowIdx; j <= highIdx; j++) {
                barCount[j]++;
            }            
            
        }
        
        int maxBarConut = 0;
        for(int i = 0; i < 100; i++) {
            maxBarConut = Math.max(maxBarConut, barCount[i]);
        }
        
        Line[] lines = new Line[100];        
        for(int i = 0; i < 100; i++) {
            lines[i] = new Line(low + step * i, (double) barCount[i] / maxBarConut);
        }

        for (int i = 0; i < len; i++) {
            output[i] = null;
        }
        output[len - 1] = lines;

        return new IndicatorResult(startIndex, len);
    }

    /***********
     *  START  *
     ***********/
    public void onStart(IIndicatorContext context) {
        this.console = context.getConsole();

        inputParameterInfos = new InputParameterInfo[]{
            new InputParameterInfo("price", InputParameterInfo.Type.BAR)
        };

        optInputParameterInfos = new OptInputParameterInfo[]{
            new OptInputParameterInfo("Period",
                OptInputParameterInfo.Type.OTHER,
                new IntegerRangeDescription(period, 3, 1000, 2)),        
        };

        outputParameterInfos = new OutputParameterInfo[]{
            new OutputParameterInfo("Main",
            OutputParameterInfo.Type.OBJECT,
            OutputParameterInfo.DrawingStyle.LINE) {
                {
                    setDrawnByIndicator(true);
                }
            },};

        output = new double[outputParameterInfos.length][];

        indicatorInfo = new IndicatorInfo("LEVEL_SENSOR", "Level sensor", "My indicators", true, false, false, inputParameterInfos.length, optInputParameterInfos.length, outputParameterInfos.length);
        indicatorInfo.setRecalculateAll(true);
    }

    @Override
    public java.awt.Point drawOutput(
            Graphics g,
            int outputIdx,
            Object values2,
            Color color,
            Stroke stroke,
            IIndicatorDrawingSupport draw,
            List<Shape> shapes,
            Map<Color, List<java.awt.Point>> handles) {

        if (values2 != null) {
            Graphics2D g2d = (Graphics2D) g;
            g2d.setColor(Color.RED);

            Object[] output = (Object[]) values2;
            for (Object o : output) {
                if (o != null) {
                    Line[] lines = (Line[]) (o);
                    
                    draw.getChartWidth();

                    int totalWidth = draw.getChartWidth() / 3;
                    int rightX = Math.round(draw.getMiddleOfCandle( draw.getIndexOfFirstCandleOnScreen()
                            + draw.getNumberOfCandlesOnScreen()
                            - 5));
                    for (Line line : lines) {                        
                        try {
                            int y = (int) draw.getYForValue(line.price);
                            int startX = rightX - (int) Math.round(totalWidth * line.length);
                            g2d.drawLine(startX, y, rightX, y);                            
                        } catch (Exception e) {
                            print("Error : " + e.toString());
                        }
                    }

                }
            }

        }

        return null;
    }
    

    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) {
        inBar = (IBar[]) array;
    }

    public void setOptInputParameter(int index, Object value) {
        period = (Integer) value;
    }

    public void setOutputParameter(int index, Object array) {
        output = (Object[]) array;
    }

    // ________________________________
    // Support: Some helper methods for printing
    private void print(Object... o) {
        for (Object ob : o) {
            //console.getOut().print(ob + "  ");
            if (ob instanceof double[]) {
                print2((double[]) ob);
            } else if (ob instanceof double[][]) {
                print2((double[][]) ob);
            } else if (ob instanceof Long) {
                print2(dateToStr((Long) 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(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);
    }
    // ________________________________
}