import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.TimeZone;

import com.dukascopy.api.*;
import com.dukascopy.api.IEngine.OrderCommand;
import com.dukascopy.api.IIndicators.AppliedPrice;
import com.dukascopy.api.IIndicators.MaType;
import com.dukascopy.api.indicators.IIndicator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

public class Hedge_V2 implements IStrategy {

    private static final Period period = null;
    private IEngine engine;
    private IConsole console;
    private IHistory history;
    private IIndicators indicators;
    private int counter = 0;
    public OfferSide offerSide = OfferSide.BID;
    public AppliedPrice appliedPrice = AppliedPrice.CLOSE;
    public MaType shortMAType = MaType.SMA;
    public int shortMAPeriod = 5;
    public double acceleration = 0.05;
    public double maximum = 0.5;

    @Configurable("Instrument")
    public Instrument instrument = Instrument.EURUSD;

    @Configurable("Period")
    public Period selectedPeriod = Period.ONE_HOUR;

    @Configurable("Slippage")
    public double slippage = 1;

    @Configurable("Amount")
    public double amount = 0.1;

    @Configurable("Stop loss in pips")
    public int stopLossPips = 0;

    @Configurable("Take profit pips")
    public int takeProfitPips = 50;

    @Configurable("Cumulative Profit")
    public int cumulative = 100;

    @Configurable("Max Axctive Orders")
    public int activeOrds = 10;

    private List<IOrder> activeOrders = new LinkedList<IOrder>();

    @SuppressWarnings("serial")
    private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") {
        {
            setTimeZone(TimeZone.getTimeZone("GMT"));
        }
    };

    @Override
    public void onStart(IContext context) throws JFException {
        this.console = context.getConsole();
        this.indicators = context.getIndicators();
        this.history = context.getHistory();
        this.engine = context.getEngine();

        IChart chart = context.getChart(instrument);
        if (chart != null) {
            chart.addIndicator(indicators.getIndicator("SAR"), new Object[] { acceleration, maximum });
        }
    }

    @Override
    public void onBar(Instrument instrument, Period period, IBar askBar, IBar bidBar) throws JFException {
        if (period != this.selectedPeriod || instrument != this.instrument) {
            return;
        }

        ITick tick = history.getLastTick(instrument);

        if (period == this.selectedPeriod) {

            double PIP = instrument.getPipValue();
            int pos = 0;
            for (IOrder order : engine.getOrders(instrument)) {
                if (order.getState() == IOrder.State.FILLED) {
                    if (order.isLong())
                        pos = 1;
                    else
                        pos = -1;
                    if (stopLossPips > 0 && order.getStopLossPrice() == 0)
                        order.setStopLossPrice(order.getOpenPrice() - pos * PIP * stopLossPips);
                    if (takeProfitPips > 0 && order.getTakeProfitPrice() == 0)
                        order.setTakeProfitPrice(order.getOpenPrice() + pos * PIP * takeProfitPips);
                }
            }

            IBar bar0 = history.getBar(instrument, selectedPeriod, offerSide, 0);
            IBar bar1 = history.getBar(instrument, selectedPeriod, offerSide, 1);

            double[] sar = indicators.sar(instrument, selectedPeriod, offerSide, acceleration, maximum,
                    Filter.ALL_FLATS, 2, tick.getTime(), 0);

            boolean isLongSignal = false;
            boolean isShortSignal = false;

            if (sar[1] >= bar1.getClose() && sar[0] < bar0.getClose()) {
                // 1.a) Long: the candle crosses the variable psar [0.05/0.5] in up direction
                isLongSignal = true;

            } else if (sar[1] <= bar1.getClose() && sar[0] > bar0.getClose()) {
                // 1.b) Short: the candle crosses the variable psar [0.05/0.5] in down direction
                isShortSignal = true;
            }

            if (!isLongSignal && !isShortSignal) {
                return;
            }

            // remove orders that are cancelled or closed by the platform
            for (Iterator<IOrder> it = activeOrders.iterator(); it.hasNext();) {
                IOrder order = it.next();
                if (order.getState() == IOrder.State.CANCELED || order.getState() == IOrder.State.CLOSED) {
                    it.remove();
                }
            }

            // close all positions if cumulative profit > cumulative pips
            int profitLoss = 0;
            for (IOrder order : activeOrders) {
                profitLoss += order.getProfitLossInPips();
            }
            if (profitLoss >= cumulative) {
                while (activeOrders.size() > 0) {
                    IOrder order = activeOrders.remove(0);
                    order.close();
                }
            }

            List<IOrder> newOrders = new LinkedList<IOrder>();

            if (isLongSignal) {
                for (IOrder order : activeOrders) {
                    // Having loosing short position(s) for the long signal: open equal amount long
                    // position
                    if (!order.isLong() && order.getProfitLossInPips() < 0 && activeOrders.size() <= activeOrds) {
                        newOrders.add(submitHedgeOrder(OrderCommand.BUY));
                    }
                }

                if (activeOrders.size() == 0) {
                    newOrders.add(submitOrder(OrderCommand.BUY));
                }

            } else if (isShortSignal) {
                for (IOrder order : activeOrders) {
                    // Having loosing long position(s) for the short signal: open equal amount long
                    // position
                    if (order.isLong() && order.getProfitLossInPips() < 0 && activeOrders.size() <= activeOrds) {
                        newOrders.add(submitHedgeOrder(OrderCommand.SELL));
                    }
                }

                if (activeOrders.size() == 0) {
                    newOrders.add(submitOrder(OrderCommand.SELL));
                }
            }

            activeOrders.addAll(newOrders);

        }
    }

    public void onTick(Instrument instrument, ITick tick) throws JFException {
        if (period != this.selectedPeriod || instrument != this.instrument) {
            return;
        }
    }

    public void onMessage(IMessage message) throws JFException {
    }

    public void onAccount(IAccount account) throws JFException {
    }

    public void onStop() throws JFException {
    }

    private IOrder submitHedgeOrder(OrderCommand orderCmd) throws JFException {
        return engine.submitOrder(getLabel(instrument), instrument, orderCmd, amount, 0, slippage, 0, 0);
    }

    private IOrder submitOrder(OrderCommand orderCmd) throws JFException {
        return engine.submitOrder(getLabel(instrument), instrument, orderCmd, amount, 0, slippage, 0, 0);
    }

    @SuppressWarnings("unused")
    private double getPipPrice(int pips) {
        return pips * this.instrument.getPipValue();
    }

    private String getLabel(Instrument instrument) {
        String label = instrument.name();
        label = label + (counter++);
        label = label.toUpperCase();
        return label;
    }

    @SuppressWarnings("unused")
    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) {
                print(dateToStr((Long) ob));
            } else {
                print(ob);
            }
            print(" ");
        }
        console.getOut().println();
    }

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

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

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

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

    @SuppressWarnings("unused")
    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());
        }
        console.getOut().println();
    }

    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") {

            private static final long serialVersionUID = 1L;

            {
                setTimeZone(TimeZone.getTimeZone("GMT"));
            }
        };
        return sdf.format(time);
    }

    public SimpleDateFormat getSdf() {
        return sdf;
    }
}