package com.tzld.piaoquan.recommend.server.framework.merger;


import com.google.common.base.Function;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import com.tzld.piaoquan.recommend.server.common.base.RankItem;
import com.tzld.piaoquan.recommend.server.framework.candidiate.CandidateInfo;
import com.tzld.piaoquan.recommend.server.framework.common.User;
import com.tzld.piaoquan.recommend.server.gen.recommend.RecommendRequest;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationTargetException;
import java.util.*;


public class MergeUtils {
    public static final Logger LOGGER = LoggerFactory.getLogger(MergeUtils.class);

    // mergeConfFile_topQueueName 对应的 StrategyQueue 缓存
    private static final Map<String, Config> queueConfigMap = new HashMap<>();

    public static StrategyQueue createTopQueue(String mergeConfFile, String topQueueName) {
        String key = mergeConfFile + "_" + topQueueName;
        Config mergeQueueConf;
        if (queueConfigMap.containsKey(key)) {
            mergeQueueConf = queueConfigMap.get(key);
        } else {
            mergeQueueConf = ConfigFactory.parseResources(mergeConfFile);
            queueConfigMap.put(key, mergeQueueConf);
        }
        StrategyQueueConfig strategyQueueConfig = new StrategyQueueConfig();
        if (strategyQueueConfig.load(mergeQueueConf)) {
            LOGGER.debug("Merger config init succ");
        } else {
            LOGGER.error("Merge config init failed: init MergeBiz using queue config {}", mergeConfFile);
        }

        StrategyQueue topQueue = null;
        try {
            topQueue = MergeUtils.constructStrategyQueue(topQueueName, strategyQueueConfig);
        } catch (InstantiationException e) {
            LOGGER.error("construct top StrategyQueue: [{}]", e);
        } catch (IllegalAccessException e) {
            LOGGER.error("construct top StrategyQueue: [{}]", e);
        } catch (ClassNotFoundException e) {
            LOGGER.error("construct top StrategyQueue: [{}]", e);
        } catch (NoSuchMethodException e) {
            LOGGER.error("construct top StrategyQueue: [{}]", e);
        } catch (InvocationTargetException e) {
            LOGGER.error("construct top StrategyQueue: [{}]", e);
        }
        return topQueue;
    }

    public static StrategyQueue constructStrategyQueue(String queueName, StrategyQueueConfig strategyQueueConfig) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        if (!strategyQueueConfig.getStrategyQueueInfoMap().containsKey(queueName)) {
            return null;
        }

        StrategyQueueInfo strategyQueueInfo = strategyQueueConfig.getStrategyQueueInfoMap().get(queueName);
        StrategyQueue strategyQueue = (StrategyQueue) Class.forName(strategyQueueInfo.getQueueClass())
                .getConstructor(StrategyQueueInfo.class, strategyQueueConfig.getClass())
                .newInstance(strategyQueueInfo, strategyQueueConfig);

