package indicators;

import com.dukascopy.api.indicators.*;
import com.dukascopy.api.*;
import com.dukascopy.api.indicators.OutputParameterInfo.DrawingStyle;
import com.dukascopy.api.indicators.OutputParameterInfo.Type;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.GeneralPath;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
//import java.util.Date;

/* Created by: S.Kaufmann
 * Date: Jan 30, 2011
 * Time: 9:03:16 PM
 */
 
public class FibonacciIndicator_v1 implements IIndicator, IDrawingIndicator 
{
    private IndicatorInfo indicatorInfo;
    private InputParameterInfo[] inputParameterInfos;
    private OutputParameterInfo[] outputParameterInfos;
    private OptInputParameterInfo[] optInputParameterInfos;
    
    private IBar[][] inputs = new IBar[2][];
    private double[][] outputs = new double[11][];
    
    private InputParameterInfo calculationTimeframe, displayTimeframe;
    private DecimalFormat decimalFormat;
    
    private final GeneralPath generalPath = new GeneralPath(); 
    private List<Point> tmpHandlesPoints = new ArrayList<Point>();   
    
    private boolean showExtensions = true;
    private boolean labelsRight = true;
    private boolean largeLabels = false; 
    private boolean displayPeriod = true;  
    
    private int labelscale = 16; 
    private int lookback = 5;
    
    private int period = 0; // default = DAILY
    private int[] periodValues = new int[3];
    private String[] periodNames = new String[3];
    private IIndicatorContext context = null;
    private IConsole console;
    
    private Color Red = Color.RED;
    private Color Crimson = new Color(220, 20, 60); 
    private Color IndianRed = new Color(205, 92, 92);
    private Color LightCoral = new Color(240, 128, 128);
    private Color PaleVioletRed = new Color(219, 112, 147);
    private Color MidGray = new Color(169, 169, 169); 
    private Color Plum = new Color(221, 160, 221); 

