/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * 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.alibaba.druid.support.http.stat;

import com.alibaba.druid.support.profile.ProfileStat;

import java.util.Date;
import java.util.Map;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;

import static com.alibaba.druid.util.JdbcSqlStatUtils.get;

public class WebURIStat {
    private final String uri;

    private volatile int runningCount;
    private volatile int concurrentMax;
    private volatile long requestCount;
    private volatile long requestTimeNano;
    private volatile long requestTimeNanoMax;
    private volatile long requestTimeNanoMaxOccurTime;

    static final AtomicIntegerFieldUpdater<WebURIStat> runningCountUpdater = AtomicIntegerFieldUpdater.newUpdater(WebURIStat.class,
            "runningCount");
    static final AtomicIntegerFieldUpdater<WebURIStat> concurrentMaxUpdater = AtomicIntegerFieldUpdater.newUpdater(WebURIStat.class,
            "concurrentMax");
    static final AtomicLongFieldUpdater<WebURIStat> requestCountUpdater = AtomicLongFieldUpdater.newUpdater(WebURIStat.class,
            "requestCount");
    static final AtomicLongFieldUpdater<WebURIStat> requestTimeNanoUpdater = AtomicLongFieldUpdater.newUpdater(WebURIStat.class,
            "requestTimeNano");
    static final AtomicLongFieldUpdater<WebURIStat> requestTimeNanoMaxUpdater = AtomicLongFieldUpdater.newUpdater(WebURIStat.class,
            "requestTimeNanoMax");

    static final AtomicLongFieldUpdater<WebURIStat> requestTimeNanoMaxOccurTimeUpdater = AtomicLongFieldUpdater.newUpdater(WebURIStat.class,
            "requestTimeNanoMaxOccurTime");

    private volatile long jdbcFetchRowCount;
    private volatile long jdbcFetchRowPeak;                                                                                       // 单次请求读取行数的峰值
    static final AtomicLongFieldUpdater<WebURIStat> jdbcFetchRowCountUpdater = AtomicLongFieldUpdater.newUpdater(WebURIStat.class,
            "jdbcFetchRowCount");
    static final AtomicLongFieldUpdater<WebURIStat> jdbcFetchRowPeakUpdater = AtomicLongFieldUpdater.newUpdater(WebURIStat.class,
            "jdbcFetchRowPeak");

    private volatile long jdbcUpdateCount;
    private volatile long jdbcUpdatePeak;                                                                                         // 单次请求更新行数的峰值
    static final AtomicLongFieldUpdater<WebURIStat> jdbcUpdateCountUpdater = AtomicLongFieldUpdater.newUpdater(WebURIStat.class,
            "jdbcUpdateCount");
    static final AtomicLongFieldUpdater<WebURIStat> jdbcUpdatePeakUpdater = AtomicLongFieldUpdater.newUpdater(WebURIStat.class,
            "jdbcUpdatePeak");

    private volatile long jdbcExecuteCount;
    private volatile long jdbcExecuteErrorCount;
    private volatile long jdbcExecutePeak;                                                                                        // 单次请求执行SQL次数的峰值
    private volatile long jdbcExecuteTimeNano;
    static final AtomicLongFieldUpdater<WebURIStat> jdbcExecuteCountUpdater = AtomicLongFieldUpdater.newUpdater(WebURIStat.class,
            "jdbcExecuteCount");
    static final AtomicLongFieldUpdater<WebURIStat> jdbcExecuteErrorCountUpdater = AtomicLongFieldUpdater.newUpdater(WebURIStat.class,
            "jdbcExecuteErrorCount");
    static final AtomicLongFieldUpdater<WebURIStat> jdbcExecutePeakUpdater = AtomicLongFieldUpdater.newUpdater(WebURIStat.class,
            "jdbcExecutePeak");
    static final AtomicLongFieldUpdater<WebURIStat> jdbcExecuteTimeNanoUpdater = AtomicLongFieldUpdater.newUpdater(WebURIStat.class,
            "jdbcExecuteTimeNano");

