/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alipay.sofa.common.thread;

import com.alipay.sofa.common.thread.log.ThreadLogger;
import com.alipay.sofa.common.utils.StringUtil;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author <a href="mailto:[email protected]">Alaneuler</a>
 * Created on 2020/3/17
 */
public class ThreadPoolGovernor {
    public static String                                         CLASS_NAME         = ThreadPoolGovernor.class
                                                                                        .getCanonicalName();
    private static long                                          period             = 30;
    private static boolean                                       loggable           = false;

    private static ScheduledExecutorService                      scheduler          = Executors
                                                                                        .newScheduledThreadPool(
                                                                                            1,
                                                                                            new NamedThreadFactory(
                                                                                                "t.p.g"));
    private static ScheduledFuture<?>                            scheduledFuture;
    private static final Object                                  monitor            = new Object();
    private static GovernorInfoDumper                            governorInfoDumper = new GovernorInfoDumper();

    private static ConcurrentHashMap<String, ThreadPoolExecutor> registry           = new ConcurrentHashMap<String, ThreadPoolExecutor>();

    public synchronized static void startSchedule() {
        if (scheduledFuture == null) {
            scheduledFuture = scheduler.scheduleAtFixedRate(governorInfoDumper, period, period,
                TimeUnit.SECONDS);
            ThreadLogger.info("Started {} with period: {} SECONDS", CLASS_NAME, period);
        } else {
            ThreadLogger
                .warn("{} has already started with period: {} SECONDS.", CLASS_NAME, period);
        }
    }

    public synchronized static void stopSchedule() {
        if (scheduledFuture != null) {
            scheduledFuture.cancel(true);
            scheduledFuture = null;
            ThreadLogger.info("Stopped {}.", CLASS_NAME);
        } else {
            ThreadLogger.warn("{} is not scheduling!", CLASS_NAME);
        }
    }

    /**
     * Can also be used to manage JDK thread pool.
     * SofaThreadPoolExecutor should **not** call this method!
     * @param name thread pool name
     * @param threadPoolExecutor thread pool instance
     */
    public static void registerThreadPoolExecutor(String name, ThreadPoolExecutor threadPoolExecutor) {
        if (StringUtil.isEmpty(name)) {
            ThreadLogger.error("Rejected registering request of instance {} with empty name: {}.",
                threadPoolExecutor, name);
            return;
        }

        ThreadPoolExecutor existingExecutor = registry.putIfAbsent(name, threadPoolExecutor);
        if (existingExecutor != null) {
            ThreadLogger.error(
                "Rejected registering request of instance {} with duplicate name: {}",
                threadPoolExecutor, name);
        } else {
            ThreadLogger.info("Thread pool with name '{}' registered", name);
        }
    }

    public static void registerThreadPoolExecutor(SofaThreadPoolExecutor threadPoolExecutor) {
        registerThreadPoolExecutor(threadPoolExecutor.getThreadPoolName(), threadPoolExecutor);
    }

    public static void unregisterThreadPoolExecutor(String name) {
        registry.remove(name);
        ThreadLogger.info("Thread pool with name '{}' unregistered", name);
    }

    public static ThreadPoolExecutor getThreadPoolExecutor(String name) {
        return registry.get(name);
    }

    static class GovernorInfoDumper implements Runnable {
        @Override
        public void run() {
            try {
                if (loggable) {
                    for (Map.Entry<String, ThreadPoolExecutor> entry : registry.entrySet()) {
                        ThreadLogger.info("Thread pool '{}' exists with instance: {}",
                            entry.getKey(), entry.getValue());
                    }
                }
            } catch (Throwable e) {
                ThreadLogger.warn("{} is interrupted when running: {}", this, e);
            }
        }
    }

    public static long getPeriod() {
        return period;
    }

    public static void setPeriod(long period) {
        ThreadPoolGovernor.period = period;

        synchronized (monitor) {
            if (scheduledFuture != null) {
                scheduledFuture.cancel(true);
                scheduledFuture = scheduler.scheduleAtFixedRate(governorInfoDumper, period, period,
                    TimeUnit.SECONDS);
                ThreadLogger.info("Reschedule {} with period: {} SECONDS", CLASS_NAME, period);
            }
        }
    }

    public static boolean isLoggable() {
        return loggable;
    }

    public static void setLoggable(boolean loggable) {
        ThreadPoolGovernor.loggable = loggable;
    }
}