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


import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigObject;
import com.typesafe.config.ConfigValue;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

@Data
public class StrategyQueueConfig {
    private final Logger LOGGER = LoggerFactory.getLogger(StrategyQueueConfig.class);

    private static final String CHILDREN_STRING = "children";
    private static final String STRATEGY_QUEUE_CLASS_CONF_STRING = "class";

    public static final String DEFAULT_STRATEGY_CLASS_NAME = "com.tzld.piaoquan.recommend.server.framework.merger.SimpleMergeQueue";

    private static final String MERGE_RULE_STRING = "merge-rule";
    private static final String QUEUE_NAME_STRING = "queue-name";
    private static final String RECALL_PERCENTAGE_STRING = "recall-percentage";
    private static final String ENABLE_EXPERIMENT_LIST_STRING = "enable-exp";
    private static final String DISABLE_EXPERIMENT_LIST_STRING = "disable-exp";
    private static final String MIN_MERGE_NUM_STRING = "min-merge-num";
    private static final String MAX_MERGE_NUM_STRING = "max-merge-num";

    private static final Set<String> RESERVED_PROPERTIES = new HashSet<String>(Arrays.asList(QUEUE_NAME_STRING, RECALL_PERCENTAGE_STRING, ENABLE_EXPERIMENT_LIST_STRING, DISABLE_EXPERIMENT_LIST_STRING, MIN_MERGE_NUM_STRING, MAX_MERGE_NUM_STRING));

    Map<String, StrategyQueueInfo> strategyQueueInfoMap;


    public boolean load(Config config) {
        config.checkValid(config, "queue-config");
        Config queueConf = config.getConfig("queue-config");
        if (loadMergeQueues(queueConf)) {
            LOGGER.debug("Load queues config success");
        } else {
            LOGGER.error("Load queues config failed");
            return false;
        }

        config.checkValid(config, "rule-config");
        Config rulesConf = config.getConfig("rule-config");
        if (loadMergeRules(rulesConf)) {
            LOGGER.debug("Load rules config success");
        } else {
            LOGGER.error("Load rules config failed");
            return false;
        }
        // TODO: validate queue structure & rules
        for (String queueName : strategyQueueInfoMap.keySet()) {
            StrategyQueue queue = null;
            try {
                queue = MergeUtils.constructStrategyQueue(queueName, this);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }

            if (queue == null) {
                LOGGER.error("Queue config info & rules error");
                return false;
            }
        }

        return true;
    }

    public boolean load(String configFile) {
        Config queueConfig = ConfigFactory.parseResources(configFile);
        return load(queueConfig);
    }

    private boolean loadMergeQueues(Config queueConf) {
        strategyQueueInfoMap = new HashMap<String, StrategyQueueInfo>();

        ConfigObject confObj = queueConf.root();
        for (ConfigObject.Entry<String, ConfigValue> it : confObj.entrySet()) {
            String myName = it.getKey();
            Config myConf = ((ConfigObject) it.getValue()).toConfig();
            if (loadMergeQueueInfo(myName, myConf)) {
                LOGGER.debug("Load queue [{}] info success", myName);
            } else {
                LOGGER.error("Load queue [{}] info failed", myName);
                return false;
            }
        }
        return true;
    }

    private boolean loadMergeQueueInfo(String name, Config conf) {
        LOGGER.debug("Start load queue [{}]", name);
        if (!strategyQueueInfoMap.containsKey(name)) {
            strategyQueueInfoMap.put(name, new StrategyQueueInfo(name));
        }
        StrategyQueueInfo mergeQueueInfo = strategyQueueInfoMap.get(name);

        if (conf.hasPath(STRATEGY_QUEUE_CLASS_CONF_STRING)) {
            mergeQueueInfo.setQueueClass(conf.getString(STRATEGY_QUEUE_CLASS_CONF_STRING));
        } else {
            mergeQueueInfo.setQueueClass(DEFAULT_STRATEGY_CLASS_NAME);
        }

        // 递归的把child节点放在queue中
        if (conf.hasPath(CHILDREN_STRING)) {
            ConfigObject childrenConf = conf.getObject(CHILDREN_STRING);
            for (ConfigObject.Entry<String, ConfigValue> it : childrenConf.entrySet()) {
                String key = it.getKey();
                Config childConf = ((ConfigObject) it.getValue()).toConfig();
                String childName = key;

                boolean isLoadSuccess = loadMergeQueueInfo(childName, childConf);
                if (isLoadSuccess) {
                    LOGGER.debug("Load queue [{}] SUCCESS", childName);
                } else {
                    LOGGER.error("Load queue [{}] FAILED: [{}]", childName, it.getValue());
                    return false;
                }

                mergeQueueInfo.addChild(childName);
            }
        } else {
            mergeQueueInfo.setLeaf(true);
        }

        LOGGER.debug("Load queue [{}] success", name);
        return true;
    }