    public void onStart(IIndicatorContext context) 
    {
        this.context = context;
        
        indicatorInfo = new IndicatorInfo("FibonacciIndicator_v1", "Fibonacci Levels", "Support & Resistance Indicators", true, false, true, 2, 6, 11);
        indicatorInfo.setSparceIndicator(true);
        indicatorInfo.setRecalculateAll(true);
               
        displayTimeframe = new InputParameterInfo("Display Input data", InputParameterInfo.Type.BAR);
        displayTimeframe.setFilter(Filter.WEEKENDS);
        
        calculationTimeframe = new InputParameterInfo("Calculation Input data", InputParameterInfo.Type.BAR);
        calculationTimeframe.setPeriod(Period.DAILY_SUNDAY_IN_MONDAY);
        calculationTimeframe.setFilter(Filter.WEEKENDS);
             
        inputParameterInfos = new InputParameterInfo[] 
        {
            displayTimeframe,           // inputs[0]
            calculationTimeframe        // inputs[1]
        };
        
        setPeriods();

        optInputParameterInfos = new OptInputParameterInfo[] 
        {
            new OptInputParameterInfo("Fibonacci Timeframe", OptInputParameterInfo.Type.OTHER, new IntegerListDescription(period, periodValues, periodNames)),
            new OptInputParameterInfo("Period Lookback for Fib High/Low", OptInputParameterInfo.Type.OTHER, new IntegerRangeDescription(lookback, 1, 500, 1)),
            new OptInputParameterInfo("Show Extension Levels", OptInputParameterInfo.Type.OTHER, new BooleanOptInputDescription(showExtensions)),
            new OptInputParameterInfo("Large Labels", OptInputParameterInfo.Type.OTHER, new BooleanOptInputDescription(largeLabels)), 
            new OptInputParameterInfo("Align Labels Right", OptInputParameterInfo.Type.OTHER, new BooleanOptInputDescription(labelsRight)), 
            new OptInputParameterInfo("Display Period", OptInputParameterInfo.Type.OTHER, new BooleanOptInputDescription(displayPeriod))
        };

        outputParameterInfos = new OutputParameterInfo[] 
        {
            createOutputParameterInfo("0.00", OutputParameterInfo.Type.DOUBLE, OutputParameterInfo.DrawingStyle.LINE ),
            createOutputParameterInfo("23.6", OutputParameterInfo.Type.DOUBLE, OutputParameterInfo.DrawingStyle.DASH_LINE ),
            createOutputParameterInfo("38.2", OutputParameterInfo.Type.DOUBLE, OutputParameterInfo.DrawingStyle.DASH_LINE ),
            createOutputParameterInfo("50.0", OutputParameterInfo.Type.DOUBLE, OutputParameterInfo.DrawingStyle.DASH_LINE ),
            createOutputParameterInfo("61.8", OutputParameterInfo.Type.DOUBLE, OutputParameterInfo.DrawingStyle.DASH_LINE ),
            createOutputParameterInfo("76.8", OutputParameterInfo.Type.DOUBLE, OutputParameterInfo.DrawingStyle.DASH_LINE ),
            createOutputParameterInfo("100.0", OutputParameterInfo.Type.DOUBLE, OutputParameterInfo.DrawingStyle.LINE ),
            createOutputParameterInfo("123.6", OutputParameterInfo.Type.DOUBLE, OutputParameterInfo.DrawingStyle.DASH_LINE ),
            createOutputParameterInfo("161.8", OutputParameterInfo.Type.DOUBLE, OutputParameterInfo.DrawingStyle.DASH_LINE ),
            createOutputParameterInfo("261.8", OutputParameterInfo.Type.DOUBLE, OutputParameterInfo.DrawingStyle.DASH_LINE ),
            createOutputParameterInfo("Diagonal", OutputParameterInfo.Type.DOUBLE, OutputParameterInfo.DrawingStyle.DOT_LINE )
        };
        
        // fib levels colors
        outputParameterInfos[0].setColor(Crimson);
        outputParameterInfos[1].setColor(Red);
        outputParameterInfos[2].setColor(Red);
        outputParameterInfos[3].setColor(Red);
        outputParameterInfos[4].setColor(Red);
        outputParameterInfos[5].setColor(Red);
        outputParameterInfos[6].setColor(Crimson);
        outputParameterInfos[7].setColor(IndianRed);
        outputParameterInfos[8].setColor(IndianRed);
        outputParameterInfos[9].setColor(IndianRed);
        outputParameterInfos[10].setColor(MidGray);
             
        decimalFormat = new DecimalFormat("0.0000");
 
    }
    
    private OutputParameterInfo createOutputParameterInfo(String name, Type type, DrawingStyle drawingStyle) 
    {
        return new OutputParameterInfo(name, type, drawingStyle, false)
        {{
            setDrawnByIndicator(true);
        }};
    }
    
    private void setPeriods()
    {
        for (int i = 0; i < periodValues.length; i++) {  periodValues[i] = i; }
       
        periodNames[0] = "Daily";
        periodNames[1] = "Weekly";
        periodNames[2] = "Monthly";  
    }

