package jforex;

import com.dukascopy.api.*;
import com.dukascopy.api.indicators.IIndicator;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.TimeZone;

public class TrailingStopLossStrategy implements IStrategy {
    private IEngine engine;
    private IConsole console;
    private IHistory history;
    private IContext context;
    private IIndicators indicators;
    private IUserInterface userInterface;

    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();


    }

    public void onTick(Instrument instrument, ITick tick) throws JFException {
        for(IOrder order : engine.getOrders(instrument)) {
            updateTrailingStopLoss(order, tick, 3, 3);
        }
    }

    // (START) trailing stop loss is activated when price is higher than oper price + trailingTrigger pips
    // (TRAILING STOP) if price moves further up (for BUY order), stop loss is updated to pStopLossPips
    public void updateTrailingStopLoss(IOrder order, ITick tick, double pTriggerPips, double pStopLossPips) throws JFException {
        
        if (pTriggerPips <= 0.0) {
            return;
        }

        if (order != null && inState(order, IOrder.State.FILLED)) {
            Instrument instr = order.getInstrument();

            double newStop;
            double openPrice = order.getOpenPrice();
            double currentStopLoss = order.getStopLossPrice();

            if (order.isLong()) { // long side order
                if ((currentStopLoss == 0.0 || tick.getBid() > currentStopLoss + toPrice(pStopLossPips, instr))
                        && tick.getBid() > openPrice + toPrice(pTriggerPips, instr)) {
                    // trailing stop loss
                    newStop = tick.getBid() - toPrice(pStopLossPips, instr);
                    newStop = round(newStop, instr);

                    if (currentStopLoss != newStop) {
                        order.setStopLossPrice(newStop);
                        return;
                    }
                }

            } else { // short side order
                if ((currentStopLoss == 0.0 || tick.getAsk() < currentStopLoss - toPrice(pStopLossPips, instr))
                        && tick.getAsk() < openPrice - toPrice(pTriggerPips, instr)) {

                    // trailing stop loss
                    newStop = tick.getAsk() + toPrice(pStopLossPips, instr);
                    newStop = round(newStop, instr);

                    if (currentStopLoss != newStop) {
                        order.setStopLossPrice(newStop);
                        return;
                    }
                }
            }
        }
    }

    boolean inState(IOrder order, IOrder.State state) {
        return order.getState() == state;
    }

    public void onAccount(IAccount account) throws JFException {
    }

    public void onMessage(IMessage message) throws JFException {
    }

    public void onStop() throws JFException {
    }

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


    private double round(double price, Instrument instr) {
        BigDecimal bd = new BigDecimal(price);
        bd = bd.setScale(instr.getPipScale() + 1, RoundingMode.HALF_UP);
        return bd.doubleValue();
    }

    private double roundPips(double pips) {
        BigDecimal bd = new BigDecimal(pips);
        bd = bd.setScale(1, RoundingMode.HALF_UP);
        return bd.doubleValue();
    }

    private double toPrice(double pips, Instrument instr) {
        return pips * instr.getPipValue();
    }


/************************************************/
/*  PRINT                                       */
    /************************************************/

    private void print(Object... o) {
        for (Object ob : o) {
            //console.getOut().print(ob + "  ");
            if (ob instanceof Double) {
                print2(toStr((Double) ob));
            } else if (ob instanceof double[]) {
                print((double[]) ob);
            } else if (ob instanceof double[][]) {
                print((double[][]) ob);
            } else if (Long.class.isInstance(ob)) {
                print2(toStr((Long) ob));
            } else if (ob instanceof IBar) {
                print2(toStr((IBar) ob));
            } else {
                print2(ob);
            }
            print2(" ");
        }
        console.getOut().println();
    }

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

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

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

    private void print(IBar bar) {
        print(toStr(bar));
    }

    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 toStr(double[] arr) {
        String str = "";
        for (int r = 0; r < arr.length; r++) {
            str += "[" + r + "] " + (new DecimalFormat("#.#######")).format(arr[r]) + "; ";
        }
        return str;
    }

    public static String toStr(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 toStr(double d) {
        return (new DecimalFormat("#.#######")).format(d);
    }

    public String toStr(long time) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") {
            {
                setTimeZone(TimeZone.getTimeZone("GMT"));
            }
        };
        return sdf.format(time);
    }

    private String toStr(IBar bar) {
        return toStr(bar.getTime()) + "  O:" + bar.getOpen() + " C:" + bar.getClose() + " H:" + bar.getHigh() + " L:" + bar.getLow();
    }

    private void print(Long time) {
        console.getOut().println(toStr(time));
    }

    private void print(Throwable th) {
        StackTraceElement[] elem = th.getStackTrace();

        // print stack trace in reverse order because console in jforex client prints in reverse
        for(int i = elem.length - 1; i >= 0; i--) {
            console.getErr().println(elem[i]);
        }
    }
}