package jforex;

import java.util.*;

import com.dukascopy.api.*;
import com.dukascopy.api.drawings.IChartObjectFactory;

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

    @Configurable("instrument")
    public Instrument instrument = Instrument.EURUSD;
    @Configurable("period")
    public Period period = Period.ONE_MIN;

    double STEP = instrument.getPipValue();
    double BASE = 1;
    double TOP = 1.2;

    List<Double> priceLevels = new LinkedList<>();
    Map<Double, Double> volumes = new HashMap<>();


    IChart chart;
    IChartObjectFactory factory;
    
    List<IChartObject> objects = new LinkedList<>();
    
    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();

        initPriceLevels();

        chart = context.getChart(instrument);
        factory = chart.getChartObjectFactory();

    }

    private void initPriceLevels() {
        for(double price = BASE; price < TOP; price = price + STEP) {
            priceLevels.add(price);
            volumes.put(price, 0.0);
        }
    }

    public void onAccount(IAccount account) throws JFException {
    }

    public void onMessage(IMessage message) throws JFException {
    }

    public void onStop() throws JFException {
    }

    public void onTick(Instrument instrument, ITick tick) throws JFException {
        if(!instrument.equals(this.instrument)) {
            return;
        }

        addVolumeToLevel(tick);
    }

    private void addVolumeToLevel(ITick tick) {
        Double currentPriceLevel = -1.0;
        for(Double priceLevel : priceLevels) {
            if(priceLevel > tick.getAsk()) {
                break;
            }
            currentPriceLevel = priceLevel;
        }
        Double volume = volumes.get(currentPriceLevel);
        volume = volume + tick.getAskVolume();
        volumes.put(currentPriceLevel, volume);
    }

    public void onBar(Instrument instrument, Period period, IBar askBar, IBar bidBar) throws JFException {
        if(!instrument.equals(this.instrument) || !period.equals(this.period)) {
            return;
        }
        
        drawHistogram(bidBar);
    }

    private void drawHistogram(IBar bidBar) {
        long maxLineWidthInMillis = this.period.getInterval() * 20;
        long currentTime = bidBar.getTime();
        Double maxVolume = findMaxVolume();

        removeAllObjects();
        for(Double priceLevel : priceLevels) {
            Double volume = volumes.get(priceLevel);
            if(volume > 0) {
                drawLine(factory, maxLineWidthInMillis, currentTime, volume, maxVolume, priceLevel);
            }            
        }
    }

    private void removeAllObjects() {
        for(IChartObject object : objects) {
            chart.remove(object);
        }
        objects.clear();
    }

    private Double findMaxVolume() {
        Double maxVolume = -1.0;
        for(Double priceLevel : priceLevels) {
            Double volume = volumes.get(priceLevel);
            if(volume > maxVolume) {
                maxVolume = volume;
            }
        }
        return maxVolume;
    }

    private void drawLine(IChartObjectFactory factory, long maxLineWidthInMillis, long currentTime, Double volume, Double maxVolume, Double priceLevel) {
        long lineStartTime = getLineStartTime(maxLineWidthInMillis, currentTime, volume, maxVolume);
        IChartObject object = factory.createShortLine("level"+ priceLevel, lineStartTime, priceLevel, currentTime, priceLevel);
        objects.add(object);
        chart.add(object);
    }

    private long getLineStartTime(long maxLineWidthInMillis, long currentTime, Double volume, Double maxVolume) {
        double onePercent = maxVolume / 100;
        long lineWidthInPercent = Math.round(volume / onePercent);
        long widthInMilliseconds = lineWidthInPercent * (maxLineWidthInMillis / 100);
        return currentTime - widthInMilliseconds;
    }
}