    public IndicatorResult calculate(int startIndex, int endIndex) 
    {
        int inputLength = inputs[1].length - 1;
        
        if (  inputLength < lookback || startIndex > endIndex || 
              calculationTimeframe.getPeriod().isSmallerThan(displayTimeframe.getPeriod()) )
          {
              new IndicatorResult(0, 0);
          }  
        
        int lastBar = -1;
        if (inputs[1].length > lookback)
        {
           lastBar = getTimeIndex(inputs[1][inputs[1].length - lookback].getTime(), inputs[0]);
        }
        if (lastBar == -1)
        {
              new IndicatorResult(0, 0);
          }
        
        Arrays.fill(outputs[0], Double.NaN);
        Arrays.fill(outputs[1], Double.NaN);
        Arrays.fill(outputs[2], Double.NaN);
        Arrays.fill(outputs[3], Double.NaN);
        Arrays.fill(outputs[4], Double.NaN);
        Arrays.fill(outputs[5], Double.NaN);
        Arrays.fill(outputs[6], Double.NaN);
        Arrays.fill(outputs[7], Double.NaN);
        Arrays.fill(outputs[8], Double.NaN);
        Arrays.fill(outputs[9], Double.NaN);
        Arrays.fill(outputs[10], Double.NaN);
              
        // find the high and lows of the last X periods on the calculation timeframe     
        // grab those values as the fib high and fib low, calculate the levels based on these
        // grab those bar indexes as the timestamps to draw to and from on the display timeframe
        // and draw levels between them
        
        int timeIndexSH = -1;
        int timeIndexSL = -1;
        
        double high = 0;
        double low = 999999;
        
        for (int i = inputs[0].length-1; i > lastBar; i--)
        {
            if (inputs[0][i].getHigh() > high)
            {
                high = inputs[0][i].getHigh();
                timeIndexSH = i;
            }
            if (inputs[0][i].getLow() < low)
            {
                low = inputs[0][i].getLow();
                timeIndexSL = i;
            }
        } 

        if ( (timeIndexSH > -1) && (timeIndexSL > -1) ) 
        {                     
            double range = high-low;
            double baselevel = high;
            int    order = 0;
            
            outputs[10][timeIndexSL]  = low;
            outputs[10][timeIndexSH]  = high;
            
            if (timeIndexSH < timeIndexSL) 
            { 
                range = -range; 
                baselevel = low;
                
                outputs[0][timeIndexSH]  = high;     
                outputs[1][timeIndexSH]  = high - (0.238*range);    
                outputs[2][timeIndexSH]  = high - (0.382*range);       
                outputs[3][timeIndexSH]  = high - (0.500*range);
                outputs[4][timeIndexSH]  = high - (0.618*range);
                outputs[5][timeIndexSH]  = high - (0.768*range);
                outputs[6][timeIndexSH]  = low;   
                
                outputs[0][timeIndexSL]  = high;     
                outputs[1][timeIndexSL]  = high - (0.238*range);    
                outputs[2][timeIndexSL]  = high - (0.382*range);       
                outputs[3][timeIndexSL]  = high - (0.500*range);
                outputs[4][timeIndexSL]  = high - (0.618*range);
                outputs[5][timeIndexSL]  = high - (0.768*range);
                outputs[6][timeIndexSL]  = low; 
                
            }
            else
            {
                outputs[6][timeIndexSH]  = high;     
                outputs[5][timeIndexSH]  = high - (0.238*range);    
                outputs[4][timeIndexSH]  = high - (0.382*range);       
                outputs[3][timeIndexSH]  = high - (0.500*range);
                outputs[2][timeIndexSH]  = high - (0.618*range);
                outputs[1][timeIndexSH]  = high - (0.768*range);
                outputs[0][timeIndexSH]  = low;  
                
                outputs[6][timeIndexSL]  = high;     
                outputs[5][timeIndexSL]  = high - (0.238*range);    
                outputs[4][timeIndexSL]  = high - (0.382*range);       
                outputs[3][timeIndexSL]  = high - (0.500*range);
                outputs[2][timeIndexSL]  = high - (0.618*range);
                outputs[1][timeIndexSL]  = high - (0.768*range);
                outputs[0][timeIndexSL]  = low; 
            }
            
            if (showExtensions)
            {
                outputs[7][timeIndexSH]  = baselevel+(0.238*range);
                outputs[8][timeIndexSH]  = baselevel+(0.618*range);
                outputs[9][timeIndexSH]  = baselevel+(1.618*range);
                outputs[7][timeIndexSL]  = baselevel+(0.238*range);
                outputs[8][timeIndexSL]  = baselevel+(0.618*range);
                outputs[9][timeIndexSL]  = baselevel+(1.618*range);
            }
        }      
        return new IndicatorResult(startIndex, endIndex+1);
    } 
      