        return strategyQueue;
    }


    /**
     * 分发items到策略树中
     * 分发的过程中不要破坏相对顺序
     *
     * @param strategyQueue
     * @param items
     */
    public static void distributeItemsToMultiQueues(StrategyQueue strategyQueue, List<RankItem> items) {
        List<StrategyQueue> strategyQueueList = strategyQueue.getAllQueues();

        Multimap<String, RankItem> mergeQueuesItems = ArrayListMultimap.create();
        for (RankItem item : items) {
            for (CandidateInfo candidateInfo : item.getCandidateInfoList()) {
                String mergeQueue = candidateInfo.getCandidate().getMergeQueueName();
                RankItem currentItem = item.deepcopy();

                // set candidate info
                currentItem.setQueue(mergeQueue);
                currentItem.setCandidateInfo(candidateInfo);

                mergeQueuesItems.put(mergeQueue, currentItem);
            }
        }
        for (StrategyQueue queue : strategyQueueList) {
            String mergeQueueName = queue.getStrategyQueueInfo().getQueueName();
            if (mergeQueuesItems.containsKey(mergeQueueName)) {
                List<RankItem> currMergeQueueItems = new ArrayList<RankItem>(mergeQueuesItems.get(mergeQueueName));
                queue.setItems(currMergeQueueItems);
            }
        }
    }




    /**
     * 基于 score 字段融合: score 较大的 Item 优先
     *
     * @param resultRankerItems
     * @param rankerItemsListMap
     * @param freeRecNum
     * @param expId
     */
    public static void simpleMergeByScore(List<RankItem> resultRankerItems,
                                          final Map<String, Pair<MergeRule, List<RankItem>>> rankerItemsListMap,
                                          int freeRecNum,
                                          int expId) {
        // 先定义按score排序优选的独立额
        PriorityQueue<Pair<String, Integer>> mergePriorityQueue = new PriorityQueue<Pair<String, Integer>>(freeRecNum, Comparator.comparing(o -> rankerItemsListMap.get(o.getLeft()).getRight().get(o.getRight())));

        // 大于最小mergenum 同时小于maxMergenum，在队列中add
        for (Pair<MergeRule, List<RankItem>> entry : rankerItemsListMap.values()) {
            if (entry.getLeft().isDisabled(expId) || entry.getRight() == null || entry.getRight().isEmpty()) {
                continue;
            }

            MergeRule myRule = entry.getLeft();
            if (myRule.minMergeNum < myRule.maxMergeNum && entry.getRight().size() > myRule.minMergeNum) {
                Collections.sort(entry.getRight().subList(myRule.minMergeNum, entry.getRight().size()));
                mergePriorityQueue.add(Pair.of(myRule.queueName, myRule.minMergeNum));
            }
        }
        //
        while (freeRecNum > 0 && !mergePriorityQueue.isEmpty()) {
            Pair<String, Integer> item = mergePriorityQueue.poll();
            String myName = item.getLeft();
            int myIndex = item.getRight();

            resultRankerItems.add(rankerItemsListMap.get(myName).getRight().get(myIndex));
            resultRankerItems.get(resultRankerItems.size() - 1).addMergeDecisionLabel("score:" + myName);
            freeRecNum -= 1;
            myIndex += 1;

            if (freeRecNum > 0 && rankerItemsListMap.get(myName).getRight().size() > myIndex && myIndex < rankerItemsListMap.get(myName).getLeft().maxMergeNum) {
                mergePriorityQueue.add(Pair.of(myName, myIndex));
            }
        }
    }

    /**
     * 队列融合:
     * 1. 各队列基于排序不变;
     * 2. 各队列保证最小合并数量 (minMergeNum) 条目;
     * 3. 各队列限制最大合并数量 (maxMergeNum) 条目;
     * 4. 从各队列取 Score 最高条目.
     *
     * @param rankerItemsListMap
     * @param recNum
     * @param expId
     * @return
     */
    public static int simpleMergeWithProtection(List<RankItem> resultRankerItems,
                                                final Map<String, Pair<MergeRule, List<RankItem>>> rankerItemsListMap,
                                                int recNum,
                                                int expId) {
        if (resultRankerItems == null) {
            resultRankerItems = new LinkedList<RankItem>();
        }
        // 保证各队列最小合并条目后剩余可自由调配条目数量
        int freeRecNum = recNum;
        for (Pair<MergeRule, List<RankItem>> entry : rankerItemsListMap.values()) {

            if (entry.getLeft().isDisabled(expId) || entry.getRight() == null || entry.getRight().isEmpty()) {
                continue;
            }

            MergeRule myRule = entry.getLeft();
            int mySize = entry.getRight().size();
            int myMinMergeNum = Math.min(myRule.minMergeNum, mySize);
            freeRecNum -= myMinMergeNum;

            int prevSize = resultRankerItems.size();
            // 先保证最小长度添加
            resultRankerItems.addAll(entry.getRight().subList(0, myMinMergeNum));

            int afterSize = resultRankerItems.size();
            for (int ix = prevSize; ix < afterSize; ++ix) {
                resultRankerItems.get(ix).addMergeDecisionLabel("min_protect:" + myRule.queueName);
            }
        }

        if (freeRecNum > 0) {
            //保证最小长度后，其他按score融合
            simpleMergeByScore(resultRankerItems, rankerItemsListMap, freeRecNum, expId);
        }

        return resultRankerItems.size();
    }

    /**
     * 按优先级队列融合:
     * 1. 各队列基于 priority 依次融合: 各队列保证最小合并数量 (minMergeNum) 条目;
     * 2. 若没有凑足 recNum, 各队列限制最大合并数量 (maxMergeNum) 条目条件下按 score 优先选择;
     *
     * @param rankerItemsListMap
     * @param recNum
     * @param user
     * @param requestData
     * @param requestIndex
     * @param expId
     * @return
     */
    public static int simpleMergeByPriority(List<RankItem> resultRankerItems, final Map<String, Pair<MergeRule, List<RankItem>>> rankerItemsListMap, int recNum, User user, RecommendRequest requestData, int requestIndex, int expId) {
        if (resultRankerItems == null) {
            resultRankerItems = new LinkedList<RankItem>();
        }

        final List<Map.Entry<String, Pair<MergeRule, List<RankItem>>>> rankerItemsList = new LinkedList<Map.Entry<String, Pair<MergeRule, List<RankItem>>>>(rankerItemsListMap.entrySet());

        // 保证各队列最小合并条目后剩余可自由调配条目数量
        Collections.sort(rankerItemsList, new Comparator<Map.Entry<String, Pair<MergeRule, List<RankItem>>>>() {
            private final int DEFAULT_PRIORITY = 0;

            @Override
            public int compare(Map.Entry<String, Pair<MergeRule, List<RankItem>>> o1, Map.Entry<String, Pair<MergeRule, List<RankItem>>> o2) {
                int p1 = getPriority(o1.getValue().getLeft()), p2 = getPriority(o2.getValue().getLeft());
                return p1 - p2;
            }

            private int getPriority(MergeRule rule) {
                if (rule.getProperties().containsKey("priority")) {
                    return Integer.valueOf(rule.getProperties().get("priority"));
                }
                return DEFAULT_PRIORITY;
            }
        });

        int freeRecNum = recNum;
        for (int i = 0; i < rankerItemsList.size(); ++i) {
            if (rankerItemsList.get(i).getValue().getLeft().isDisabled(expId) || rankerItemsList.get(i).getValue().getRight() == null || rankerItemsList.get(i).getValue().getRight().isEmpty()) {
                continue;
            }

            MergeRule myRule = rankerItemsList.get(i).getValue().getLeft();
            int mySize = rankerItemsList.get(i).getValue().getRight().size();
            int myMinMergeNum = Math.min(myRule.minMergeNum, mySize);
            freeRecNum -= myMinMergeNum;

            int prevSize = resultRankerItems.size();
            resultRankerItems.addAll(rankerItemsList.get(i).getValue().getRight().subList(0, myMinMergeNum));
            if (mySize > myMinMergeNum) {
                Collections.sort(rankerItemsList.get(i).getValue().getRight().subList(myMinMergeNum, mySize));
            }
            int afterSize = resultRankerItems.size();
            for (int ix = prevSize; ix < afterSize; ++ix) {
                resultRankerItems.get(ix).addMergeDecisionLabel("min_protect:" + myRule.queueName);
            }
        }

        if (freeRecNum > 0) {
            simpleMergeByScore(resultRankerItems, rankerItemsListMap, freeRecNum, expId);
        }

        return resultRankerItems.size();
    }

    /**
     * 基于 tag 和 category 重排序, 保证具有相同的分类或标签的 Item 在 windowSize 窗口内只有 similarLimit 条
     *
     * @param items
     * @param expectedRecommendItemsCount
     * @param windowSize
     * @param similarLimit
     */
    public static void diversityRerank(List<RankItem> items, Function<Pair<RankItem, RankItem>, Boolean> similarFunc, int expectedRecommendItemsCount, final int windowSize, final int similarLimit) {
        List<RankItem> rerankedItems = new LinkedList<RankItem>();
        expectedRecommendItemsCount = Math.min(expectedRecommendItemsCount, items.size());
        for (int ix = 0; ix < expectedRecommendItemsCount; ++ix) {
            int windowStartPosition = Math.max(ix - windowSize + 1, 0);
            int nextId = 0;
            while (nextId < items.size()) {
                int similarCount = 0;
                for (int jx = windowStartPosition; jx < ix && similarCount < similarLimit; ++jx) {
                    if (similarFunc.apply(Pair.of(items.get(nextId), rerankedItems.get(jx)))) {
                        similarCount += 1;
                    }
                }
                if (similarCount < similarLimit) {
                    break;
                }
                nextId += 1;
            }
            if (nextId >= items.size()) {
                nextId = 0;
            }

            RankItem nextItem = items.get(nextId);
            nextItem.addMergeDecisionLabel("sim_rerank:" + windowStartPosition + "_" + windowSize);
            items.remove(nextId);
            rerankedItems.add(nextItem);
        }

        if (items.size() > 0) {
            rerankedItems.addAll(items);
        }

        items.clear();
        items.addAll(rerankedItems);
    }
}
