package resa.evaluation.topology.vld;

import backtype.storm.generated.GlobalStreamId;
import backtype.storm.task.OutputCollector;
import backtype.storm.task.TopologyContext;
import backtype.storm.topology.OutputFieldsDeclarer;
import backtype.storm.topology.base.BaseRichBolt;
import backtype.storm.tuple.Fields;
import backtype.storm.tuple.Tuple;
import backtype.storm.tuple.Values;
import resa.util.ConfigUtil;
import resa.util.Counter;

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Created by ding on 14-7-3.
 */
public class Aggregater extends BaseRichBolt implements Constant {

    private class FrameContext {
        String frameId;
        int featDescCount = 0;
        int curr = 0;
        Map<Integer, Counter> imageCounter = new HashMap<>();

        FrameContext(String frameId) {
            this.frameId = frameId;
        }

        void update(int[] matchedImages) {
            curr++;
            for (int i = 0; i < matchedImages.length; i += 2) {
                imageCounter.computeIfAbsent(matchedImages[i], (k) -> new Counter()).incAndGet(matchedImages[i + 1]);
            }
        }

        boolean isFinish() {
            return indexPieces == curr && featDescCount != 0;
        }
    }

    private Map<String, FrameContext> pendingFrames;
    private OutputCollector collector;
    private double minPercentage;
    private int indexPieces;

    @Override
    public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
        pendingFrames = new HashMap<>();
        this.collector = collector;
        minPercentage = ConfigUtil.getDouble(stormConf, CONF_MATCH_RATIO, 0.5);
        String srcComp = context.getThisSources().keySet().stream()
                .filter(stream -> stream.get_streamId().equals(STREAM_MATCH_IMAGES))
                .map(GlobalStreamId::get_componentId)
                .findFirst().orElseThrow(() -> new RuntimeException("Can not find source comp"));
        indexPieces = context.getComponentTasks(srcComp).size();
    }

    @Override
    public void execute(Tuple input) {
        FrameContext fCtx = pendingFrames.computeIfAbsent(input.getStringByField(FIELD_FRAME_ID),
                (k) -> new FrameContext(k));
        switch (input.getSourceStreamId()) {
            case STREAM_FEATURE_COUNT:
                fCtx.featDescCount = input.getIntegerByField(FIELD_FEATURE_CNT);
                break;
            case STREAM_MATCH_IMAGES:
                fCtx.update((int[]) input.getValueByField(FIELD_MATCH_IMAGES));
                break;
            default:
                throw new IllegalStateException("Bad stream");
        }
        if (fCtx.isFinish()) {
            String out = fCtx.frameId + ":" + fCtx.imageCounter.entrySet().stream()
                    .filter(e -> (double) e.getValue().get() / fCtx.featDescCount > minPercentage)
                    .map(e -> e.getKey().toString()).collect(Collectors.joining(","));
            System.out.println(out);
            // just for metrics output
            collector.emit(new Values(out));
            pendingFrames.remove(fCtx.frameId);
        }
        collector.ack(input);
    }

    @Override
    public void declareOutputFields(OutputFieldsDeclarer declarer) {
        declarer.declare(new Fields("out"));
    }
}
