package newjforex;

import java.awt.Color;
import java.io.File;
import java.text.ParseException;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.*;

import com.dukascopy.api.*;
import com.dukascopy.api.IEngine.OrderCommand;
import com.dukascopy.api.IIndicators.AppliedPrice;
import com.dukascopy.api.IHistory;
import com.dukascopy.api.IIndicators.MaType;
import com.dukascopy.api.indicators.IIndicator;
import com.dukascopy.api.indicators.OutputParameterInfo;
import com.dukascopy.api.indicators.OutputParameterInfo.DrawingStyle;

/**
 * The following strategy shows how one can use a custom, multi-output indicator by:
 * - parsing and logging its outputs,
 * - plotting it on the chart.
 *
 * There are two calculation variants used:
 *  - by shift (single value)
 *  - by candle interval (value array)
 *
 */
@RequiresFullAccess
public class CustomIndPivot implements IStrategy {
    private IConsole console;
    private IHistory history;
    private IIndicators indicators;
    private IChart chart;
    private IOrder order;
    private IEngine engine;

    @Configurable("")
    public File indicatorJfxFile = new File("C:/JForex/Strategies/files/MyIndicator.jfx");
    @Configurable("from time (HH:mm)")
	public String fromTimeStr = "02:00";
	@Configurable("to time (HH:mm)")
	public String toTimeStr = "01:00";
    @Configurable("Instrument")
    public Instrument instrument = Instrument.EURUSD;
    @Configurable("Period")
    public Period period = Period.ONE_HOUR;
    @Configurable("Printable bar count")
    public int candleCount = 20;
    @Configurable("Shift")
    public int shift = 2;
    @Configurable("")
    public AppliedPrice appliedPrice = AppliedPrice.CLOSE;
    @Configurable("")
    public double orderAmount = 0.001;
    @Configurable("SL (pips)")
    public int slPips = 20;
    @Configurable("TP (pips)")
    public int tpPips = 40;
    @Configurable("slippage (pips)")
    public int slippage = 5;
    
    private int counter;
    
    //optional inputs according to optInputParameterInfos of the indicator
    @Configurable("Period")
    public int periodValues = 5;
    @Configurable("Show historical levels")
    public boolean showHistoricalLevels = true;
   
  //outputs according to outputParameterInfos of the indicator

    private double ZeroByShift;
    private double PositiveByShift;
    private double NegativeByShift;
    
    private int outCount = 3;
    private String indicatorName = "MyIndicator";
    Object[] optionalInputArray;

