package com.lts.core.registry;

import com.lts.core.AppContext;
import com.lts.core.cluster.Node;
import com.lts.core.commons.collect.ConcurrentHashSet;
import com.lts.core.constant.Constants;
import com.lts.core.factory.NamedThreadFactory;

import java.util.*;
import java.util.concurrent.*;

/**
 * @author Robert HG (254963746@qq.com) on 5/17/15.
 */
public abstract class FailbackRegistry extends AbstractRegistry {

    // 定时任务执行器
    private final ScheduledExecutorService retryExecutor = Executors.newScheduledThreadPool(1,
            new NamedThreadFactory("LTSRegistryFailedRetryTimer", true));

    // 失败重试定时器，定时检查是否有请求失败，如有，无限次重试
    private ScheduledFuture<?> retryFuture;

    // 注册失败的定时重试
    private final Set<Node> failedRegistered = new ConcurrentHashSet<Node>();
    private final Set<Node> failedUnRegistered = new ConcurrentHashSet<Node>();
    private final ConcurrentMap<Node, Set<NotifyListener>> failedSubscribed = new ConcurrentHashMap<Node, Set<NotifyListener>>();
    private final ConcurrentMap<Node, Set<NotifyListener>> failedUnsubscribed = new ConcurrentHashMap<Node, Set<NotifyListener>>();
    private final ConcurrentMap<Node, Map<NotifyListener, NotifyPair<NotifyEvent, List<Node>>>> failedNotified = new ConcurrentHashMap<Node, Map<NotifyListener, NotifyPair<NotifyEvent, List<Node>>>>();

    public FailbackRegistry(AppContext appContext) {
        super(appContext);

        int retryPeriod = appContext.getConfig().getParameter(Constants.REGISTRY_RETRY_PERIOD_KEY, Constants.DEFAULT_REGISTRY_RETRY_PERIOD);

        this.retryFuture = retryExecutor.scheduleWithFixedDelay(new Runnable() {
            public void run() {
                // 检测并连接注册中心
                try {
                    retry();
                } catch (Throwable t) { // 防御性容错
                    LOGGER.error("Unexpected error occur at failed retry, cause: " + t.getMessage(), t);
                }
            }
        }, retryPeriod, retryPeriod, TimeUnit.MILLISECONDS);
    }

    @Override
    public void register(Node node) {
        try {
            super.register(node);
            failedRegistered.clear();
            doRegister(node);
        } catch (Exception e) {
            // 将失败的注册请求记录到失败列表，定时重试
            failedRegistered.add(node);
        }
    }

    @Override
    public void unregister(Node node) {
        try {
            super.unregister(node);
            failedUnRegistered.clear();
            doUnRegister(node);
        } catch (Exception e) {
            // 将失败的取消注册请求记录到失败列表，定时重试
            failedUnRegistered.add(node);
        }
    }

    @Override
    public void subscribe(Node node, NotifyListener listener) {
        try {
            super.subscribe(node, listener);

            removeFailedSubscribed(node, listener);

            doSubscribe(node, listener);

        } catch (Exception e) {
            addFailedSubscribed(node, listener);
        }
    }

    @Override
    public void unsubscribe(Node node, NotifyListener listener) {
        try {
            super.unsubscribe(node, listener);

            removeFailedSubscribed(node, listener);

            doUnsubscribe(node, listener);

        } catch (Exception e) {
            addFailedUnsubscribed(node, listener);
        }
    }

    protected void addFailedUnsubscribed(Node node, NotifyListener listener) {
        // 将失败的取消订阅请求记录到失败列表，定时重试
        Set<NotifyListener> listeners = failedUnsubscribed.get(node);
        if (listeners == null) {
            failedUnsubscribed.putIfAbsent(node, new ConcurrentHashSet<NotifyListener>());
            listeners = failedUnsubscribed.get(node);
        }
        listeners.add(listener);
    }