    private volatile long jdbcCommitCount;
    private volatile long jdbcRollbackCount;
    static final AtomicLongFieldUpdater<WebURIStat> jdbcCommitCountUpdater = AtomicLongFieldUpdater.newUpdater(WebURIStat.class,
            "jdbcCommitCount");
    static final AtomicLongFieldUpdater<WebURIStat> jdbcRollbackCountUpdater = AtomicLongFieldUpdater.newUpdater(WebURIStat.class,
            "jdbcRollbackCount");

    private volatile long jdbcPoolConnectionOpenCount;
    private volatile long jdbcPoolConnectionCloseCount;
    static final AtomicLongFieldUpdater<WebURIStat> jdbcPoolConnectionOpenCountUpdater = AtomicLongFieldUpdater.newUpdater(WebURIStat.class,
            "jdbcPoolConnectionOpenCount");
    static final AtomicLongFieldUpdater<WebURIStat> jdbcPoolConnectionCloseCountUpdater = AtomicLongFieldUpdater.newUpdater(WebURIStat.class,
            "jdbcPoolConnectionCloseCount");

    private volatile long jdbcResultSetOpenCount;
    private volatile long jdbcResultSetCloseCount;

    private volatile long errorCount;

    private volatile long lastAccessTimeMillis = -1L;

    private volatile ProfileStat profiletat = new ProfileStat();

    static final AtomicLongFieldUpdater<WebURIStat> jdbcResultSetOpenCountUpdater = AtomicLongFieldUpdater.newUpdater(WebURIStat.class,
            "jdbcResultSetOpenCount");
    static final AtomicLongFieldUpdater<WebURIStat> jdbcResultSetCloseCountUpdater = AtomicLongFieldUpdater.newUpdater(WebURIStat.class,
            "jdbcResultSetCloseCount");
    static final AtomicLongFieldUpdater<WebURIStat> errorCountUpdater = AtomicLongFieldUpdater.newUpdater(WebURIStat.class,
            "errorCount");
    static final AtomicLongFieldUpdater<WebURIStat> lastAccessTimeMillisUpdater = AtomicLongFieldUpdater.newUpdater(WebURIStat.class,
            "lastAccessTimeMillis");

    private static final ThreadLocal<WebURIStat> currentLocal = new ThreadLocal<WebURIStat>();

    private volatile long histogram_0_1;
    private volatile long histogram_1_10;
    private volatile long histogram_10_100;
    private volatile long histogram_100_1000;
    private volatile int histogram_1000_10000;
    private volatile int histogram_10000_100000;
    private volatile int histogram_100000_1000000;
    private volatile int histogram_1000000_more;

    static final AtomicLongFieldUpdater<WebURIStat> histogram_0_1_Updater = AtomicLongFieldUpdater.newUpdater(WebURIStat.class,
            "histogram_0_1");
    static final AtomicLongFieldUpdater<WebURIStat> histogram_1_10_Updater = AtomicLongFieldUpdater.newUpdater(WebURIStat.class,
            "histogram_1_10");
    static final AtomicLongFieldUpdater<WebURIStat> histogram_10_100_Updater = AtomicLongFieldUpdater.newUpdater(WebURIStat.class,
            "histogram_10_100");
    static final AtomicLongFieldUpdater<WebURIStat> histogram_100_1000_Updater = AtomicLongFieldUpdater.newUpdater(WebURIStat.class,
            "histogram_100_1000");
    static final AtomicIntegerFieldUpdater<WebURIStat> histogram_1000_10000_Updater = AtomicIntegerFieldUpdater.newUpdater(WebURIStat.class,
            "histogram_1000_10000");
    static final AtomicIntegerFieldUpdater<WebURIStat> histogram_10000_100000_Updater = AtomicIntegerFieldUpdater.newUpdater(WebURIStat.class,
            "histogram_10000_100000");
    static final AtomicIntegerFieldUpdater<WebURIStat> histogram_100000_1000000_Updater = AtomicIntegerFieldUpdater.newUpdater(WebURIStat.class,
            "histogram_100000_1000000");
    static final AtomicIntegerFieldUpdater<WebURIStat> histogram_1000000_more_Updater = AtomicIntegerFieldUpdater.newUpdater(WebURIStat.class,
            "histogram_1000000_more");

