package jforex.strategies.indicators;

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.feed.IRangeBar;
import com.dukascopy.api.feed.IRangeBarFeedListener;
import com.dukascopy.api.indicators.IIndicator;

@RequiresFullAccess
public class RangeBarStrat implements IStrategy {

    private IEngine engine;
    private IConsole console;
    private IHistory history;
    private int counter = 0;
    private IOrder order;
    
    @Configurable("Open long pips")
    public double openLongPips = 1.5;
    @Configurable("Close long pips")
    public double closeLongPips = 0;
    @Configurable("Open short pips")
    public double openShortPips = -1.5;
    @Configurable("Close short pips")
    public double closeShortPips = 0;
    
    @Configurable("Price range (pips)")
    public int priceRangePips = 2;
    
    @Configurable("Instrument")
    public Instrument instrument = Instrument.EURUSD;
    @Configurable("Period")
    public Period selectedPeriod = Period.TEN_MINS;
    @Configurable("Offer side")
    public OfferSide offerSide = OfferSide.BID;
    @Configurable("Slippage")
    public double slippage = 0;
    @Configurable("Amount")
    public double amount = 0.02;
    
    @Configurable("Take profit pips")
    public int takeProfitPips = 60;
    @Configurable("Stop loss in pips")
    public int stopLossPips = 30;
    @Configurable("Trailing trigger pips")
    public double trailingTriggerPips = 30;
    

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

        IRangeBarFeedListener rangeBarListener = new IRangeBarFeedListener() {
            
            public void onBar(Instrument instrument, OfferSide offerSide, PriceRange priceRange, IRangeBar bar) {
                try {
                    
                    boolean shortSign = false;
                    boolean longSign = false;
                    boolean closeLongSign = false;
                    boolean closeShortSign = false;
                    
                    double delta = bar.getClose() - bar.getOpen();
                    printTime(bar.getTime());
                    print(toStr(delta));
                    
                    if(delta > getPipPrice(openLongPips, instrument)) {
                        longSign = true;
                        closeShortSign = true;
                    }
                    if(delta < getPipPrice(closeLongPips, instrument)) {
                        closeLongSign = true;
                    }
                    
                    if(delta < getPipPrice(openShortPips, instrument)) {
                        shortSign = true;
                        closeLongSign = true;
                    }
                    if(delta > getPipPrice(closeShortPips, instrument)) {
                        closeShortSign = true;
                    }
                    
                    if (order != null) {
                        if(closeLongSign && order.isLong()) {
                            closeOrder(order);
                            order = null;
                        }
                        if(closeShortSign && !order.isLong()) {
                            closeOrder(order);
                            order = null;
                        }
                    }

                    // PLACE ORDER  
                    if(order == null) {
                        if (longSign) {
                            order = submitOrder(OrderCommand.BUY);

                        } else if (shortSign) {
                            order = submitOrder(OrderCommand.SELL);
                        }
                    }                   

                } catch (JFException ex) {
                    ex.printStackTrace(console.getErr());
                }
            }

            private IOrder submitOrder(OrderCommand orderCmd) throws JFException {

                double stopLossPrice = 0.0, takeProfitPrice = 0.0;

                // Calculating order price, stop loss and take profit prices
                if (orderCmd == OrderCommand.BUY) {
                    if (stopLossPips > 0) {
                        stopLossPrice = history.getLastTick(instrument).getBid() - getPipPrice(stopLossPips, instrument);
                    }
                    if (takeProfitPips > 0) {
                        takeProfitPrice = history.getLastTick(instrument).getBid() + getPipPrice(takeProfitPips, instrument);
                    }
                } else {
                    if (stopLossPips > 0) {
                        stopLossPrice = history.getLastTick(instrument).getBid() + getPipPrice(stopLossPips, instrument);
                    }
                    if (takeProfitPips > 0) {
                        takeProfitPrice = history.getLastTick(instrument).getBid() - getPipPrice(takeProfitPips, instrument);
                    }
                }

                return engine.submitOrder(getLabel(instrument), instrument, orderCmd, amount, 0, slippage, stopLossPrice, takeProfitPrice);
            }

            private void closeOrder(IOrder order) throws JFException {
                if (order == null) {
                    return;
                }
                if (order.getState() != IOrder.State.CLOSED && order.getState() != IOrder.State.CREATED && order.getState() != IOrder.State.CANCELED) {
                    order.close();
                    order = null;
                }
            }
            
            private double getPipPrice(double pips, Instrument instr) {
                return pips * instr.getPipValue();
            }

            private String getLabel(Instrument instrument) {
                String label = instrument.name();
                label = label + (counter++);
                label = label.toUpperCase();
                return label;
            }
        };
        
        context.subscribeToRangeBarFeed(instrument, offerSide, PriceRange.valueOf(priceRangePips), rangeBarListener);
    }


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

    }

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

    public void onMessage(IMessage message) throws JFException {
    }

    public void onAccount(IAccount account) throws JFException {
    }

    public void onStop() throws JFException {
    }

    /**************** debug print functions ***********************/
    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 (ob instanceof Long) {
                print2(toStr((Long) ob));
            } else if (ob instanceof IBar) {
                print2(toStr((IBar) ob));
            } else {
                print2(ob);
            }
            print2(" ");
        }
        console.getOut().println();
    }

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

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

    private void print(double d) {
        print(toStr(d));
    }

    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 printTime(Long time) {
        console.getOut().println(toStr(time));
    }
}