    private int getTimeIndex(long time, IBar[] target) 
    {
        if (target == null) { return -1; }
        
        int first = 0;
        int upto = target.length;
        while (first < upto) 
        {
            int mid = (first + upto) / 2;           
            IBar data = target[mid];
            if (data.getTime() == time) { return mid; }
            else if (time < data.getTime())  { upto = mid; } 
            else if (time > data.getTime()) { first = mid + 1; } 
        }
        return -1;
    }

    public IndicatorInfo getIndicatorInfo() 
    {
        return indicatorInfo;
    }

    public InputParameterInfo getInputParameterInfo(int index) 
    {
        if (index <= inputParameterInfos.length) 
        {
            return inputParameterInfos[index];
        }
        return null;
    }

    public int getLookback() 
    {
        return 0;
    }

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

    public void setOptInputParameter(int index, Object value) 
    {
        switch (index) 
        {
            case 0: period = ((Integer) value).intValue();
                    switch (period) 
                    {
                        case 0 : calculationTimeframe.setPeriod(Period.DAILY_SUNDAY_IN_MONDAY); break;
                        case 1 : calculationTimeframe.setPeriod(Period.WEEKLY); break;
                        case 2 : calculationTimeframe.setPeriod(Period.MONTHLY); break;
                        default: calculationTimeframe.setPeriod(Period.DAILY_SUNDAY_IN_MONDAY); break;
                    }
                    break;   
             case 1: lookback = (Integer) value; break; 
             case 2: showExtensions = (Boolean) value; break;
             case 3: largeLabels = (Boolean) value; if (largeLabels) {labelscale = 24; } else { labelscale = 16; } break;          
             case 4: labelsRight= (Boolean) value; break; 
             case 5: displayPeriod = (Boolean) value; break;
             default: break;
        } 
    }

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

    @Override
    public Point drawOutput(Graphics g,
                            int outputIdx,
                            Object values,
                            Color color,
                            Stroke stroke,
                            IIndicatorDrawingSupport indicatorDrawingSupport,
                            List<Shape> shapes,
                            Map<Color, List<Point>> handles)
    {
        tmpHandlesPoints.clear();
        if (values != null) 
        {
            Graphics2D g2D = (Graphics2D) g;
            generalPath.reset();
            double[] output = (double[]) values;          
            g2D.setColor(color);
            g2D.setStroke(stroke); 
            if (outputIdx == 10) { drawDiagonal(g2D, outputIdx, output, indicatorDrawingSupport, generalPath); }
            else { drawFibLevel(g2D, outputIdx, output, indicatorDrawingSupport, generalPath); }
            g2D.draw(generalPath);
            shapes.add((Shape) generalPath.clone()); // cloning path, so when checking for intersection each indicator has its own path
            handles.put(color, new ArrayList<Point>(tmpHandlesPoints));
        }      
        return null;
    }
    
