package ru.erfolk.jforex.tester.modules.close;

import com.dukascopy.api.*;
import com.dukascopy.api.IIndicators.MaType;
import org.apache.log4j.Logger;
import ru.erfolk.jforex.tester.Tester;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.text.DateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * Модуль для тестирования выходов.
 * Открывает (и, при необходимости, закрывает) позиции. Для входа/выхода используется пересечение обычных средних
 */
public class MAOpen implements IStrategy {
    private static final Logger LOGGER = Logger.getLogger(MAOpen.class);

    private final MathContext mathContext = new MathContext(6, RoundingMode.HALF_UP);
    private final double amount = 0.001;
    private final double slippage = 5;

    private final Filter maFilter = Filter.WEEKENDS;
    private final OfferSide offerSide = OfferSide.ASK;
    private final IIndicators.AppliedPrice appliedPrice = IIndicators.AppliedPrice.CLOSE;

    private Set<Instrument> instruments = new HashSet<Instrument>();
    private IAccount account;
    private IContext context;
    private IHistory history;
    private IEngine engine;
    private IIndicators indicators;
    private DateFormat df = DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
                                                           DateFormat.MEDIUM);

    @Configurable("Time frame")
    public Period timeframe = Period.FIFTEEN_MINS;
    @Configurable("Fast MA time period")
    public int fast = 5;
    @Configurable("Slow MA time period")
    public int slow = 20;

    public MAOpen() {
        df.setTimeZone(TimeZone.getTimeZone("GMT"));
    }


    public MAOpen(int fast, int slow, Period timeframe) {
        this.fast = fast;
        this.slow = slow;
        this.timeframe = timeframe;
        df.setTimeZone(TimeZone.getTimeZone("GMT"));
    }

    public void addInstrument(Instrument instrument) {
        instruments.add(instrument);
    }

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

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

    @Override
    public void onBar(Instrument instrument, Period period, IBar askBar, IBar bidBar) throws JFException {
        if (period != timeframe) return;
        if (!shouldProcess(instrument)) return;
        Date date = new Date(askBar.getTime());
        if (!Tester.isWeekday(date)) return;

        long time = history.getPreviousBarStart(period, askBar.getTime());
        double[] fast = indicators.ma(instrument,
                                      this.timeframe,
                                      this.offerSide,
                                      this.appliedPrice,
                                      this.fast,
                                      MaType.SMA,
                                      this.maFilter,
                                      2,
                                      time,
                                      0);
        double[] slow = indicators.ma(instrument,
                                      this.timeframe,
                                      this.offerSide,
                                      this.appliedPrice,
                                      this.slow,
                                      MaType.SMA,
                                      this.maFilter,
                                      2,
                                      time,
                                      0);

        BigDecimal[] _fast = {new BigDecimal(fast[0], mathContext),
                              new BigDecimal(fast[1], mathContext)};
        BigDecimal[] _slow = {new BigDecimal(slow[0], mathContext),
                              new BigDecimal(slow[1], mathContext)};

        LOGGER.info(df.format(date) + ": [" + _fast[0] + "]" + ((fast[0] > slow[0]) ? ">" : "<") + "[" + _slow[0] + "]"
                + " [" + _fast[1] + "]" + ((fast[1] > slow[1]) ? ">" : "<") + "[" + _slow[1] + "]");

        // быстрая скользящая пересекла медленную снизу вверх
        if (fast[0] > slow[0] && fast[1] < slow[1]) {
            LOGGER.info(df.format(date) + ": SELL. ");
            LOGGER.info(df.format(new Date(time)) + ": "
                    + "[2]= [" + _fast[1] + "]/[" + _slow[1] + "] "
                    + "[1]= [" + _fast[0] + "]/[" + _slow[0] + "]");
            closeLongOrders();
            submitOrder(instrument, IEngine.OrderCommand.SELL, bidBar.getOpen(), "Sell");
        }
        // быстрая скользящая пересекла медленную сверху вниз
        if (fast[0] < slow[0] && fast[1] > slow[1]) {
            LOGGER.info(df.format(date) + ": BUY. ");
            LOGGER.info(df.format(new Date(time)) + ": "
                    + "[2]= [" + _fast[1] + "]/[" + _slow[1] + "] "
                    + "[1]= [" + _fast[0] + "]/[" + _slow[0] + "]");
            closeShortOrders();
            submitOrder(instrument, IEngine.OrderCommand.BUY, askBar.getOpen(), "Buy");
        }
    }

    @Override
    public void onMessage(IMessage message) throws JFException {
    }

    @Override
    public void onAccount(IAccount account) throws JFException {
        this.account = account;
    }

    @Override
    public void onStop() throws JFException {
        closeOrders();
    }


    private void submitOrder(Instrument instrument,
                             IEngine.OrderCommand orderCmd,
                             double price,
                             String label) throws JFException {
        IOrder order = engine.submitOrder(label, instrument, orderCmd, amount, price, slippage);

        IMessage message;
        while (order.getState() == IOrder.State.CREATED || order.getState() == IOrder.State.OPENED) {
            message = order.waitForUpdate(2, TimeUnit.SECONDS);
            if (message != null) {
                this.context.getConsole().getOut().println(message.getContent());
                LOGGER.info(message.getType() + ": " + message.getContent());
                if (message.getType() == IMessage.Type.ORDER_SUBMIT_REJECTED) System.exit(1);
            }
        }
    }


    private boolean shouldProcess(Instrument instrument) {
        return (instruments.isEmpty() || instruments.contains(instrument));
    }

    private void closeOrders() throws JFException {
        List<IOrder> orders = engine.getOrders();
        for (IOrder order : orders) {
            if (order.getState() == IOrder.State.OPENED) order.close();
            if (order.getState() == IOrder.State.FILLED) order.close();
        }
    }

    private void closeLongOrders() throws JFException {
        List<IOrder> orders = engine.getOrders();
        for (IOrder order : orders) {
            if (!order.getOrderCommand().isLong()) continue;
            if (order.getState() == IOrder.State.OPENED) order.close();
            if (order.getState() == IOrder.State.FILLED) order.close();
        }
    }

    private void closeShortOrders() throws JFException {
        List<IOrder> orders = engine.getOrders();
        for (IOrder order : orders) {
            if (!order.getOrderCommand().isShort()) continue;
            if (order.getState() == IOrder.State.OPENED) order.close();
            if (order.getState() == IOrder.State.FILLED) order.close();
        }
    }
}
