import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.dukascopy.api.Configurable;
import com.dukascopy.api.Filter;
import com.dukascopy.api.IAccount;
import com.dukascopy.api.IBar;
import com.dukascopy.api.IConsole;
import com.dukascopy.api.IContext;
import com.dukascopy.api.IEngine;
import com.dukascopy.api.IEngine.OrderCommand;
import com.dukascopy.api.IMessage;
import com.dukascopy.api.IOrder;
import com.dukascopy.api.IStrategy;
import com.dukascopy.api.ITick;
import com.dukascopy.api.Instrument;
import com.dukascopy.api.JFException;
import com.dukascopy.api.Period;

public class STEPS_Merge_Double_Negatives implements IStrategy {

    private IEngine engine;
    private IConsole console;
    private int counter = 0;
    private String string = "";

    @Configurable("Instrument")
    public Instrument instrument = Instrument.EURUSD;
    @Configurable("Step pips")
    public double step = 20;

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

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

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

    public Filter filter = Filter.ALL_FLATS;

    private ITick previousTick = null;

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

    }

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

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

        if (tick.getBid() <= previousTick.getBid() - getPipPrice(step)) {
            if (getLongOrders().size() > 1) {
                megaMergeLongs();
                waitUntilOrdersFilled();
            }
            
            if (tradesTotalSELL(instrument)==0) {submitOrder(OrderCommand.BUY, instrument);previousTick = tick;}
            if (negativeOrdersBuy(instrument)>0) {submitOrder(OrderCommand.SELL, instrument);previousTick = tick;}
            
            for (IOrder orderPlus : engine.getOrders()) {
                if (orderPlus.getState() != IOrder.State.CLOSED && orderPlus.getState() != IOrder.State.CANCELED
                        && orderPlus.getState() != IOrder.State.CREATED) {
                    if (orderPlus.getProfitLossInPips() > 1) {
                        orderPlus.close();
                    }
                }
            }

        } else if (tick.getAsk() >= previousTick.getAsk() + getPipPrice(step)) {
            if (getShortOrders().size() > 1) {
                megaMergeShorts();
                waitUntilOrdersFilled();
            }
            if (tradesTotalBUY(instrument)==0) {submitOrder(OrderCommand.SELL, instrument);previousTick = tick;}
            if (negativeOrdersSell(instrument)>0) {submitOrder(OrderCommand.BUY, instrument);previousTick = tick;}

            for (IOrder orderPlus : engine.getOrders()) {
                if (orderPlus.getState() != IOrder.State.CLOSED && orderPlus.getState() != IOrder.State.CANCELED
                        && orderPlus.getState() != IOrder.State.CREATED) {
                    if (orderPlus.getProfitLossInPips() > 1) {
                        orderPlus.close();
                    }
                }
            }
            
        }
    }

    private double getPipPrice(double pips) {
        return pips * this.instrument.getPipValue();
    }

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

    public void onMessage(IMessage message) throws JFException {
        print(message);
    }

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

    public void onAccount(IAccount account) throws JFException {
    }

    public void onStop() throws JFException {
    }

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

    @Override
    public void onBar(Instrument instrument, Period period, IBar askBar, IBar bidBar) throws JFException {
        for (IOrder orderMinus : engine.getOrders()) {
            if (orderMinus.getState() != IOrder.State.CLOSED && orderMinus.getState() != IOrder.State.CANCELED
                    && orderMinus.getState() != IOrder.State.CREATED) {
                if ((stopLossPips > 0) && (orderMinus.getProfitLossInPips() <= -stopLossPips)) {
                    orderMinus.close();
                }
            }
        }

    }

    private List<IOrder> getLongOrders() throws JFException {
        List<IOrder> orders = new ArrayList<IOrder>();
        for (IOrder o : engine.getOrders()) {
            if (o.isLong()) {
                orders.add(o);
            }
        }
        return orders;
    }

    private List<IOrder> getShortOrders() throws JFException {
        List<IOrder> orders = new ArrayList<IOrder>();
        for (IOrder o : engine.getOrders()) {
            if (!o.isLong()) {
                orders.add(o);
            }
        }
        return orders;
    }

    private void waitUntilOrdersFilled() throws JFException {
        for (IOrder order : engine.getOrders()) {
            while (!order.getState().equals(IOrder.State.FILLED) && !order.getState().equals(IOrder.State.OPENED)) {
                order.waitForUpdate(1000, IOrder.State.FILLED);
            }
        }
    }

    private void megaMergeLongs() throws JFException {

        // collecting all instrument we have opened
        Map<Instrument, List<IOrder>> allByInstruments = new HashMap<Instrument, List<IOrder>>();
        for (IOrder order : getLongOrders()) {
            Instrument instr = order.getInstrument();
            List<IOrder> orders = allByInstruments.get(instr);
            if (orders == null) {
                orders = new ArrayList<IOrder>();
                allByInstruments.put(instr, orders);
            }
            orders.add(order);
        }

        for (Instrument instr : allByInstruments.keySet()) {
            List<IOrder> list = allByInstruments.get(instr);
            if (list.size() > 1) {
                mergeOrders(list);
            }
        }
    }

    private void megaMergeShorts() throws JFException {

//collecting all instrument we have opened
        Map<Instrument, List<IOrder>> allByInstruments = new HashMap<Instrument, List<IOrder>>();
        for (IOrder order : getShortOrders()) {
            Instrument instr = order.getInstrument();
            List<IOrder> orders = allByInstruments.get(instr);
            if (orders == null) {
                orders = new ArrayList<IOrder>();
                allByInstruments.put(instr, orders);
            }
            orders.add(order);
        }

        for (Instrument instr : allByInstruments.keySet()) {
            List<IOrder> list = allByInstruments.get(instr);
            if (list.size() > 1) {
                mergeOrders(list);
            }
        }
    }

    public void mergeOrders(List<IOrder> positions) throws JFException {

        if (positions == null || positions.size() == 0) {
            return;
        }
        engine.mergeOrders("Merge" + getLabel(instrument) + counter, positions.toArray(new IOrder[0]));
    }
    public int negativeOrdersBuy(Instrument instrument) throws JFException {
        int counter1 = 0;
        for (IOrder ORD : engine.getOrders(instrument)) {
            if (ORD.getState() == IOrder.State.FILLED && ORD.isLong()
                    && ORD.getProfitLossInPips() <= -1) {
                counter1++;
            }
        }
        return counter1;
    }

    public int negativeOrdersSell(Instrument instrument) throws JFException {
        int counter1 = 0;
        for (IOrder ORD : engine.getOrders(instrument)) {
            if (ORD.getState() == IOrder.State.FILLED && !ORD.isLong()
                    && ORD.getProfitLossInPips() <= -1) {
                counter1++;
            }
        }
        return counter1;
    }
    public int tradesTotalBUY(Instrument instrument) throws JFException {
        int counter1 = 0;
        for (IOrder ORD : engine.getOrders(instrument)) {
            if (ORD.getState() == IOrder.State.FILLED && ORD.isLong()) {
                counter1++;
            }
        }
        return counter1;
    }

    public int tradesTotalSELL(Instrument instrument) throws JFException {
        int counter2 = 0;
        for (IOrder ORD : engine.getOrders(instrument)) {
            if (ORD.getState() == IOrder.State.FILLED && !ORD.isLong()) {
                counter2++;
            }
        }
        return counter2;
    }

}