    DecimalFormat df = new DecimalFormat("0.00000##");
    @SuppressWarnings("serial")
    SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss") {
        {
            setTimeZone(TimeZone.getTimeZone("GMT"));
        }
    };

    public void onStart(IContext context) throws JFException {
        this.console = context.getConsole();
        this.indicators = context.getIndicators();
        this.chart = context.getChart(instrument);
       
        this.indicators.registerCustomIndicator(indicatorJfxFile);        

        //note that we retrieve int value of MaType enum
        optionalInputArray = new Object[] { periodValues, showHistoricalLevels };

        IIndicator myIndicator = indicators.getIndicator(indicatorName);
       
        chart.addIndicator(myIndicator, optionalInputArray);

 	if(myIndicator == null){
        	   console.getErr().println("Indicator " +indicatorName+ " not found!");
        	   context.stop();
        	}
        	       
        	if(chart == null){
        	   console.getErr().println("Chart for " +instrument+ " not found! Please open the chart.");
        	   context.stop();
        	}
    }
    
  //use of string operations
  	private boolean isValidTime() throws JFException {			

  		boolean result = false;
  		long lastTickTime = history.getLastTick(instrument).getTime();
  		//you want to work with the date of the last tick - in a case you are back-testing
  		String fromStr = sdf.format(lastTickTime).substring(0, 11) + fromTimeStr + ":00";
  		String toStr = sdf.format(lastTickTime).substring(0, 11) + toTimeStr + ":00";
  		try {
  			long from = sdf.parse(fromStr).getTime();
  			long to = sdf.parse(toStr).getTime();
  			result = lastTickTime > from  && lastTickTime < to;			
  		} catch (ParseException e) {
  			e.printStackTrace();
  		}
  		
  		return result;
  	}

  	private void print(Object o) {
		console.getOut().println(o);
	}
	
	private void printErr(Object o) {
		console.getErr().println(o);
	}
    
    @Override
	public void onBar(Instrument instrument, Period period, IBar askBar, IBar bidBar) throws JFException {
		if (period != this.period || instrument != this.instrument ||  !isValidTime()){
			return;
		}

  //imported by myself strategy needs to know the actual current price?
    	double aBid = history.getLastTick(instrument).getBid();
    	double aAsk = history.getLastTick(instrument).getAsk();
    	
        Object[] resultArr = indicators.calculateIndicator(instrument, period, new OfferSide[] { OfferSide.BID },
                indicatorName, new IIndicators.AppliedPrice[] { IIndicators.AppliedPrice.CLOSE }, optionalInputArray,
                Filter.NO_FILTER, candleCount, bidBar.getTime(), 0);
       
        Object[] resultByShift = indicators.calculateIndicator(instrument, period, new OfferSide[] { OfferSide.BID },
                indicatorName, new IIndicators.AppliedPrice[] { IIndicators.AppliedPrice.CLOSE }, optionalInputArray,
                shift);
      //outputs according to outputParameterInfos of the indicator
        double[] P = (double []) resultArr[0];
        double[] PR1 = (double []) resultArr[1];
        double[] PS1 = (double []) resultArr[2];
        
        double[] outputsByShift = new double[outCount];
        
        for (int i = 0; i < outCount; i++) {
        	resultArr[i] = (double[]) resultArr[i];
            outputsByShift[i] = Double.valueOf(resultByShift[i].toString());
        
        int last = PR1.length - 1;
        int last2 = PS1.length - 1;
        
        print(String.format("%s P: last - %.5f, previous - %.5f; signal: last - %.5f, previous = %.5f", 
				sdf.format(bidBar.getTime()), PR1[last], PR1[last-1], PS1[last2], PS1[last2-1]));
        
	
		//buy if ("Bid Price") is higher than the ("Resistance level PR1")					
		if(aBid > PR1[last]){
			submitOrder(OrderCommand.BUY);
		}
		//sell if ("Ask Price") is bellow the ("Support level SR1")
		if(aAsk < PS1[last2]){
			submitOrder(OrderCommand.SELL);
		}
		//for convenience assign the results to meaningful variables
	ZeroByShift = outputsByShift[0];
        PositiveByShift = outputsByShift[1];
        NegativeByShift = outputsByShift[2]; 
        
        print(sdf.format(bidBar.getTime()) + " Outputs for the last " +candleCount+" bars:");
        print("ZeroOutput: " + arrayToStringLast(P));
        print("PositiveOutput: " + getMaxInfo(PR1) + arrayToStringLast(PR1));
        print("NegativeOutput: " + getMinInfo(PS1) + arrayToStringLast(PS1));
        print(String.format("Outputs by shift=%s: Zero=%s Positive=%s Negative=%s",
                shift, ZeroByShift, df.format(PositiveByShift), df.format(NegativeByShift)));
        }   
    }
    
		private IOrder submitOrder(OrderCommand orderCmd) throws JFException {
		       
	        //send request to close the previous order
	        if(order != null && order.getState() == IOrder.State.FILLED){
	            order.close();
	        }

	        double bid = history.getLastTick(instrument).getBid();
	        double ask = history.getLastTick(instrument).getAsk();
	        double pip = instrument.getPipValue();        
	       
	        //calculate SL and TP prices
	        double stopLossPrice = orderCmd.isLong()
	            ? bid - slPips * pip
	            : ask + slPips * pip;
	        double takeProfitPrice = orderCmd.isLong()
	            ? bid + tpPips * pip
	            : ask - tpPips * pip;
	       
	        order = engine.submitOrder("order" + ++counter , instrument, orderCmd, orderAmount, 0, slippage, stopLossPrice, takeProfitPrice);

	        return order;
	    }
		
    private String getMaxInfo(double[] arr){
        double value = Double.MIN_VALUE;
        int index = -1;
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] > value) {
                index = i;
                value = arr[i];
            }
        }
        return "Max value " + df.format(value) + " index: " + index + "; ";
    }
   
    private String getMinInfo(double[] arr){
        double value = Double.MAX_VALUE;
        int index = -1;
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] < value) {
                index = i;
                value = arr[i];
            }
        }
        return "Min value " + df.format(value) + " index: " + index + "; ";
    }

    private String arrayToStringLast(double[] arr) {
        String str = "";
        for (int r = 0; r < arr.length; r++) {
            str += " [" + r + "]" + df.format(arr[r]) + ",";
        }
        return str;
    }
    public void onAccount(IAccount account) throws JFException {}
    public void onMessage(IMessage message) throws JFException {}
    public void onStop() throws JFException {chart.removeAll();}
    public void onTick(Instrument instrument, ITick tick) throws JFException {}

}