    private void drawFibLevel(Graphics2D g2, int outputIdx, double[] output, IIndicatorDrawingSupport indicatorDrawingSupport, GeneralPath generalPath)
    {
        int maxX = indicatorDrawingSupport.getChartWidth();
        int minX = -maxX;
     
        Integer previousX = null;
        boolean labelsDone = false;
        for (int i = output.length - 1; i >= 0; i --) 
        {
            double d = output[i]; 
            if (Double.isNaN(d))  { continue; }
            
            int x = (int) (indicatorDrawingSupport.getMiddleOfCandle(i-1) - (indicatorDrawingSupport.getCandleWidthInPixels()/2) 
                        - (indicatorDrawingSupport.getSpaceBetweenCandlesInPixels()/2));
            int y = (int)indicatorDrawingSupport.getYForValue(d);
            
            if (previousX == null) 
            { 
                previousX = new Integer( maxX );
            }
            
            if ( (minX <= previousX.intValue() && previousX.intValue() <= maxX) || (minX <= x && x <= maxX) )
            {
                generalPath.moveTo(previousX.intValue(), y);
                generalPath.lineTo(x, y);  // draw the pivot level
                int fontSize = calculateFontSize(indicatorDrawingSupport);
                boolean drawLabels = canDrawLabels(fontSize);
                if (drawLabels && !labelsDone)   // draw the pivot label
                {       
                    java.awt.Font font = new Font(Font.SANS_SERIF, Font.BOLD, fontSize);
                    g2.setFont(font);
                    FontMetrics fm  = g2.getFontMetrics();
                    String valueStr = decimalFormat.format(d);
                    String label = getLabelText(outputIdx) + " (" + valueStr + ")";
                    int width = fm.stringWidth(label);
                    int labelx = getLabelx(output, i, width, indicatorDrawingSupport);
                    int labely = getLabely(output, i, y, indicatorDrawingSupport);
                    g2.drawString(label, labelx, labely); 
                    labelsDone = true;
                } 
            }
            previousX = Integer.valueOf(x);
        }
    }
    
    private void drawDiagonal(Graphics2D g2, int outputIdx, double[] output, IIndicatorDrawingSupport indicatorDrawingSupport, GeneralPath generalPath)
    {
        int x1 = 0;
        int y1 = 0;
        int x2 = 0;
        int y2 = 0; 
        for (int i = output.length - 1; i >= 0; i --) 
        {
            double d = output[i]; 

            if (Double.isNaN(d))  { continue; }
            
            if ( x1 ==0 && y1 ==0)
            {
                x1 = (int) indicatorDrawingSupport.getMiddleOfCandle(i);
                y1 = (int) indicatorDrawingSupport.getYForValue(d);   
            }
            else
            {
                x2 = (int) indicatorDrawingSupport.getMiddleOfCandle(i);
                y2 = (int) indicatorDrawingSupport.getYForValue(d);
                break; 
            }      
        } 
        g2.fillArc(x1-3, y1-3, 6, 6, 0, 360);
        g2.fillArc(x2-3, y2-3, 6, 6, 0, 360);
        generalPath.moveTo(x1, y1);
        generalPath.lineTo(x2, y2);  // draw the fib diagonal
    }
    
    private boolean canDrawLabels(int fontSize) 
    {
        final int MIN_FONT_SIZE = 7; 
        if (fontSize < MIN_FONT_SIZE) {  return false; }
        return true;
    }

    private int calculateFontSize(IIndicatorDrawingSupport indicatorDrawingSupport) 
    {
        final int MIN_FONT_SIZE = 9;
        int MAX_FONT_SIZE = 21;
        if (!largeLabels) { MAX_FONT_SIZE = 13; }
        
        int chartWidth = indicatorDrawingSupport.getChartWidth();
        Double ratio = new Double(indicatorDrawingSupport.getCandleWidthInPixels())/chartWidth;
        int fontsize = (int) (ratio*labelscale);  
        if (fontsize > MAX_FONT_SIZE) { fontsize = MAX_FONT_SIZE; }
        if (fontsize < MIN_FONT_SIZE) { fontsize = MIN_FONT_SIZE; }
        return fontsize;
    }
    
    private int getLabelx(double[] output, int index, int width, IIndicatorDrawingSupport indicatorDrawingSupport)
    {
        int x = (int) indicatorDrawingSupport.getChartWidth() - width - 3;
        if (!labelsRight) // find start of Fib
        {   
            index=0;     
            while ((index < output.length-1) && Double.isNaN(output[index])) {index++;}
            x = (int)(indicatorDrawingSupport.getMiddleOfCandle(index))+2;
        }
        return x;        
    }
    
