package jforex;

import java.util.*;
import javax.swing.*;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import java.awt.event.*;
import java.awt.Color;
import java.awt.Insets;

import com.dukascopy.api.*;
import com.dukascopy.api.system.*;
import com.dukascopy.api.drawings.*;
import com.dukascopy.api.indicators.*;
import java.awt.FlowLayout;
import java.awt.ScrollPane;


public class FiboTools implements IStrategy {
    
    int retracementDepth = 80;
    int nbars = 1200;

    enum YesNoOpt { YES, NO };
    enum PeriodOpt { SKIP, MONTHLY, WEEKLY, DAILY, FOUR_HOURS, ONE_HOUR,
            THIRTY_MINS, FIFTEEN_MINS, TEN_MINS, FIVE_MINS, ONE_MIN, TEN_SECS };
            
    Object[] periods = new Object[] {
        PeriodOpt.MONTHLY,
        PeriodOpt.WEEKLY,
        PeriodOpt.DAILY,
        PeriodOpt.FOUR_HOURS,
        PeriodOpt.ONE_HOUR,
        PeriodOpt.SKIP,
    };
    
    int lineStyle = 2;
    double transparency = 0.333;

    Color Aqua = new Color(0,255,255);
    Color Gold = new Color(255,215,0);
    Color HoneyDew = new Color(240,255,250);
    Color Fushia = new Color(255,0,255);
    Color Chartreuse = new Color(127,255,0);

    Color getColorWithTransparency(Color color, double transparency) {
        return new Color(color.getRed(), color.getGreen(), color.getBlue(), (int)(255 - (255 * transparency)));
    }

    Object[][] colors = new Object[][] {
        new Object[] { Period.MONTHLY, Aqua },
        new Object[] { Period.WEEKLY, Aqua.darker() },
        new Object[] { Period.DAILY, Gold },
        new Object[] { Period.FOUR_HOURS, Gold.darker() },
        new Object[] { Period.ONE_HOUR, HoneyDew },
        new Object[] { Period.TEN_MINS, HoneyDew.darker() }
    };
    
    Color weeklyPivotsColor = getColorWithTransparency(Chartreuse, 0.5);
    Color monthlyPivotsColor = getColorWithTransparency(Fushia, 0.5);

    int uid = 0;

    private IEngine engine;
    private IConsole console;
    private IHistory history;
    private IContext context;
    private IIndicators indicators;
    private IUserInterface userInterface;
    
    private JComboBox<String> instrumentComboBox;
    private JComboBox<String> wicksComboBox;
    private JSpinner depthSpinner;
    private JComboBox<String> expansionsComboBox;
    private JComboBox[] periodComboBoxes = new JComboBox[6];


    void log(Exception e) {
        console.getOut().println(e.getClass().getName() + ": " + e.getMessage());
    }
    void log(String msg) {
        console.getOut().println(msg);
    }

    Color getColor(Period period) {
        for (int i = 0; i < colors.length; i++) {
            Object[] minPeriodAndColor = colors[i];
            if (period.getInterval() >= ((Period)minPeriodAndColor[0]).getInterval()) {
                return (Color) getColorWithTransparency((Color) minPeriodAndColor[1], transparency);
            }
        }
        return (Color) colors[colors.length - 1][1];
    }

    int getMaxBars(Instrument instrument, Period period, int nbarsRequested) {
        if (period.getInterval() >= Period.MONTHLY.getInterval()) {
            return 180;
        } else if (period.getInterval() >= Period.WEEKLY.getInterval()) {
            return 1000;
        } else if (period.getInterval() >= Period.DAILY.getInterval()) {
            return 8000;
        }
        return Math.min(40000, nbarsRequested);
    }

    boolean chartObjectExists(IChart chart, IChartObject chartObj) {
        for (IChartObject chartObj2 : chart.getAll()) {
            if (chartObj2.getPointsCount() != chartObj.getPointsCount()) {
                continue;
            }
            if (chartObj.getTime(0) == chartObj2.getTime(0) && chartObj.getTime(1) == chartObj2.getTime(1)) {
                return true;
            }
        }
        return false;
    }
    
    JComboBox<String> createEnumComboBox(Class clazz) {
        JComboBox<String> comboBox = new JComboBox<String>();
        for (Object enumConst : clazz.getEnumConstants()) {
            comboBox.addItem(enumConst.toString());
        }
        return comboBox;
    }
    
    JComboBox<String> createEnumComboBox(Class clazz, String... extraItems) {
        JComboBox<String> comboBox = new JComboBox<String>();
        for (String stringItem : extraItems) {
            comboBox.addItem(stringItem);
        }
        for (Object enumConst : clazz.getEnumConstants()) {
            comboBox.addItem(enumConst.toString());
        }
        return comboBox;
    }
    