    private boolean loadMergeRules(Config conf) {
        ConfigObject confObj = conf.root();

        for (ConfigObject.Entry<String, ConfigValue> it : confObj.entrySet()) {
            String myName = it.getKey();
            Config subTreeConf = ((ConfigObject) it.getValue()).toConfig();
            boolean isParseSuccess = parseMergeRule(myName, subTreeConf);
            if (isParseSuccess) {
                LOGGER.debug("Parse rules for queue [{}] SUCCESS", myName);
            } else {
                LOGGER.error("Parse rules for queue [{}] FAILED: [{}]", myName, it.getValue());
                return false;
            }
        }

        return true;
    }

    private boolean parseMergeRule(String name, Config ruleConf) {
        if (!strategyQueueInfoMap.containsKey(name)) {
            LOGGER.error("skip rules for not exist queue [{}]", name);
            return true;
        } else {
            LOGGER.debug("parse rules for queue [{}]", name);
        }

        if (!ruleConf.hasPath(MERGE_RULE_STRING)) {
            LOGGER.debug("no merge rules for queue [{}]", name);
            return true;
        }

        ConfigObject ruleConfObj = ruleConf.getObject(MERGE_RULE_STRING);

        for (ConfigObject.Entry<String, ConfigValue> it : ruleConfObj.entrySet()) {
            String ruleName = it.getKey();
            MergeRule rule = new MergeRule();

            Config myConf = ((ConfigObject) it.getValue()).toConfig();

            rule.recallPercentage = getOrDefault(myConf, RECALL_PERCENTAGE_STRING, 0.1);
            rule.minMergeNum = getOrDefault(myConf, MIN_MERGE_NUM_STRING, 0);
            rule.maxMergeNum = getOrDefault(myConf, MAX_MERGE_NUM_STRING, 500);

            // 子队列名规则: 用 queue-name 显式指定, 默认为对应规则名
            rule.queueName = getOrDefault(myConf, QUEUE_NAME_STRING, ruleName);
            if (myConf.hasPath(ENABLE_EXPERIMENT_LIST_STRING)) {
                List<Integer> expIds = myConf.getIntList(ENABLE_EXPERIMENT_LIST_STRING);
                rule.enableExpIdSet = new HashSet<Integer>(expIds);
            }
            if (myConf.hasPath(DISABLE_EXPERIMENT_LIST_STRING)) {
                List<Integer> expIds = myConf.getIntList(DISABLE_EXPERIMENT_LIST_STRING);
                rule.disableExpIdSet = new HashSet<Integer>(expIds);
            }

            for (ConfigObject.Entry<String, ConfigValue> properties : myConf.entrySet()) {
                if (!RESERVED_PROPERTIES.contains(properties.getKey())) {
                    rule.putProperty(properties.getKey(), myConf.getString(properties.getKey()));
                }
            }

            if (strategyQueueInfoMap.get(name).getChildren().contains(rule.queueName)) {
                strategyQueueInfoMap.get(name).addRule(rule);
            }
        }
        return true;
    }

    private int getOrDefault(Config conf, String path, int defaultValue) {
        if (conf.hasPath(path)) {
            return conf.getInt(path);
        }
        return defaultValue;
    }

    private String getOrDefault(Config conf, String path, String defaultValue) {
        if (conf.hasPath(path)) {
            return conf.getString(path);
        }
        return defaultValue;
    }

    private double getOrDefault(Config conf, String path, double defaultValue) {
        if (conf.hasPath(path)) {
            return conf.getDouble(path);
        }
        return defaultValue;
    }
}