    private int getLabely(double[] output, int index, int height, IIndicatorDrawingSupport indicatorDrawingSupport)
    {
        int result = height-2;
        return result;    
    }

    private String getLabelText(int outputIdx) 
    {
        String lineCode = "";
        if (displayPeriod) { lineCode = periodNames[period]; }
        switch (outputIdx) 
        {
            case 0  : lineCode += "   0.0%"; break;  
            case 1  : lineCode += "  23.6%"; break;
            case 2  : lineCode += "  38.2%"; break;
            case 3  : lineCode += "  50.0%"; break;
            case 4  : lineCode += "  61.8%"; break;
            case 5  : lineCode += "  78.6%"; break;
            case 6  : lineCode += " 100.0%"; break;
            case 7  : lineCode += " 123.6%"; break;
            case 8  : lineCode += " 161.8%"; break;
            case 9  : lineCode += " 261.8%"; break;
            case 10  : lineCode += ""; break;
            default: throw new IllegalArgumentException("Illegal outputIdx - " + outputIdx);
        }
        return lineCode;
    }
      /*  
    // Color Palette from MT4
    
    // yellows
    private Color DarkOrange = new Color(255, 140, 0);
    private Color Orange = new Color(255, 165, 0);
    private Color Gold = new Color(255, 215, 0);
    
    private Color Mocassin = new Color(255, 228, 181);
    private Color LightYellow = new Color(255, 255, 224);
    private Color PaleGoldenRod = new Color(238, 232, 170);
    private Color Khaki = new Color(240, 230, 140);
    
    // greens
    private Color ForestGreen = new Color(34, 139, 34);
    private Color LimeGreen = new Color(50, 205, 50);
    private Color PaleGreen = new Color(152, 251, 152);
    private Color LightGreen = new Color(144, 238, 144);
    
    // brown and tans
    private Color Tan = new Color(210, 180, 140);
    private Color Chocolate = new Color(210, 105, 30);
    private Color RosyBrown = new Color(188, 143, 143);
    private Color BurlyWood = new Color(222, 184, 135);
    
    // reds
    private Color Firebrick = new Color(178, 34, 34); 
    private Color Crimson = new Color(220, 20, 60); 
    private Color IndianRed = new Color(205, 92, 92);
    private Color LightCoral = new Color(240, 128, 128);
    private Color Pink = new Color(255, 192, 203);
    private Color MistyRose = new Color(255, 228, 225);
    
    // purple and pale purple
    private Color PaleVioletRed = new Color(219, 112, 147);
    private Color Thistle = new Color(216, 191, 216);
    private Color Plum = new Color(221, 160, 221);
    private Color MediumOrchid = new Color(186, 85, 211);
    private Color Orchid = new Color(218, 112, 214);     
       
    // blues
    private Color LightSkyBlue = new Color(135, 206, 250);
    private Color SkyBlue = new Color(135, 206, 235);
    private Color SteelBlue = new Color(70, 130, 180);
    private Color CornflowerBlue = new Color(100, 149, 237);
    private Color DodgerBlue = new Color(30, 144, 255);
    private Color RoyalBlue = new Color(65, 105, 225);
    
    // grays and blacks
    private Color Gainsboro = new Color(220, 220, 220);
    private Color Silver = new Color(192, 192, 192);
    private Color MidGray = new Color(169, 169, 169);  // MT4 DarkGray
    private Color Gray = new Color(128, 128, 128);
    private Color DimGray = new Color(105, 105, 105);
    private Color DarkGray = new Color(85, 85, 85);
    private Color DemiBlack = new Color(64, 64, 64);
    private Color SemiBlack = new Color(32, 32, 32);
    private Color Black = new Color(0,0,0);
    
    // off whites
    private Color Linen = new Color(250, 240, 230);
    private Color NavajoWhite = new Color(255, 222, 173);
    private Color Ivory = new Color(255, 240, 240);
    private Color Snow = new Color(255, 250, 250);
            
    */ 
    
}