    JPanel createPanel(Object... objs) {
        JPanel row = new JPanel();
        if (objs[0] != null) {
            row.setBorder(BorderFactory.createTitledBorder((String) objs[0]));
        }
        row.setLayout(new java.awt.GridBagLayout());
        GridBagConstraints c = new GridBagConstraints();
        c.gridx = 0;
        c.gridy = 0;
        c.weightx = 0.0;
        c.weighty = 0.0;
        c.anchor = c.EAST;
        for (int i = 1; i < objs.length; i++) {
            Object o = objs[i];
            if (o instanceof String) {
                c.insets = new Insets(5, 5, 5, 5);
                row.add(new JLabel((String) o), c);
            } else if (o instanceof JComponent) {
                c.insets = new Insets(5, 5, 5, 20);
                row.add((JComponent) o, c);
            }
            c.gridx++;
        }
        c.gridx = c.gridy = 99;
        c.weightx = c.weighty = 1.0;
        JPanel p = new JPanel();
        p.setPreferredSize(new java.awt.Dimension(0,0));
        row.add(p, c);
        return row;
    }

    public void onStart(IContext context) throws JFException {
        this.engine = context.getEngine();
        this.console = context.getConsole();
        this.history = context.getHistory();
        this.context = context;
        this.indicators = context.getIndicators();
        this.userInterface = context.getUserInterface();
        JPanel tab = this.userInterface.getBottomTab("Fibonacci Tools");
        GridBagLayout gridbag = new GridBagLayout();
        GridBagConstraints c = new GridBagConstraints();
        tab.setLayout(gridbag);
        
        instrumentComboBox = createEnumComboBox(Instrument.class, "ALL");
        instrumentComboBox.setSelectedItem("ALL");
        wicksComboBox = createEnumComboBox(YesNoOpt.class);
        depthSpinner = new JSpinner(new SpinnerNumberModel(retracementDepth, 1, 20000, 1));
        expansionsComboBox = createEnumComboBox(YesNoOpt.class);
        c.fill = c.BOTH;
        c.gridx = 0;
        tab.add(createPanel("Settings:",
                "Instrument:", instrumentComboBox,
                "Draw Expansions:", expansionsComboBox,
                "Retrace Wicks:", wicksComboBox,
                "Retracement Depth: ", depthSpinner), c);

        JButton drawAllButton = new JButton("Draw Retracements");
        JButton eraseAllButton = new JButton("Erase Retracements");
        JPanel multiPanel = createPanel("Retracements For Multiple Time-Frames:", drawAllButton, eraseAllButton);
        for (int i = 0; i < periodComboBoxes.length; i++) {
            periodComboBoxes[i] = createEnumComboBox(PeriodOpt.class);
            periodComboBoxes[i].setSelectedItem(periods[i].toString());
        } 
        GridBagConstraints c2 = new GridBagConstraints();
        c2.gridy = 1;
        c2.gridwidth = 1;
        multiPanel.add(createPanel(null, "Period 1:", periodComboBoxes[0], "Period 2:", periodComboBoxes[1], "Period 3:", periodComboBoxes[2]), c2);
        c2.gridy = 2;
        multiPanel.add(createPanel(null, "Period 4:", periodComboBoxes[3], "Period 5:", periodComboBoxes[4], "Period 6:", periodComboBoxes[5]), c2);
        c.gridheight = 2;
        tab.add(multiPanel, c);
        
        JButton drawButton = new JButton("Draw Retracement");
        JButton eraseButton = new JButton("Erase Retracement");
        c.gridheight = 1;
        c.gridx = 1;
        c.gridy = 1;
        tab.add(createPanel("Retracement For Current Chart Periods:", drawButton, eraseButton), c);
        
        JButton addPivotsButton = new JButton("Add Pivots");
        JButton removePivotsButton = new JButton("Remove Pivots");
        c.gridy = 2;
        tab.add(createPanel("Pivots", addPivotsButton, removePivotsButton), c);
        
        c.gridx = 9;
        c.gridy = 9;
        c.weightx = 1.0;
        c.weighty = 1.0;
        c.gridwidth = 9;
        c.gridheight = 9;
        c.fill = c.BOTH;
        tab.add(Box.createVerticalStrut(2), c);
        
        drawAllButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                try {
                    drawAllRetracements();
                } catch (JFException exception) {
                    log(exception);
                }
            }
        });
        drawButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                try {
                    drawRetracement();
                } catch (JFException exception) {
                    log(exception);
                }
            }
        });
        eraseAllButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                eraseAllRetracements();
            }
        });
        eraseButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                eraseRetracements();
            }
        });
        addPivotsButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                addPivots();
            }
        });
        removePivotsButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                removePivots();
            }
        });
    }
   
   IIndicator getPivots(int timePeriod) {
       IIndicator pivots = indicators.getIndicator("FibPivot");
       for (int i = 0; i < pivots.getIndicatorInfo().getNumberOfOutputs(); i++) {
           if (timePeriod == 9) {
               pivots.getOutputParameterInfo(i).setColor(monthlyPivotsColor);
           } else {
               pivots.getOutputParameterInfo(i).setColor(weeklyPivotsColor);
           }
           if (i > 0) {
               pivots.getOutputParameterInfo(i).setDrawingStyle(OutputParameterInfo.DrawingStyle.DOT_LINE);
           }
       }
       return pivots;
   }
    
    void addPivots() {
        for (Instrument instrument : context.getSubscribedInstruments()) {
            if (!instrumentIsSelected(instrument)) {
                continue;
            }
            for (IChart chart : context.getCharts(instrument)) {
                boolean hasPivots = false;
                for (IIndicator indicator : chart.getIndicators()) {
                    if (indicator.getIndicatorInfo().getName().equalsIgnoreCase("FibPivot")) {
                        hasPivots = true;
                        break;
                    }
                }
                if (!hasPivots) {
                    IIndicator weeklyPivots = getPivots(8);
                    chart.addIndicator(weeklyPivots, new Object[] { 8 });
                    IIndicator monthlyPivots = getPivots(9);
                    chart.addIndicator(monthlyPivots, new Object[] { 9 });
                }
            }
        }
    }
    
    void removePivots() {
        for (Instrument instrument : context.getSubscribedInstruments()) {
            if (!instrumentIsSelected(instrument)) {
                continue;
            }
            for (IChart chart : context.getCharts(instrument)) {
                boolean hasPivots = false;
                for (IIndicator indicator : chart.getIndicators()) {
                    if (indicator.getIndicatorInfo().getName().equalsIgnoreCase("FibPivot")) {
                        chart.removeIndicator(indicator);
                    }
                }
            }
        }
    }
    
    boolean instrumentIsSelected(Instrument instrument) {
        String selectedInstrument = (String) instrumentComboBox.getSelectedItem();
        return selectedInstrument.equals("ALL") || instrument.toString().equals(selectedInstrument);
    }

    void eraseAllRetracements() {
        for (Instrument instrument : context.getSubscribedInstruments()) {
            if (!instrumentIsSelected(instrument)) {
                continue;
            }
            for (IChart chart : context.getCharts(instrument)) {
                for (IChartObject chartObj : chart.getAll()) {
                    if ((chartObj instanceof IFiboRetracementChartObject) || (chartObj instanceof IFiboExpansionChartObject)) {
                        chart.remove(chartObj);
                    }
                }
            }
        }
    }

    void eraseRetracements() {
        for (Instrument instrument : context.getSubscribedInstruments()) {
            for (IChart chart : context.getCharts(instrument)) {
                for (IChartObject chartObj : chart.getAll()) {
                    if ((chartObj instanceof IFiboRetracementChartObject) || (chartObj instanceof IFiboExpansionChartObject)) {
                        String[] keyParts = chartObj.getKey().split("-");
                        if (keyParts.length == 3 && keyParts[1].equals(chart.getSelectedPeriod().toString())) {
                            chart.remove(chartObj);
                        }
                    }
                }
            }
        }
    }

    void drawAllRetracements() throws JFException {
        for (Instrument instrument : context.getSubscribedInstruments()) {
            if (!instrumentIsSelected(instrument)) {
                continue;
            }
            for (JComboBox comboBox : periodComboBoxes) {
                String selectedPeriod = comboBox.getSelectedItem().toString();
                if (!selectedPeriod.equals("SKIP")) {
                    drawRetracement(instrument, Period.valueOf(selectedPeriod), false);
                }
            }
        }
    }

    void drawRetracement() throws JFException {
        for (Instrument instrument : context.getSubscribedInstruments()) {
            for (IChart chart : context.getCharts(instrument)) {
                drawRetracement(instrument, chart.getSelectedPeriod(), true);
            }
        }
    }

    void drawRetracement(Instrument instrument, Period period, boolean matchPeriod) throws JFException {
        for (IChart chart : context.getCharts(instrument)) {
            if (matchPeriod && !period.equals(chart.getSelectedPeriod())) {
                continue;
            }
            Number[] retracement = calculateRetracement(instrument, period);
            IChartObjectFactory chartObjectFactory = chart.getChartObjectFactory();
            IFiboRetracementChartObject retracementChartObj = chartObjectFactory
                    .createFiboRetracement("retracement-" + period.toString() + "-" + uid++,
                    retracement[0].longValue(), retracement[1].doubleValue(),
                    retracement[2].longValue(), retracement[3].doubleValue());
            retracementChartObj.setColor(getColor(period));
            retracementChartObj.setLineStyle(lineStyle);
            IFiboExpansionChartObject expansionChartObj = null;
            if (expansionsComboBox.getSelectedItem().equals("YES")) {
                expansionChartObj = chartObjectFactory
                        .createFiboExpansion("expansion-" + period.toString() + "-" + uid++);
                expansionChartObj.setTime(0, retracement[0].longValue());
                expansionChartObj.setPrice(0, retracement[1].doubleValue());
                expansionChartObj.setTime(1, retracement[2].longValue());
                expansionChartObj.setPrice(1, retracement[3].doubleValue());
                expansionChartObj.setTime(2, retracement[2].longValue());
                expansionChartObj.setPrice(2, retracement[3].doubleValue());
                expansionChartObj.setColor(getColor(period));
                expansionChartObj.setLineStyle(lineStyle);
            }
            if (!chartObjectExists(chart, retracementChartObj)) {
                chart.addToMainChart(retracementChartObj);
                if (expansionChartObj != null) {
                    chart.addToMainChart(expansionChartObj);
                }
            }
        }
    }
    
    double[] calculateZigZag(Instrument instrument, Period period, long from, long to) throws JFException {
        String indicatorName = wicksComboBox.getSelectedItem().toString().equals("NO")? "WicklessZigZag" : "ZigZag";
        retracementDepth = (Integer) depthSpinner.getValue()    // API values do not match values that appear on charts?!
                ;//* (period.getInterval() <= Period.ONE_HOUR.getInterval()? 1.6 : 1);
        return (double[]) indicators.calculateIndicator(instrument, period, new OfferSide[] { OfferSide.BID },
                indicatorName, new IIndicators.AppliedPrice[] { IIndicators.AppliedPrice.CLOSE },
                new Object[] { retracementDepth, 5, 3 }, Filter.NO_FILTER, getMaxBars(instrument, period, nbars), to, 0)[0];
    }

    Number[] calculateRetracement(Instrument instrument, Period period) throws JFException {
        ITick lastTick = history.getLastTick(instrument);
        long to = history.getBarStart(period, lastTick.getTime());
        long from = history.getTimeForNBarsBack(period, to, getMaxBars(instrument, period, nbars));
        double[] retracementZigzags = calculateZigZag(instrument, period, from, to);
        int zigzagCount = 0;
        double[] largestZigzag = new double[] { Double.NaN, Double.NaN };
        long[] largestZigzagTime = new long[2];
        double[] zigzag = new double[2];
        double priceAtPoint = Double.NaN;
        long timeAtPoint = 0;
        for (int i = retracementZigzags.length - 2; i >= 0; i--) {
            if (Double.isNaN(retracementZigzags[i])) {
                continue;
            }
            if (Double.isNaN(largestZigzag[1])) {
                largestZigzag[1] = retracementZigzags[i];
                largestZigzagTime[1] = from + i * period.getInterval();
            } else if (Double.isNaN(largestZigzag[0])) {
                largestZigzag[0] = retracementZigzags[i];
                largestZigzagTime[0] = from + i * period.getInterval();
            }
            zigzag[0] = retracementZigzags[i];
            zigzag[1] = priceAtPoint;
            Arrays.sort(zigzag);
            if (!Double.isNaN(priceAtPoint)
                    && zigzag[0] <= Math.min(largestZigzag[0], largestZigzag[1])
                    && zigzag[1] >= Math.max(largestZigzag[0], largestZigzag[1])) {
                largestZigzag[0] = retracementZigzags[i];
                largestZigzagTime[0] = from + i * period.getInterval();
                largestZigzag[1] = priceAtPoint;
                largestZigzagTime[1] = timeAtPoint;
            }
            priceAtPoint = retracementZigzags[i];
            timeAtPoint = from + i * period.getInterval();
        }
        if (!Double.isNaN(largestZigzag[0]) && !Double.isNaN(largestZigzag[1]))
            return new Number[] { largestZigzagTime[0], largestZigzag[0], largestZigzagTime[1], largestZigzag[1] };
        else
            return null;
    }

    public void onAccount(IAccount account) throws JFException {
    }

    public void onMessage(IMessage message) throws JFException {
    }

    public void onStop() throws JFException {
    }

    public void onTick(Instrument instrument, ITick tick) throws JFException {
    }

    public void onBar(Instrument instrument, Period period, IBar askBar, IBar bidBar) throws JFException {
    }
}