package jforex.requests;

import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.ArrayBlockingQueue;

import com.dukascopy.api.*;


public class ReadTicksMultiInstrQueue implements IStrategy {
    
    private IHistory history;
    private IConsole console;
    private IContext context;
    
    @SuppressWarnings("serial")
    private final SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss") { { setTimeZone(TimeZone.getTimeZone("GMT")); } };
    
    @Configurable("from dd-MM-yyyy HH:mm:ss")
    public String fromStr = "24-06-2012 21:00:00";
    @Configurable("to dd-MM-yyyy HH:mm:ss")
    public String toStr = "25-06-2012 12:00:00";
    @Configurable("tick queue size")
    private int queueSize = 5000;
    
    private final Set<Instrument> instruments = new HashSet<Instrument>(Arrays.asList(new Instrument[] {
            Instrument.CHFJPY,
            Instrument.EURJPY,
            Instrument.EURUSD,
            Instrument.USDJPY
    }));
    
    private  Map<Instrument, Queue<ITick>> insrtTickLists = new HashMap<Instrument, Queue<ITick>>();
    private  Map<Instrument, Boolean> insrtDataLoaded = new HashMap<Instrument, Boolean>();   
    
    @Override
    public void onStart(IContext context) throws JFException {
        history = context.getHistory();
        console = context.getConsole();
        this.context = context;
        
        subscribeInstruments();
        
        long from =0, to=0;
        try {
            from = sdf.parse(fromStr).getTime();
            to = sdf.parse(toStr).getTime();
        } catch (ParseException e) {
            console.getErr().println(e + " on date parsing. The straetgy will stop.");
            context.stop();
        }
        
        
        for(final Instrument instrument : instruments){
            insrtDataLoaded.put(instrument, false);
            final Queue<ITick> ticks = new ArrayBlockingQueue<ITick>(queueSize);
            history.readTicks(instrument, from, to, 
                    
                new LoadingDataListener(){
                @Override
                public void newTick(Instrument instrument, long time, double ask, double bid, double askVol, double bidVol) {
                    ticks.offer(new MockTick(time, ask, bid,askVol, bidVol));
                }

                @Override
                public void newBar(Instrument instrument, Period period, OfferSide side, long time, double open, double close, double low,
                        double high, double vol) {
                    // no bars expected
                    
                }}, 
                
                new LoadingProgressListener () {

                    @Override
                    public void dataLoaded(long start, long end, long currentPosition, String information) {
                        print("dataLoaded: instrument=%s, start=%s, end=%s, currentPosition=%s, information=%s", 
                                instrument, sdf.format(start), sdf.format(end), sdf.format(currentPosition), information);                            
                    }

                    @Override
                    public void loadingFinished(boolean allDataLoaded, long start, long end, long currentPosition) {
                        print("loadingFinished: instrument=%s, allDataLoaded=%s, start=%s, end=%s, currentPosition=%s", 
                                instrument, allDataLoaded, sdf.format(start), sdf.format(end), sdf.format(currentPosition));   
                            insrtDataLoaded.put(instrument, true);
                    }

                    @Override
                    public boolean stopJob() {
                        // TODO Auto-generated method stub 
                        return false;
                    }});
            insrtTickLists.put(instrument, ticks);
        }

    }
    
    private void subscribeInstruments(){
        context.setSubscribedInstruments(instruments);

        // wait max 1 second for the instruments to get subscribed
        int i = 10;
        while (!context.getSubscribedInstruments().containsAll(instruments)) {
            try {
                console.getOut().println("Instruments not subscribed yet " + i);
                Thread.sleep(100);
            } catch (InterruptedException e) {
                console.getOut().println(e.getMessage());
            }
            i--;
        }
    }
    
    private void print (String format, Object...args){
        console.getOut().println(String.format(format, args));
    }

    @Override
    public void onTick(Instrument instrument, ITick tick) throws JFException {
        //perform logging attempt only on EUR/USD tick
        if(instrument != Instrument.EURUSD){
            return;
        }
        //log some info
        boolean allLoadingFinished = true;
        for(Map.Entry<Instrument, Queue<ITick>> entry : insrtTickLists.entrySet()){
            Instrument instr = entry.getKey();
            Queue<ITick> ticks = entry.getValue();
            Boolean loaded = insrtDataLoaded.get(instr);
            if(loaded.booleanValue()){
                if(ticks.size() > 0){
                    print("%s loaded %s ticks, \n\t first tick=%s, \n\t last tick=%s",instr, ticks.size(), ticks.toArray()[0], ticks.toArray()[ticks.size() - 1]);
                } else {
                    print("%s loading finished, but no ticks loaded",instr);   
                }
            } else {
                allLoadingFinished = false;
                print("%s loading not finished, currently loaded %s ticks",instr, ticks.size());                       
            }
        }
        
        if(allLoadingFinished){
            print("All ticks loaded, stopping the strategy.");
            context.stop();
        }
    }

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

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

    @Override
    public void onAccount(IAccount account) throws JFException {}

    @Override
    public void onStop() throws JFException {}
    
    public class MockTick implements ITick {

        long time;
        double ask;
        double bid;
        double askVolume;
        double bidVolume;
        
        @SuppressWarnings("serial")
        private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS") {
            {
                setTimeZone(TimeZone.getTimeZone("GMT"));
            }
        };
        private DecimalFormat df = new DecimalFormat("0.00000");

        
        public MockTick (long time, double ask, double bid, double askVolume, double bidVolume){
            this.ask = ask;
            this.bid = bid;
            this.askVolume = askVolume;
            this.bidVolume = bidVolume;
            this.time = time;
            
        }
        
        @Override
        public long getTime() {
            return time;
        }

        @Override
        public double getAsk() {
            return ask;
        }

        @Override
        public double getBid() {
            return bid;
        }

        @Override
        public double getAskVolume() {
            return askVolume;
        }

        @Override
        public double getBidVolume() {
            return bidVolume;
        }
        
        @Override
        public String toString() {

            StringBuilder str = new StringBuilder();
            str.append(time).append("[").append(sdf.format(time)).append("] Ask: ")
                    .append(df.format(ask)).append(" Bid: ").append(df.format(bid)).append(" AskVol: ").append(df.format(askVolume)).append(" BidVol: ")
                    .append(df.format(bidVolume));
            return str.toString();
        }

        @Override
        public double[] getAsks() {
            return new double[]{getAsk()};
        }

        @Override
        public double[] getBids() {
            return new double[]{getBid()};
        }

        @Override
        public double[] getAskVolumes() {
            return new double[]{getAskVolume()};
        }

        @Override
        public double[] getBidVolumes() {
            return new double[]{getBidVolume()};
        }

        @Override
        public double getTotalAskVolume() {
            return 0.0;}

        @Override
        public double getTotalBidVolume() {
            return 0.0;}

    }

}
