/*
 *  Copyright 2009-2016 Weibo, Inc.
 *
 *    Licensed 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.weibo.api.motan.rpc;

import com.weibo.api.motan.closable.Closable;
import com.weibo.api.motan.closable.ShutDownHook;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 
 * rpc call statistic.
 *
 * @author fishermen
 * @version V1.0 created at: 2013-5-23
 */

public class RpcStats {

    private static final String SEPERATOR_METHOD_AND_PARAM = "|";

    private static ConcurrentHashMap<String, StatInfo> serviceStat = new ConcurrentHashMap<String, RpcStats.StatInfo>();
    private static ConcurrentHashMap<String, ConcurrentHashMap<String, StatInfo>> methodStat =
            new ConcurrentHashMap<String, ConcurrentHashMap<String, StatInfo>>();

    private static ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(1);
    static{
        ShutDownHook.registerShutdownHook(new Closable() {
            @Override
            public void close() {
                if(!scheduledExecutor.isShutdown()){
                    scheduledExecutor.shutdown();
                }
            }
        });
    }
    /**
     * call before invoke the request
     * 
     * @param url
     * @param request
     */
    public static void beforeCall(URL url, Request request) {
        String uri = url.getUri();
        onBeforeCall(getServiceStat(uri));
        onBeforeCall(getMethodStat(uri, request.getMethodName(), request.getParamtersDesc()));
    }

    /**
     * call after invoke the request
     * 
     * @param url
     * @param request
     * @param success
     * @param procTimeMills
     */
    public static void afterCall(URL url, Request request, boolean success, long procTimeMills) {
        String uri = url.getUri();
        onAfterCall(getServiceStat(uri), success, procTimeMills);
        onAfterCall(getMethodStat(uri, request.getMethodName(), request.getParamtersDesc()), success, procTimeMills);
    }

    public static StatInfo getServiceStat(URL url) {
        return getServiceStat(url.getUri());
    }

    public static StatInfo getMethodStat(URL url, Request request) {
        return getMethodStat(url.getUri(), request.getMethodName(), request.getParamtersDesc());
    }

    private static StatInfo getServiceStat(String uri) {
        StatInfo stat = serviceStat.get(uri);
        if (stat == null) {
            stat = new StatInfo();
            serviceStat.putIfAbsent(uri, stat);
            stat = serviceStat.get(uri);
        }
        return stat;
    }

    private static StatInfo getMethodStat(String uri, String methodName, String methodParaDesc) {
        ConcurrentHashMap<String, StatInfo> sstats = methodStat.get(uri);
        if (sstats == null) {
            sstats = new ConcurrentHashMap<String, StatInfo>();
            methodStat.putIfAbsent(uri, sstats);
            sstats = methodStat.get(uri);
        }

        String methodNameAndParams = methodName + SEPERATOR_METHOD_AND_PARAM + methodParaDesc;
        StatInfo mstat = sstats.get(methodNameAndParams);
        if (mstat == null) {
            mstat = new StatInfo();
            sstats.putIfAbsent(methodNameAndParams, mstat);
            mstat = sstats.get(methodNameAndParams);
        }
        return mstat;
    }

    private static void onBeforeCall(StatInfo statInfo) {
        statInfo.activeCount.incrementAndGet();
    }

    private static void onAfterCall(StatInfo statInfo, boolean success, long procTimeMills) {
        statInfo.activeCount.decrementAndGet();
        if (!success) {
            statInfo.failCount.incrementAndGet();
        }
        statInfo.totalCountTime.inc(1, procTimeMills);
        statInfo.latestCountTime.inc(1, procTimeMills);
    }

    private static void startCleaner() {

    }

    private static void cleanLatestStat() {
        if (serviceStat.size() == 0) {
            return;
        }
        for (StatInfo si : serviceStat.values()) {
            si.resetLatestStat();
        }

    }

    public static class StatInfo {

        private AtomicInteger activeCount = new AtomicInteger();
        private AtomicLong failCount = new AtomicLong();
        private CountTime totalCountTime = new CountTime();
        private CountTime latestCountTime = new CountTime();

        public int getActiveCount() {
            return activeCount.get();
        }

        public long getFailCount() {
            return failCount.get();
        }

        public CountTime getTotalCountTime() {
            return totalCountTime;
        }

        public CountTime getLatestCountTime() {
            return latestCountTime;
        }

        public void resetLatestStat() {
            latestCountTime.reset();
        }
    }

    public static class CountTime {
        private AtomicLong count;
        private AtomicLong timeMills;

        public CountTime() {
            count = new AtomicLong();
            timeMills = new AtomicLong();
        }

        private void inc(int incCount, long incTimeMills) {
            count.getAndAdd(incCount);
            timeMills.getAndAdd(incTimeMills);
        }

        public long getCount() {
            return count.get();
        }

        public void reset() {
            count.set(0);
            timeMills.set(0);
        }

    }


}