    public WebURIStat(String uri) {
        super();
        this.uri = uri;
    }

    public static WebURIStat current() {
        return currentLocal.get();
    }

    public String getUri() {
        return uri;
    }

    public void beforeInvoke() {
        currentLocal.set(this);

        int running = runningCountUpdater.incrementAndGet(this);

        for (; ; ) {
            int max = concurrentMaxUpdater.get(this);
            if (running > max) {
                if (concurrentMaxUpdater.compareAndSet(this, max, running)) {
                    break;
                }
            } else {
                break;
            }
        }

        requestCountUpdater.incrementAndGet(this);

        WebRequestStat requestStat = WebRequestStat.current();
        if (requestStat != null) {
            this.setLastAccessTimeMillis(requestStat.getStartMillis());
        }
    }

    public void afterInvoke(Throwable error, long nanos) {
        runningCountUpdater.decrementAndGet(this);
        requestTimeNanoUpdater.addAndGet(this, nanos);

        for (; ; ) {
            long current = requestTimeNanoMaxUpdater.get(this);
            if (current < nanos) {
                if (requestTimeNanoMaxUpdater.compareAndSet(this, current, nanos)) {
                    // 可能不准确，但是绝大多数情况下都会正确，性能换取一致性
                    requestTimeNanoMaxOccurTime = System.currentTimeMillis();

                    break;
                } else {
                    continue;
                }
            } else {
                break;
            }
        }

        histogramRecord(nanos);

        if (error != null) {
            errorCountUpdater.incrementAndGet(this);
        }

        {
            WebRequestStat localStat = WebRequestStat.current();
            if (localStat != null) {
                {
                    long fetchRowCount = localStat.getJdbcFetchRowCount();
                    this.addJdbcFetchRowCount(fetchRowCount);

                    for (; ; ) {
                        long peak = jdbcFetchRowPeakUpdater.get(this);
                        if (fetchRowCount <= peak) {
                            break;
                        }

                        if (jdbcFetchRowPeakUpdater.compareAndSet(this, peak, fetchRowCount)) {
                            break;
                        }
                    }
                }
                {
                    long executeCount = localStat.getJdbcExecuteCount();
                    this.addJdbcExecuteCount(executeCount);

                    for (; ; ) {
                        long peak = jdbcExecutePeakUpdater.get(this);
                        if (executeCount <= peak) {
                            break;
                        }

                        if (jdbcExecutePeakUpdater.compareAndSet(this, peak, executeCount)) {
                            break;
                        }
                    }
                }
                {
                    long updateCount = localStat.getJdbcUpdateCount();
                    this.addJdbcUpdateCount(updateCount);

                    for (; ; ) {
                        long peak = jdbcUpdatePeakUpdater.get(this);
                        if (updateCount <= peak) {
                            break;
                        }

                        if (jdbcUpdatePeakUpdater.compareAndSet(this, peak, updateCount)) {
                            break;
                        }
                    }
                }

                jdbcExecuteErrorCountUpdater.addAndGet(this, localStat.getJdbcExecuteErrorCount());
                jdbcExecuteTimeNanoUpdater.addAndGet(this, localStat.getJdbcExecuteTimeNano());

                this.addJdbcPoolConnectionOpenCount(localStat.getJdbcPoolConnectionOpenCount());
                this.addJdbcPoolConnectionCloseCount(localStat.getJdbcPoolConnectionCloseCount());

                this.addJdbcResultSetOpenCount(localStat.getJdbcResultSetOpenCount());
                this.addJdbcResultSetCloseCount(localStat.getJdbcResultSetCloseCount());
            }
        }

        currentLocal.set(null);
    }

