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_V3 implements IStrategy {

    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;
    private ITick previousTick = null;

    @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 = 0;

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

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

    @Configurable("Step pips for Hedges")
    public double step = 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;
        }
    }

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

        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.getProfitLossInAccountCurrency();
        }
        if (profitLoss >= cumulative) {
            while (activeOrders.size() > 0) {
                IOrder order = activeOrders.remove(0);
                order.close();
            }
        }

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

        if (previousTick == null) {
            previousTick = tick;
            return;
        }

        if (tick.getBid() >= previousTick.getBid() + getPipPrice(step)) {}

        
        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
                        && tick.getBid() >= previousTick.getBid() + getPipPrice(step)) {
                    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
                        && tick.getBid() <= previousTick.getBid() - getPipPrice(step)) {
                    newOrders.add(submitHedgeOrder(OrderCommand.SELL));
                    previousTick = tick;
                }
            }

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

        activeOrders.addAll(newOrders);

    }

    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;
    }
    private double getPipPrice(double pips) {
        return pips * this.instrument.getPipValue();
    }

}