    @Override
    protected void notify(NotifyEvent event, List<Node> nodes, NotifyListener listener) {
        try {
            super.notify(event, nodes, listener);
        } catch (Exception e) {
            // 将失败的通知请求记录到失败列表，定时重试
            Map<NotifyListener, NotifyPair<NotifyEvent, List<Node>>> listeners = failedNotified.get(getNode());

            if (listeners == null) {
                failedNotified.putIfAbsent(getNode(), new ConcurrentHashMap<NotifyListener, NotifyPair<NotifyEvent, List<Node>>>());
                listeners = failedNotified.get(getNode());
            }
            listeners.put(listener, new NotifyPair<NotifyEvent, List<Node>>(event, nodes));
            LOGGER.error("Failed to notify, waiting for retry, cause: " + e.getMessage(), e);
        }
    }

    @Override
    public void destroy() {
        super.destroy();
        try {
            retryFuture.cancel(true);
        } catch (Throwable t) {
            LOGGER.warn(t.getMessage(), t);
        }
    }

    @Override
    protected void recover() throws Exception {
        // register
        Set<Node> recoverRegistered = new HashSet<Node>(getRegistered());
        if (!recoverRegistered.isEmpty()) {
            if (LOGGER.isInfoEnabled()) {
                LOGGER.info("Recover register node " + recoverRegistered);
            }
            for (Node node : recoverRegistered) {
                failedRegistered.add(node);
            }
        }
        // subscribe
        Map<Node, Set<NotifyListener>> recoverSubscribed = new HashMap<Node, Set<NotifyListener>>(getSubscribed());
        if (!recoverSubscribed.isEmpty()) {
            if (LOGGER.isInfoEnabled()) {
                LOGGER.info("Recover subscribe node " + recoverSubscribed.keySet());
            }
            for (Map.Entry<Node, Set<NotifyListener>> entry : recoverSubscribed.entrySet()) {
                Node node = entry.getKey();
                for (NotifyListener listener : entry.getValue()) {
                    addFailedSubscribed(node, listener);
                }
            }
        }
    }

    private void removeFailedSubscribed(Node node, NotifyListener listener) {
        Set<NotifyListener> listeners = failedSubscribed.get(node);
        if (listeners != null) {
            listeners.remove(listener);
        }
        listeners = failedUnsubscribed.get(node);
        if (listeners != null) {
            listeners.remove(listener);
        }
        Map<NotifyListener, NotifyPair<NotifyEvent, List<Node>>> notified = failedNotified.get(node);
        if (notified != null) {
            notified.remove(listener);
        }
    }

    private void addFailedSubscribed(Node node, NotifyListener listener) {
        Set<NotifyListener> listeners = failedSubscribed.get(node);
        if (listeners == null) {
            failedSubscribed.putIfAbsent(node, new ConcurrentHashSet<NotifyListener>());
            listeners = failedSubscribed.get(node);
        }
        listeners.add(listener);
    }