    private void histogramRecord(long nanos) {
        final long millis = nanos / 1000 / 1000;

        if (millis < 1) {
            histogram_0_1_Updater.incrementAndGet(this);
        } else if (millis < 10) {
            histogram_1_10_Updater.incrementAndGet(this);
        } else if (millis < 100) {
            histogram_10_100_Updater.incrementAndGet(this);
        } else if (millis < 1000) {
            histogram_100_1000_Updater.incrementAndGet(this);
        } else if (millis < 10000) {
            histogram_1000_10000_Updater.incrementAndGet(this);
        } else if (millis < 100000) {
            histogram_10000_100000_Updater.incrementAndGet(this);
        } else if (millis < 1000000) {
            histogram_100000_1000000_Updater.incrementAndGet(this);
        } else {
            histogram_1000000_more_Updater.incrementAndGet(this);
        }
    }

    public int getRunningCount() {
        return this.runningCount;
    }

    public long getConcurrentMax() {
        return concurrentMax;
    }

    public long getRequestCount() {
        return requestCount;
    }

    public long getRequestTimeNano() {
        return requestTimeNano;
    }

    public long getRequestTimeMillis() {
        return getRequestTimeNano() / (1000 * 1000);
    }

    public void addJdbcFetchRowCount(long delta) {
        jdbcFetchRowCountUpdater.addAndGet(this, delta);
    }

    public long getJdbcFetchRowCount() {
        return jdbcFetchRowCount;
    }

    public long getJdbcFetchRowPeak() {
        return jdbcFetchRowPeak;
    }

    public void addJdbcUpdateCount(long updateCount) {
        jdbcUpdateCountUpdater.addAndGet(this, updateCount);
    }

    public long getJdbcUpdateCount() {
        return jdbcUpdateCount;
    }

    public long getJdbcUpdatePeak() {
        return jdbcUpdatePeak;
    }

    public void incrementJdbcExecuteCount() {
        jdbcExecuteCountUpdater.incrementAndGet(this);
    }

    public void addJdbcExecuteCount(long executeCount) {
        jdbcExecuteCountUpdater.addAndGet(this, executeCount);
    }

    public long getJdbcExecuteCount() {
        return jdbcExecuteCount;
    }

    public long getJdbcExecuteErrorCount() {
        return jdbcExecuteErrorCount;
    }

    public long getJdbcExecutePeak() {
        return jdbcExecutePeak;
    }

    public long getJdbcExecuteTimeMillis() {
        return getJdbcExecuteTimeNano() / (1000 * 1000);
    }

    public long getJdbcExecuteTimeNano() {
        return jdbcExecuteTimeNano;
    }

    public void incrementJdbcCommitCount() {
        jdbcCommitCountUpdater.incrementAndGet(this);
    }

    public long getJdbcCommitCount() {
        return jdbcCommitCount;
    }

    public void incrementJdbcRollbackCount() {
        jdbcRollbackCountUpdater.incrementAndGet(this);
    }

    public long getJdbcRollbackCount() {
        return jdbcRollbackCount;
    }

    public void setLastAccessTimeMillis(long lastAccessTimeMillis) {
        this.lastAccessTimeMillis = lastAccessTimeMillis;
    }

    public Date getLastAccessTime() {
        if (lastAccessTimeMillis < 0L) {
            return null;
        }

        return new Date(lastAccessTimeMillis);
    }

    public long getLastAccessTimeMillis() {
        return lastAccessTimeMillis;
    }

    public long getErrorCount() {
        return errorCount;
    }

    public long getJdbcPoolConnectionOpenCount() {
        return jdbcPoolConnectionOpenCount;
    }

    public void addJdbcPoolConnectionOpenCount(long delta) {
        jdbcPoolConnectionOpenCountUpdater.addAndGet(this, delta);
    }

    public void incrementJdbcPoolConnectionOpenCount() {
        jdbcPoolConnectionOpenCountUpdater.incrementAndGet(this);
    }

    public long getJdbcPoolConnectionCloseCount() {
        return jdbcPoolConnectionCloseCount;
    }

    public void addJdbcPoolConnectionCloseCount(long delta) {
        jdbcPoolConnectionCloseCountUpdater.addAndGet(this, delta);
    }

    public void incrementJdbcPoolConnectionCloseCount() {
        jdbcPoolConnectionCloseCountUpdater.incrementAndGet(this);
    }

