/*
 * 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.logging.Log;
import com.alibaba.druid.support.logging.LogFactory;
import com.alibaba.druid.util.LRUCache;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

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

public class WebAppStat {
    private static final Log LOG = LogFactory.getLog(WebAppStat.class);

    public static final int DEFAULT_MAX_STAT_URI_COUNT = 1000;
    public static final int DEFAULT_MAX_STAT_SESSION_COUNT = 1000;

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

    private volatile int maxStatUriCount = DEFAULT_MAX_STAT_URI_COUNT;
    private volatile int maxStatSessionCount = DEFAULT_MAX_STAT_SESSION_COUNT;

    private final AtomicInteger runningCount = new AtomicInteger();
    private final AtomicInteger concurrentMax = new AtomicInteger();
    private final AtomicLong requestCount = new AtomicLong(0);
    private final AtomicLong sessionCount = new AtomicLong(0);

    private final AtomicLong jdbcFetchRowCount = new AtomicLong();
    private final AtomicLong jdbcUpdateCount = new AtomicLong();
    private final AtomicLong jdbcExecuteCount = new AtomicLong();
    private final AtomicLong jdbcExecuteTimeNano = new AtomicLong();

    private final AtomicLong jdbcCommitCount = new AtomicLong();
    private final AtomicLong jdbcRollbackCount = new AtomicLong();

    private final ConcurrentMap<String, WebURIStat> uriStatMap = new ConcurrentHashMap<String, WebURIStat>(
            16,
            0.75f,
            1);
    private final LRUCache<String, WebSessionStat> sessionStatMap;

    private final ReadWriteLock sessionStatLock = new ReentrantReadWriteLock();

    private final AtomicLong uriStatMapFullCount = new AtomicLong();
    private final AtomicLong uriSessionMapFullCount = new AtomicLong();

    private final AtomicLong osMacOSXCount = new AtomicLong(0);
    private final AtomicLong osWindowsCount = new AtomicLong(0);
    private final AtomicLong osLinuxCount = new AtomicLong(0);
    private final AtomicLong osSymbianCount = new AtomicLong(0);
    private final AtomicLong osFreeBSDCount = new AtomicLong(0);
    private final AtomicLong osOpenBSDCount = new AtomicLong(0);
    private final AtomicLong osAndroidCount = new AtomicLong(0);

    private final AtomicLong osWindows98Count = new AtomicLong();
    private final AtomicLong osWindowsXPCount = new AtomicLong();
    private final AtomicLong osWindows2000Count = new AtomicLong();
    private final AtomicLong osWindowsVistaCount = new AtomicLong();
    private final AtomicLong osWindows7Count = new AtomicLong();
    private final AtomicLong osWindows8Count = new AtomicLong();

    private final AtomicLong osAndroid15Count = new AtomicLong(0);
    private final AtomicLong osAndroid16Count = new AtomicLong(0);
    private final AtomicLong osAndroid20Count = new AtomicLong(0);
    private final AtomicLong osAndroid21Count = new AtomicLong(0);
    private final AtomicLong osAndroid22Count = new AtomicLong(0);
    private final AtomicLong osAndroid23Count = new AtomicLong(0);
    private final AtomicLong osAndroid30Count = new AtomicLong(0);
    private final AtomicLong osAndroid31Count = new AtomicLong(0);
    private final AtomicLong osAndroid32Count = new AtomicLong(0);
    private final AtomicLong osAndroid40Count = new AtomicLong(0);
    private final AtomicLong osAndroid41Count = new AtomicLong(0);
    private final AtomicLong osAndroid42Count = new AtomicLong(0);
    private final AtomicLong osAndroid43Count = new AtomicLong(0);

    private final AtomicLong osLinuxUbuntuCount = new AtomicLong(0);

    private final AtomicLong browserIECount = new AtomicLong(0);
    private final AtomicLong browserFirefoxCount = new AtomicLong(0);
    private final AtomicLong browserChromeCount = new AtomicLong(0);
    private final AtomicLong browserSafariCount = new AtomicLong(0);
    private final AtomicLong browserOperaCount = new AtomicLong(0);

    private final AtomicLong browserIE5Count = new AtomicLong(0);
    private final AtomicLong browserIE6Count = new AtomicLong(0);
    private final AtomicLong browserIE7Count = new AtomicLong(0);
    private final AtomicLong browserIE8Count = new AtomicLong(0);
    private final AtomicLong browserIE9Count = new AtomicLong(0);
    private final AtomicLong browserIE10Count = new AtomicLong(0);

    private final AtomicLong browser360SECount = new AtomicLong(0);

    private final AtomicLong deviceAndroidCount = new AtomicLong(0);
    private final AtomicLong deviceIpadCount = new AtomicLong(0);
    private final AtomicLong deviceIphoneCount = new AtomicLong(0);
    private final AtomicLong deviceWindowsPhoneCount = new AtomicLong(0);

    private final AtomicLong botCount = new AtomicLong();
    private final AtomicLong botBaiduCount = new AtomicLong();
    private final AtomicLong botYoudaoCount = new AtomicLong();
    private final AtomicLong botGoogleCount = new AtomicLong();
    private final AtomicLong botMsnCount = new AtomicLong();
    private final AtomicLong botBingCount = new AtomicLong();
    private final AtomicLong botSosoCount = new AtomicLong();
    private final AtomicLong botSogouCount = new AtomicLong();
    private final AtomicLong botYahooCount = new AtomicLong();

    private String contextPath;

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

    public void reset() {
        concurrentMax.set(0);
        requestCount.set(0);
        requestCount.set(0);
        sessionCount.set(0);

        jdbcFetchRowCount.set(0);
        jdbcUpdateCount.set(0);
        jdbcExecuteCount.set(0);
        jdbcExecuteTimeNano.set(0);
        jdbcCommitCount.set(0);
        jdbcRollbackCount.set(0);

        sessionStatLock.readLock().lock();
        try {
            Iterator<Map.Entry<String, WebSessionStat>> iter = sessionStatMap.entrySet().iterator();
            while (iter.hasNext()) {
                Map.Entry<String, WebSessionStat> entry = iter.next();
                entry.getValue().reset();
            }
            sessionStatMap.clear();
        } finally {
            sessionStatLock.readLock().unlock();
        }

        uriStatMap.clear();

        uriStatMapFullCount.set(0);
        uriSessionMapFullCount.set(0);

        osMacOSXCount.set(0);
        osWindowsCount.set(0);
        osLinuxCount.set(0);
        osSymbianCount.set(0);
        osOpenBSDCount.set(0);
        osFreeBSDCount.set(0);
        osAndroidCount.set(0);

        osWindows98Count.set(0);
        osWindowsXPCount.set(0);
        osWindows2000Count.set(0);
        osWindowsVistaCount.set(0);
        osWindows7Count.set(0);
        osWindows8Count.set(0);

        osLinuxUbuntuCount.set(0);

        osAndroid15Count.set(0);
        osAndroid16Count.set(0);
        osAndroid20Count.set(0);
        osAndroid21Count.set(0);
        osAndroid22Count.set(0);
        osAndroid23Count.set(0);
        osAndroid30Count.set(0);
        osAndroid31Count.set(0);
        osAndroid32Count.set(0);
        osAndroid40Count.set(0);
        osAndroid41Count.set(0);
        osAndroid42Count.set(0);
        osAndroid43Count.set(0);

        browserIE6Count.set(0);
        browserIE7Count.set(0);
        browserIE8Count.set(0);
        browserIE9Count.set(0);
        browserIE10Count.set(0);

        browserIECount.set(0);
        browserFirefoxCount.set(0);
        browserChromeCount.set(0);
        browserSafariCount.set(0);
        browserOperaCount.set(0);

        browser360SECount.set(0);

        deviceAndroidCount.set(0);
        deviceIpadCount.set(0);
        deviceIphoneCount.set(0);
        deviceWindowsPhoneCount.set(0);
    }

    public WebAppStat() {
        this(null);
    }

    public WebAppStat(String contextPath) {
        this(contextPath, DEFAULT_MAX_STAT_SESSION_COUNT);
    }

    public WebAppStat(String contextPath, int maxStatSessionCount) {
        this.contextPath = contextPath;
        this.maxStatSessionCount = maxStatSessionCount;

        sessionStatMap = new LRUCache<String, WebSessionStat>(maxStatSessionCount);
    }

    public String getContextPath() {
        return contextPath;
    }

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

        int running = runningCount.incrementAndGet();

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

        requestCount.incrementAndGet();
    }

    public WebURIStat getURIStat(String uri) {
        return getURIStat(uri, false);
    }

    public WebURIStat getURIStat(String uri, boolean create) {
        WebURIStat uriStat = uriStatMap.get(uri);

        if (uriStat != null) {
            return uriStat;
        }

        if (!create) {
            return null;
        }

        if (uriStatMap.size() >= this.getMaxStatUriCount()) {
            long fullCount = uriStatMapFullCount.getAndIncrement();

            if (fullCount == 0) {
                LOG.error("uriSessionMapFullCount is full");
            }

            return null;
        }

        uriStatMap.putIfAbsent(uri, new WebURIStat(uri));
        uriStat = uriStatMap.get(uri);

        return uriStat;
    }

    public WebSessionStat getSessionStat(String sessionId) {
        return getSessionStat(sessionId, false);
    }

    public Map<String, Object> getSessionStatData(String sessionId) {
        WebSessionStat sessionStat = sessionStatMap.get(sessionId);

        if (sessionStat == null) {
            return null;
        }

        return sessionStat.getStatData();
    }

    public Map<String, Object> getURIStatData(String uri) {
        WebURIStat uriStat = getURIStat(uri);

        if (uriStat == null) {
            return null;
        }

        return uriStat.getStatData();
    }

    public WebSessionStat getSessionStat(String sessionId, boolean create) {
        sessionStatLock.readLock().lock();
        try {
            WebSessionStat uriStat = sessionStatMap.get(sessionId);

            if (uriStat != null) {
                return uriStat;
            }
        } finally {
            sessionStatLock.readLock().unlock();
        }

        if (!create) {
            return null;
        }

        sessionStatLock.writeLock().lock();
        try {
            WebSessionStat uriStat = sessionStatMap.get(sessionId);

            if (uriStat == null) {
                if (sessionStatMap.size() >= this.getMaxStatSessionCount()) {
                    long fullCount = uriSessionMapFullCount.getAndIncrement();

                    if (fullCount == 0) {
                        LOG.warn("sessionStatMap is full");
                    }
                }

                WebSessionStat newStat = new WebSessionStat(sessionId);

                sessionStatMap.put(sessionId, newStat);

                return newStat;
            }

            return uriStat;
        } finally {
            sessionStatLock.writeLock().unlock();
        }
    }

    public void afterInvoke(Throwable error, long nanoSpan) {
        runningCount.decrementAndGet();
        currentLocal.set(null);

        WebRequestStat requestStat = WebRequestStat.current();
        if (requestStat != null) {
            this.addJdbcExecuteCount(requestStat.getJdbcExecuteCount());
            this.addJdbcFetchRowCount(requestStat.getJdbcFetchRowCount());
            this.addJdbcUpdateCount(requestStat.getJdbcUpdateCount());
            this.addJdbcCommitCount(requestStat.getJdbcCommitCount());
            this.addJdbcRollbackCount(requestStat.getJdbcRollbackCount());
            this.addJdbcExecuteTimeNano(requestStat.getJdbcExecuteTimeNano());
        }
    }

    public void incrementSessionCount() {
        sessionCount.incrementAndGet();
    }

    public long getSessionCount() {
        return sessionCount.get();
    }

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

    public long getJdbcFetchRowCount() {
        return jdbcFetchRowCount.get();
    }

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

    public long getJdbcUpdateCount() {
        return jdbcUpdateCount.get();
    }

    public void incrementJdbcExecuteCount() {
        jdbcExecuteCount.incrementAndGet();
    }

    public void addJdbcExecuteCount(long executeCount) {
        jdbcExecuteCount.addAndGet(executeCount);
    }

    public long getJdbcExecuteCount() {
        return jdbcExecuteCount.get();
    }

    public long getJdbcExecuteTimeNano() {
        return jdbcExecuteTimeNano.get();
    }

    public void addJdbcExecuteTimeNano(long nano) {
        jdbcExecuteTimeNano.addAndGet(nano);
    }

    public void incrementJdbcCommitCount() {
        jdbcCommitCount.incrementAndGet();
    }

    public long getJdbcCommitCount() {
        return jdbcCommitCount.get();
    }

    public void addJdbcCommitCount(long commitCount) {
        this.jdbcCommitCount.addAndGet(commitCount);
    }

    public void incrementJdbcRollbackCount() {
        jdbcRollbackCount.incrementAndGet();
    }

    public long getJdbcRollbackCount() {
        return jdbcRollbackCount.get();
    }

    public void addJdbcRollbackCount(long rollbackCount) {
        this.jdbcRollbackCount.addAndGet(rollbackCount);
    }

    public int getMaxStatUriCount() {
        return maxStatUriCount;
    }

    public void setMaxStatUriCount(int maxStatUriCount) {
        this.maxStatUriCount = maxStatUriCount;
    }

    public int getMaxStatSessionCount() {
        return maxStatSessionCount;
    }

    public void setMaxStatSessionCount(int maxStatSessionCount) {
        this.maxStatSessionCount = maxStatSessionCount;
    }

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

    public long getConcurrentMax() {
        return concurrentMax.get();
    }

    public long getRequestCount() {
        return requestCount.get();
    }

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

    public List<WebURIStatValue> getURIStatValueList(boolean reset) {
        List<WebURIStatValue> list = new ArrayList<WebURIStatValue>(this.uriStatMap.size());

        for (WebURIStat uriStat : this.uriStatMap.values()) {
            WebURIStatValue statValue = uriStat.getValue(reset);

            if (statValue.getRunningCount() == 0 && statValue.getRequestCount() == 0) {
                continue;
            }
            list.add(statValue);
        }

        return list;
    }

    public List<Map<String, Object>> getURIStatDataList() {
        List<Map<String, Object>> uriStatDataList = new ArrayList<Map<String, Object>>(this.uriStatMap.size());
        for (WebURIStat uriStat : this.uriStatMap.values()) {
            Map<String, Object> uriStatData = uriStat.getStatData();

            int runningCount = ((Number) uriStatData.get("RunningCount")).intValue();
            long requestCount = (Long) uriStatData.get("RequestCount");

            if (runningCount == 0 && requestCount == 0) {
                continue;
            }

            uriStatDataList.add(uriStatData);
        }
        return uriStatDataList;
    }

    public List<Map<String, Object>> getSessionStatDataList() {
        Lock lock = sessionStatLock.readLock();
        lock.lock();
        try {
            List<Map<String, Object>> sessionStatDataList = new ArrayList<Map<String, Object>>(this.sessionStatMap.size());
            for (WebSessionStat sessionStat : this.sessionStatMap.values()) {
                Map<String, Object> sessionStatData = sessionStat.getStatData();

                int runningCount = ((Number) sessionStatData.get("RunningCount")).intValue();
                long requestCount = (Long) sessionStatData.get("RequestCount");

                if (runningCount == 0 && requestCount == 0) {
                    continue;
                }

                sessionStatDataList.add(sessionStatData);
            }
            return sessionStatDataList;
        } finally {
            lock.unlock();
        }
    }

    public void computeUserAgent(String userAgent) {
        if (userAgent == null || userAgent.length() == 0) {
            return;
        }

        // Mozilla/5.0 (compatible;
        final int MOZILLA_COMPATIBLE_OFFSET = 25;

        boolean is360SE = userAgent.endsWith("360SE)");

        if (is360SE) {
            browser360SECount.incrementAndGet();
        }

        boolean isIE = userAgent.startsWith("MSIE", MOZILLA_COMPATIBLE_OFFSET);
        int iePrefixIndex = 30; // "Mozilla/5.0 (compatible; MSIE ".length();

        boolean isGoogleToolbar = false;

        if (!isIE) {
            isGoogleToolbar = userAgent.startsWith("GoogleToolbar", MOZILLA_COMPATIBLE_OFFSET);
            if (isGoogleToolbar) {
                // MSIE
                int tmp = userAgent.indexOf("IE ");
                if (tmp != -1) {
                    isIE = true;
                    iePrefixIndex = tmp + 3;
                }
            }
        }

        if (isIE) {
            browserIECount.incrementAndGet();

            char v1 = ' ', v2 = ' ';
            if (userAgent.length() > iePrefixIndex + 1) {
                v1 = userAgent.charAt(iePrefixIndex);
                v2 = userAgent.charAt(iePrefixIndex + 1);
            } else if (userAgent.length() > iePrefixIndex) {
                v1 = userAgent.charAt(iePrefixIndex);
            }

            switch (v1) {
                case '5':
                    browserIE5Count.incrementAndGet();
                    break;
                case '6':
                    browserIE6Count.incrementAndGet();
                    break;
                case '7':
                    browserIE7Count.incrementAndGet();
                    break;
                case '8':
                    browserIE8Count.incrementAndGet();
                    break;
                case '9':
                    browserIE9Count.incrementAndGet();
                    break;
                case '1':
                    if (v2 == '0') {
                        browserIE10Count.incrementAndGet();
                    }
                    break;
                default:
                    break;
            }

            osWindowsCount.incrementAndGet();

            computeUserAgentIEWindowsVersion(userAgent);

            if (userAgent.contains("Windows Phone")) {
                deviceWindowsPhoneCount.incrementAndGet();
            }

            return;
        }

        boolean isWindows = false;
        boolean isMac = false;
        boolean isIpad = false;
        boolean isIPhone = false;
        boolean isLinux = false;
        boolean isX11 = false;
        boolean isBSD = false;

        if (userAgent.startsWith("Windows", 13)) {
            isWindows = true;
        } else if (userAgent.startsWith("Macintosh", 13)) {
            isMac = true;
        } else if (userAgent.startsWith("iPad", 13)) {
            isIpad = true;
            isMac = true;
        } else if (userAgent.startsWith("iPhone", 13)) {
            isIPhone = true;
            isMac = true;
        } else if (userAgent.startsWith("Linux", 13)) {
            isLinux = true;
        } else if (userAgent.startsWith("X11", 13)) {
            isX11 = true;
        }

        boolean isAndroid = false;

        if (isWindows) {
            isWindows = true;

            osWindowsCount.incrementAndGet();

            if (userAgent.contains("Windows Phone")) {
                deviceWindowsPhoneCount.incrementAndGet();
            }
        } else if (isMac) {
            isMac = true;
            osMacOSXCount.incrementAndGet();
            if (isIpad && userAgent.contains("iPad")) {
                deviceIpadCount.incrementAndGet();
            } else if (isIPhone || userAgent.contains("iPhone")) {
                deviceIphoneCount.incrementAndGet();
            }
        } else if (isLinux) {
            osLinuxCount.incrementAndGet();
            isAndroid = computeUserAgentAndroid(userAgent);
        } else if (userAgent.contains("Symbian")) {
            osSymbianCount.incrementAndGet();
        } else if (userAgent.contains("Ubuntu")) {
            osLinuxCount.incrementAndGet();
            osLinuxUbuntuCount.incrementAndGet();
            isLinux = true;
        }

        if (isX11) {
            if (userAgent.contains("OpenBSD")) {
                osOpenBSDCount.incrementAndGet();
                isBSD = true;
            } else if (userAgent.contains("FreeBSD")) {
                osFreeBSDCount.incrementAndGet();
                isBSD = true;
            } else if ((!isLinux) && userAgent.contains("Linux")) {
                osLinuxCount.incrementAndGet();
                isLinux = true;
            }
        }

        boolean isOpera = userAgent.startsWith("Opera");

        if (isOpera) {
            if (userAgent.contains("Windows")) {
                osWindowsCount.incrementAndGet();
            } else if (userAgent.contains("Linux")) {
                osWindowsCount.incrementAndGet();
            } else if (userAgent.contains("Macintosh")) {
                osMacOSXCount.incrementAndGet();
            }
            browserOperaCount.incrementAndGet();
            return;
        }

        if (isWindows) {
            computeUserAgentFirefoxWindowsVersion(userAgent);
        }

        if (isWindows || isMac || isLinux || isBSD) {
            if (userAgent.contains("Chrome")) {
                browserChromeCount.incrementAndGet();
                return;
            }

            if ((!isAndroid) && userAgent.contains("Safari")) {
                browserSafariCount.incrementAndGet();
                return;
            }

            if (userAgent.contains("Firefox")) {
                browserFirefoxCount.incrementAndGet();
                return;
            }
        }

        if (userAgent.startsWith("User-Agent: ")) {
            String rest = userAgent.substring("User-Agent: ".length());
            computeUserAgent(rest);
        }

        boolean isJava = userAgent.startsWith("Java");

        if (isJava) {
            botCount.incrementAndGet();
        }

        if (userAgent.startsWith("msnbot")) {
            botCount.incrementAndGet();
            botMsnCount.incrementAndGet();
        } else if (userAgent.startsWith("Sosospider+")) {
            botCount.incrementAndGet();
            botSosoCount.incrementAndGet();
        } else if (userAgent.startsWith("Sogou")) {
            botCount.incrementAndGet();
            botSogouCount.incrementAndGet();
        } else if (userAgent.startsWith("HuaweiSymantecSpider")) {
            botCount.incrementAndGet();
        } else if (userAgent.startsWith("Yeti/")) {
            botCount.incrementAndGet();
        } else if (userAgent.startsWith("mahonie")) {
            botCount.incrementAndGet();
        } else if (userAgent.startsWith("findlinks")) {
            botCount.incrementAndGet();
        } else if (userAgent.startsWith("Updownerbot")) {
            botCount.incrementAndGet();
        } else if (userAgent.startsWith("DoCoMo/")) {
            botCount.incrementAndGet();
        } else if (userAgent.startsWith("Crawl")) {
            botCount.incrementAndGet();
        } else if (userAgent.startsWith("SkimBot")) {
            botCount.incrementAndGet();

        } else if (userAgent.startsWith("YoudaoBot", MOZILLA_COMPATIBLE_OFFSET)) {
            botCount.incrementAndGet();
            botYoudaoCount.incrementAndGet();
        } else if (userAgent.startsWith("bingbot", MOZILLA_COMPATIBLE_OFFSET)) {
            botCount.incrementAndGet();
            botBingCount.incrementAndGet();
        } else if (userAgent.startsWith("Googlebot", MOZILLA_COMPATIBLE_OFFSET)) {
            botCount.incrementAndGet();
            botGoogleCount.incrementAndGet();
        } else if (userAgent.startsWith("Baiduspider", MOZILLA_COMPATIBLE_OFFSET)) {
            botCount.incrementAndGet();
            botBaiduCount.incrementAndGet();
        } else if (userAgent.startsWith("MJ12bot", MOZILLA_COMPATIBLE_OFFSET)) {
            botCount.incrementAndGet();
            botBaiduCount.incrementAndGet();
        } else if (userAgent.startsWith("Mail.RU/", MOZILLA_COMPATIBLE_OFFSET)) {
            botCount.incrementAndGet();
        } else if (userAgent.startsWith("Yahoo!", MOZILLA_COMPATIBLE_OFFSET)) {
            botCount.incrementAndGet();
            botYahooCount.incrementAndGet();
        } else if (userAgent.startsWith("KaloogaBot", MOZILLA_COMPATIBLE_OFFSET)) {
            botCount.incrementAndGet();
        } else if (userAgent.startsWith("YandexBot", MOZILLA_COMPATIBLE_OFFSET)) {
            botCount.incrementAndGet();
        } else if (userAgent.startsWith("Ezooms/", MOZILLA_COMPATIBLE_OFFSET)) {
            botCount.incrementAndGet();
        } else if (userAgent.startsWith("Exabot/", MOZILLA_COMPATIBLE_OFFSET)) {
            botCount.incrementAndGet();
        } else if (userAgent.startsWith("AhrefsBot/", MOZILLA_COMPATIBLE_OFFSET)) {
            botCount.incrementAndGet();
        } else if (userAgent.startsWith("YodaoBot/", MOZILLA_COMPATIBLE_OFFSET)) {
            botCount.incrementAndGet();
        } else if (userAgent.startsWith("BeetleBot", MOZILLA_COMPATIBLE_OFFSET)) {
            botCount.incrementAndGet();
        } else if (userAgent.startsWith("archive.org_bot", MOZILLA_COMPATIBLE_OFFSET)) {
            botCount.incrementAndGet();
        } else if (userAgent.startsWith("aiHitBot", MOZILLA_COMPATIBLE_OFFSET)) {
            botCount.incrementAndGet();
        } else if (userAgent.startsWith("EventGuruBot", MOZILLA_COMPATIBLE_OFFSET)) {
            botCount.incrementAndGet();

        } else if (userAgent.equals("Mozilla/5.0 ()")) {
            botCount.incrementAndGet();
        } else if (userAgent.equals("\"Mozilla/5.0")) {
            botCount.incrementAndGet();
        } else if (userAgent.equals("Mozilla")) {
            botCount.incrementAndGet();
        } else if (userAgent.equals("-")) {
            botCount.incrementAndGet();
        } else if (userAgent.contains("Spider") || userAgent.contains("spider")) {
            botCount.incrementAndGet();
        } else if (userAgent.contains("crawl") || userAgent.contains("Crawl")) {
            botCount.incrementAndGet();
        } else if (userAgent.contains("Bot") || userAgent.contains("bot")) {
            botCount.incrementAndGet();
        }

        // Mozilla/5.0 ()
        // Mozilla/5.0 (compatible; Mail.RU/2.0)
        // Mozilla/5.0 (compatible; bingbot/2.0;
        // YoudaoBot

    }

    private void computeUserAgentFirefoxWindowsVersion(String userAgent) {
        if (userAgent.startsWith("Windows NT 5.1", 13)) {
            osWindowsXPCount.incrementAndGet();
        } else if (userAgent.startsWith("Windows NT 5.1", 25)) {
            osWindowsXPCount.incrementAndGet();

        } else if (userAgent.startsWith("Windows NT 6.0", 13)) {
            osWindowsVistaCount.incrementAndGet();

        } else if (userAgent.startsWith("Windows NT 6.1", 13)) {
            osWindows7Count.incrementAndGet();

        } else if (userAgent.startsWith("Windows NT 6.2", 13)) {
            osWindows8Count.incrementAndGet();

        } else if (userAgent.startsWith("Windows NT 5.0", 13)) {
            osWindows2000Count.incrementAndGet();
        } else if (userAgent.startsWith("Windows NT 5.0", 25)) {
            osWindows2000Count.incrementAndGet();
        }
    }

    private void computeUserAgentIEWindowsVersion(String userAgent) {
        if (userAgent.startsWith("Windows NT 5.1", 35)) {
            osWindowsXPCount.incrementAndGet();
        } else if (userAgent.startsWith("Windows NT 5.0", 35)) {
            osWindows2000Count.incrementAndGet();
        } else if (userAgent.startsWith("Windows NT 5.0", 36)) {
            osWindows2000Count.incrementAndGet();
        } else if (userAgent.startsWith("Windows NT 6.0", 35)) {
            osWindowsVistaCount.incrementAndGet();
        } else if (userAgent.startsWith("Windows NT 6.1", 35)) {
            osWindows7Count.incrementAndGet();
        } else if (userAgent.startsWith("Windows NT 6.2", 36)) {
            osWindows8Count.incrementAndGet();
        } else if (userAgent.startsWith("Windows 98", 36)) {
            osWindows98Count.incrementAndGet();
        } else if (userAgent.startsWith("Windows 98", 35)) {
            osWindows98Count.incrementAndGet();
        } else if (userAgent.startsWith("Windows XP", 35)) {
            osWindowsXPCount.incrementAndGet();
        } else if (userAgent.startsWith("Windows XP", 34)) {
            osWindowsXPCount.incrementAndGet();
        }
    }

    private boolean computeUserAgentAndroid(String userAgent) {
        boolean isAndroid = userAgent.startsWith("Android", 23);
        int toffset = 31;
        if (!isAndroid) {
            isAndroid = userAgent.startsWith("Android", 20);
            toffset = 28;
        }

        if (isAndroid) {
            osAndroidCount.incrementAndGet();
            deviceAndroidCount.incrementAndGet();

            if (userAgent.startsWith("1.5", toffset)) {
                osAndroid15Count.incrementAndGet();
            } else if (userAgent.startsWith("1.6", toffset)) {
                osAndroid16Count.incrementAndGet();
            } else if (userAgent.startsWith("2.0", toffset)) {
                osAndroid20Count.incrementAndGet();
            } else if (userAgent.startsWith("2.1", toffset)) {
                osAndroid21Count.incrementAndGet();
            } else if (userAgent.startsWith("2.2", toffset)) {
                osAndroid22Count.incrementAndGet();
            } else if (userAgent.startsWith("2.3.3", toffset)) {
                osAndroid23Count.incrementAndGet();
            } else if (userAgent.startsWith("2.3.4", toffset)) {
                osAndroid23Count.incrementAndGet();
            } else if (userAgent.startsWith("3.0", toffset)) {
                osAndroid30Count.incrementAndGet();
            } else if (userAgent.startsWith("3.1", toffset)) {
                osAndroid31Count.incrementAndGet();
            } else if (userAgent.startsWith("3.2", toffset)) {
                osAndroid32Count.incrementAndGet();
            } else if (userAgent.startsWith("4.0", toffset)) {
                osAndroid40Count.incrementAndGet();
            } else if (userAgent.startsWith("4.1", toffset)) {
                osAndroid41Count.incrementAndGet();
            } else if (userAgent.startsWith("4.2", toffset)) {
                osAndroid42Count.incrementAndGet();
            } else if (userAgent.startsWith("4.3", toffset)) {
                osAndroid43Count.incrementAndGet();
            }

            return true;
        }

        return false;
    }

    public long getOSMacOSXCount() {
        return osMacOSXCount.get();
    }

    public long getOSWindowsCount() {
        return osWindowsCount.get();
    }

    public long getOSLinuxCount() {
        return osLinuxCount.get();
    }

    public long getOSSymbianCount() {
        return osSymbianCount.get();
    }

    public long getOSFreeBSDCount() {
        return osFreeBSDCount.get();
    }

    public long getOSOpenBSDCount() {
        return osOpenBSDCount.get();
    }

    public long getOSAndroidCount() {
        return osAndroidCount.get();
    }

    public long getOSWindows98Count() {
        return osWindows98Count.get();
    }

    public long getOSWindowsXPCount() {
        return osWindowsXPCount.get();
    }

    public long getOSWindows2000Count() {
        return osWindows2000Count.get();
    }

    public long getOSWindowsVistaCount() {
        return osWindowsVistaCount.get();
    }

    public long getOSWindows7Count() {
        return osWindows7Count.get();
    }

    public long getOSWindows8Count() {
        return osWindows8Count.get();
    }

    public long getOSAndroid15Count() {
        return osAndroid15Count.get();
    }

    public long getOSAndroid16Count() {
        return osAndroid16Count.get();
    }

    public long getOSAndroid20Count() {
        return osAndroid20Count.get();
    }

    public long getOSAndroid21Count() {
        return osAndroid21Count.get();
    }

    public long getOSAndroid22Count() {
        return osAndroid22Count.get();
    }

    public long getOSAndroid23Count() {
        return osAndroid23Count.get();
    }

    public long getOSAndroid30Count() {
        return osAndroid30Count.get();
    }

    public long getOSAndroid31Count() {
        return osAndroid31Count.get();
    }

    public long getOSAndroid32Count() {
        return osAndroid32Count.get();
    }

    public long getOSAndroid40Count() {
        return osAndroid40Count.get();
    }

    public long getOSAndroid41Count() {
        return osAndroid41Count.get();
    }

    public long getOSAndroid42Count() {
        return osAndroid42Count.get();
    }

    public long getOSAndroid43Count() {
        return osAndroid43Count.get();
    }

    public long getOSLinuxUbuntuCount() {
        return osLinuxUbuntuCount.get();
    }

    public long getBrowserIECount() {
        return browserIECount.get();
    }

    public long getBrowserFirefoxCount() {
        return browserFirefoxCount.get();
    }

    public long getBrowserChromeCount() {
        return browserChromeCount.get();
    }

    public long getBrowserSafariCount() {
        return browserSafariCount.get();
    }

    public long getBrowserOperaCount() {
        return browserOperaCount.get();
    }

    public long getBrowserIE5Count() {
        return browserIE5Count.get();
    }

    public long getBrowserIE6Count() {
        return browserIE6Count.get();
    }

    public long getBrowserIE7Count() {
        return browserIE7Count.get();
    }

    public long getBrowserIE8Count() {
        return browserIE8Count.get();
    }

    public long getBrowserIE9Count() {
        return browserIE9Count.get();
    }

    public long getBrowserIE10Count() {
        return browserIE10Count.get();
    }

    public long getBrowser360SECount() {
        return browser360SECount.get();
    }

    public long getDeviceAndroidCount() {
        return deviceAndroidCount.get();
    }

    public long getDeviceIpadCount() {
        return deviceIpadCount.get();
    }

    public long getDeviceIphoneCount() {
        return deviceIphoneCount.get();
    }

    public long getDeviceWindowsPhoneCount() {
        return deviceWindowsPhoneCount.get();
    }

    public long getBotCount() {
        return botCount.get();
    }

    public long getBotBaiduCount() {
        return botBaiduCount.get();
    }

    public long getBotYoudaoCount() {
        return botYoudaoCount.get();
    }

    public long getBotGoogleCount() {
        return botGoogleCount.get();
    }

    public long getBotMsnCount() {
        return botMsnCount.get();
    }

    public long getBotBingCount() {
        return botBingCount.get();
    }

    public long getBotSosoCount() {
        return botSosoCount.get();
    }

    public long getBotSogouCount() {
        return botSogouCount.get();
    }

    public long getBotYahooCount() {
        return botYahooCount.get();
    }

    public WebAppStatValue getStatValue(boolean reset) {
        WebAppStatValue val = new WebAppStatValue();
        val.setContextPath(contextPath);

        val.setRunningCount(getRunningCount());
        val.concurrentMax = get(concurrentMax, reset);
        val.requestCount = get(requestCount, reset);
        val.sessionCount = get(sessionCount, reset);
        val.jdbcFetchRowCount = get(jdbcFetchRowCount, reset);
        val.jdbcUpdateCount = get(jdbcUpdateCount, reset);
        val.jdbcExecuteCount = get(jdbcExecuteCount, reset);
        val.jdbcExecuteTimeNano = get(jdbcExecuteTimeNano, reset);
        val.jdbcCommitCount = get(jdbcCommitCount, reset);
        val.jdbcRollbackCount = get(jdbcRollbackCount, reset);
        if (val.sessionCount < 1) {
            return val;
        }

        val.osMacOSXCount = get(osMacOSXCount, reset);
        val.osWindowsCount = get(osWindowsCount, reset);
        val.osLinuxCount = get(osLinuxCount, reset);
        val.osSymbianCount = get(osSymbianCount, reset);
        val.osFreeBSDCount = get(osFreeBSDCount, reset);
        val.osOpenBSDCount = get(osOpenBSDCount, reset);
        val.osAndroidCount = get(osAndroidCount, reset);

        val.osWindows98Count = get(osWindows98Count, reset);
        val.osWindowsXPCount = get(osWindowsXPCount, reset);
        val.osWindows2000Count = get(osWindows2000Count, reset);
        val.osWindowsVistaCount = get(osWindowsVistaCount, reset);
        val.osWindows7Count = get(osWindows7Count, reset);
        val.osWindows8Count = get(osWindows8Count, reset);

        val.osAndroid15Count = get(osAndroid15Count, reset);
        val.osAndroid16Count = get(osAndroid16Count, reset);
        val.osAndroid20Count = get(osAndroid20Count, reset);
        val.osAndroid21Count = get(osAndroid21Count, reset);
        val.osAndroid22Count = get(osAndroid22Count, reset);
        val.osAndroid23Count = get(osAndroid23Count, reset);
        val.osAndroid30Count = get(osAndroid30Count, reset);
        val.osAndroid31Count = get(osAndroid31Count, reset);
        val.osAndroid32Count = get(osAndroid32Count, reset);
        val.osAndroid40Count = get(osAndroid40Count, reset);
        val.osAndroid41Count = get(osAndroid41Count, reset);
        val.osAndroid42Count = get(osAndroid42Count, reset);
        val.osAndroid43Count = get(osAndroid43Count, reset);

        val.osLinuxUbuntuCount = get(osLinuxUbuntuCount, reset);

        val.browserIECount = get(browserIECount, reset);
        val.browserFirefoxCount = get(browserFirefoxCount, reset);
        val.browserChromeCount = get(browserChromeCount, reset);
        val.browserSafariCount = get(browserSafariCount, reset);
        val.browserOperaCount = get(browserOperaCount, reset);

        val.browserIE5Count = get(browserIE5Count, reset);
        val.browserIE6Count = get(browserIE6Count, reset);
        val.browserIE7Count = get(browserIE7Count, reset);
        val.browserIE8Count = get(browserIE8Count, reset);
        val.browserIE9Count = get(browserIE9Count, reset);
        val.browserIE10Count = get(browserIE10Count, reset);

        val.browser360SECount = get(browser360SECount, reset);
        val.deviceAndroidCount = get(deviceAndroidCount, reset);
        val.deviceIpadCount = get(deviceIpadCount, reset);
        val.deviceIphoneCount = get(deviceIphoneCount, reset);
        val.deviceWindowsPhoneCount = get(deviceWindowsPhoneCount, reset);

        val.botCount = get(botCount, reset);
        val.botBaiduCount = get(botBaiduCount, reset);
        val.botYoudaoCount = get(botYoudaoCount, reset);
        val.botGoogleCount = get(botGoogleCount, reset);
        val.botMsnCount = get(botMsnCount, reset);
        val.botBingCount = get(botBingCount, reset);
        val.botSosoCount = get(botSosoCount, reset);
        val.botSogouCount = get(botSogouCount, reset);
        val.botYahooCount = get(botYahooCount, reset);

        return val;
    }
}