    protected void retry() {
        if (!failedRegistered.isEmpty()) {
            Set<Node> failed = new HashSet<Node>(failedRegistered);
            if (failed.size() > 0) {
                if (LOGGER.isInfoEnabled()) {
                    LOGGER.info("Retry register {}", failed);
                }
                try {
                    for (Node node : failed) {
                        doRegister(node);
                        failedRegistered.remove(node);
                    }
                } catch (Throwable t) {     // 忽略所有异常，等待下次重试
                    LOGGER.warn("Failed to retry register " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                }
            }
        }
        if (!failedUnRegistered.isEmpty()) {
            Set<Node> failed = new HashSet<Node>(failedUnRegistered);
            if (failed.size() > 0) {
                if (LOGGER.isInfoEnabled()) {
                    LOGGER.info("Retry unregister {}", failed);
                }
                try {
                    for (Node node : failed) {
                        doUnRegister(node);
                        failedUnRegistered.remove(node);
                    }
                } catch (Throwable t) {     // 忽略所有异常，等待下次重试
                    LOGGER.warn("Failed to retry unregister " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                }
            }
        }
        if (!failedSubscribed.isEmpty()) {
            Map<Node, Set<NotifyListener>> failed = new HashMap<Node, Set<NotifyListener>>(failedSubscribed);
            for (Map.Entry<Node, Set<NotifyListener>> entry : new HashMap<Node, Set<NotifyListener>>(failed).entrySet()) {
                if (entry.getValue() == null || entry.getValue().size() == 0) {
                    failed.remove(entry.getKey());
                }
            }
            if (failed.size() > 0) {
                if (LOGGER.isInfoEnabled()) {
                    LOGGER.info("Retry subscribe " + failed);
                }
                try {
                    for (Map.Entry<Node, Set<NotifyListener>> entry : failed.entrySet()) {
                        Node node = entry.getKey();
                        Set<NotifyListener> listeners = entry.getValue();
                        for (NotifyListener listener : listeners) {
                            try {
                                doSubscribe(node, listener);
                                listeners.remove(listener);
                                failedSubscribed.remove(entry.getKey());
                            } catch (Throwable t) { // 忽略所有异常，等待下次重试
                                LOGGER.warn("Failed to retry subscribe " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                            }
                        }
                    }
                } catch (Throwable t) { // 忽略所有异常，等待下次重试
                    LOGGER.warn("Failed to retry subscribe " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                }
            }
        }
        if (!failedUnsubscribed.isEmpty()) {
            Map<Node, Set<NotifyListener>> failed = new HashMap<Node, Set<NotifyListener>>(failedUnsubscribed);
            for (Map.Entry<Node, Set<NotifyListener>> entry : new HashMap<Node, Set<NotifyListener>>(failed).entrySet()) {
                if (entry.getValue() == null || entry.getValue().size() == 0) {
                    failed.remove(entry.getKey());
                }
            }
            if (failed.size() > 0) {
                if (LOGGER.isInfoEnabled()) {
                    LOGGER.info("Retry unsubscribe " + failed);
                }
                try {
                    for (Map.Entry<Node, Set<NotifyListener>> entry : failed.entrySet()) {
                        Node node = entry.getKey();
                        Set<NotifyListener> listeners = entry.getValue();
                        for (NotifyListener listener : listeners) {
                            try {
                                doUnsubscribe(node, listener);
                                listeners.remove(listener);
                            } catch (Throwable t) { // 忽略所有异常，等待下次重试
                                LOGGER.warn("Failed to retry unsubscribe " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                            }
                        }
                    }
                } catch (Throwable t) { // 忽略所有异常，等待下次重试
                    LOGGER.warn("Failed to retry unsubscribe " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                }
            }
        }
        if (!failedNotified.isEmpty()) {
            Map<Node, Map<NotifyListener, NotifyPair<NotifyEvent, List<Node>>>> failed = new HashMap<Node, Map<NotifyListener, NotifyPair<NotifyEvent, List<Node>>>>(failedNotified);
            for (Map.Entry<Node, Map<NotifyListener, NotifyPair<NotifyEvent, List<Node>>>> entry : new HashMap<Node, Map<NotifyListener, NotifyPair<NotifyEvent, List<Node>>>>(failed).entrySet()) {
                if (entry.getValue() == null || entry.getValue().size() == 0) {
                    failed.remove(entry.getKey());
                }
            }
            if (failed.size() > 0) {
                if (LOGGER.isInfoEnabled()) {
                    LOGGER.info("Retry notify " + failed);
                }
                try {
                    for (Map<NotifyListener, NotifyPair<NotifyEvent, List<Node>>> values : failed.values()) {
                        for (Map.Entry<NotifyListener, NotifyPair<NotifyEvent, List<Node>>> entry : values.entrySet()) {
                            try {
                                NotifyListener listener = entry.getKey();
                                NotifyPair<NotifyEvent, List<Node>> notifyPair = entry.getValue();
                                listener.notify(notifyPair.event, notifyPair.nodes);
                                values.remove(listener);
                            } catch (Throwable t) { // 忽略所有异常，等待下次重试
                                LOGGER.warn("Failed to retry notify " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                            }
                        }
                    }
                } catch (Throwable t) { // 忽略所有异常，等待下次重试
                    LOGGER.warn("Failed to retry notify " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                }
            }
        }
    }

    private class NotifyPair<T1, T2> {
        T1 event;
        T2 nodes;

        public NotifyPair(T1 event, T2 nodes) {
            this.event = event;
            this.nodes = nodes;
        }
    }

    protected abstract void doRegister(Node node);

    protected abstract void doUnRegister(Node node);

    protected abstract void doSubscribe(Node node, NotifyListener listener);

    protected abstract void doUnsubscribe(Node node, NotifyListener listener);
}