    public long getJdbcResultSetOpenCount() {
        return jdbcResultSetOpenCount;
    }

    public void addJdbcResultSetOpenCount(long delta) {
        jdbcResultSetOpenCountUpdater.addAndGet(this, delta);
    }

    public long getJdbcResultSetCloseCount() {
        return jdbcResultSetCloseCount;
    }

    public void addJdbcResultSetCloseCount(long delta) {
        jdbcResultSetCloseCountUpdater.addAndGet(this, delta);
    }

    public ProfileStat getProfiletat() {
        return profiletat;
    }

    public long[] getHistogramValues() {
        return new long[]{
                //
                histogram_0_1, //
                histogram_1_10, //
                histogram_10_100, //
                histogram_100_1000, //
                histogram_1000_10000, //
                histogram_10000_100000, //
                histogram_100000_1000000, //
                histogram_1000000_more //
        };
    }

    public WebURIStatValue getValue(boolean reset) {
        WebURIStatValue val = new WebURIStatValue();

        val.setUri(uri);

        val.setRunningCount(runningCount);
        val.setConcurrentMax(get(this, concurrentMaxUpdater, reset));
        val.setRequestCount(get(this, requestCountUpdater, reset));
        val.setRequestTimeNano(get(this, requestTimeNanoUpdater, reset));
        val.setRequestTimeNanoMax(get(this, requestTimeNanoMaxUpdater, reset));
        val.setRequestTimeNanoMaxOccurTime(get(this, requestTimeNanoMaxOccurTimeUpdater, reset));

        val.setJdbcFetchRowCount(get(this, jdbcFetchRowCountUpdater, reset));
        val.setJdbcFetchRowPeak(get(this, jdbcFetchRowPeakUpdater, reset));

        val.setJdbcUpdateCount(get(this, jdbcUpdateCountUpdater, reset));
        val.setJdbcUpdatePeak(get(this, jdbcUpdatePeakUpdater, reset));

        val.setJdbcExecuteCount(get(this, jdbcExecuteCountUpdater, reset));
        val.setJdbcExecuteErrorCount(get(this, jdbcExecuteErrorCountUpdater, reset));
        val.setJdbcExecutePeak(get(this, jdbcExecutePeakUpdater, reset));
        val.setJdbcExecuteTimeNano(get(this, jdbcExecuteTimeNanoUpdater, reset));

        val.setJdbcCommitCount(get(this, jdbcCommitCountUpdater, reset));
        val.setJdbcRollbackCount(get(this, jdbcRollbackCountUpdater, reset));

        val.setJdbcPoolConnectionOpenCount(get(this, jdbcPoolConnectionOpenCountUpdater, reset));
        val.setJdbcPoolConnectionCloseCount(get(this, jdbcPoolConnectionCloseCountUpdater, reset));

        val.setJdbcResultSetOpenCount(get(this, jdbcResultSetOpenCountUpdater, reset));
        val.setJdbcResultSetCloseCount(get(this, jdbcResultSetCloseCountUpdater, reset));

        val.setErrorCount(get(this, errorCountUpdater, reset));

        val.setLastAccessTimeMillis(get(this, lastAccessTimeMillisUpdater, reset));

        val.setProfileEntryStatValueList(this.getProfiletat().getStatValue(reset));

        val.histogram_0_1 = get(this, histogram_0_1_Updater, reset);
        val.histogram_1_10 = get(this, histogram_1_10_Updater, reset);
        val.histogram_10_100 = get(this, histogram_10_100_Updater, reset);
        val.histogram_100_1000 = get(this, histogram_100_1000_Updater, reset);
        val.histogram_1000_10000 = get(this, histogram_1000_10000_Updater, reset);
        val.histogram_10000_100000 = get(this, histogram_10000_100000_Updater, reset);
        val.histogram_100000_1000000 = get(this, histogram_100000_1000000_Updater, reset);
        val.histogram_1000000_more = get(this, histogram_1000000_more_Updater, reset);

        return val;
    }

    public Map<String, Object> getStatData() {
        return getValue(false).getStatData();
    }
}
