package com.alibaba.druid.stat;

import com.alibaba.druid.util.Histogram;
import com.alibaba.druid.util.JMXUtils;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import javax.management.JMException;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.OpenType;
import javax.management.openmbean.SimpleType;

public class JdbcConnectionStat implements JdbcConnectionStatMBean {
   private final AtomicInteger activeCount = new AtomicInteger();
   private final AtomicInteger activeCountMax = new AtomicInteger();
   private final AtomicInteger connectingCount = new AtomicInteger();
   private final AtomicInteger connectingMax = new AtomicInteger();
   private final AtomicLong connectCount = new AtomicLong();
   private final AtomicLong connectErrorCount = new AtomicLong();
   private Throwable connectErrorLast;
   private final AtomicLong connectNanoTotal = new AtomicLong(0L);
   private final AtomicLong connectNanoMax = new AtomicLong(0L);
   private final AtomicLong errorCount = new AtomicLong();
   private final AtomicLong aliveNanoTotal = new AtomicLong();
   private Throwable lastError;
   private long lastErrorTime;
   private long connectLastTime = 0L;
   private final AtomicLong closeCount = new AtomicLong(0L);
   private final AtomicLong transactionStartCount = new AtomicLong(0L);
   private final AtomicLong commitCount = new AtomicLong(0L);
   private final AtomicLong rollbackCount = new AtomicLong(0L);
   private final AtomicLong aliveNanoMin = new AtomicLong();
   private final AtomicLong aliveNanoMax = new AtomicLong();
   private final Histogram histogram;

   public JdbcConnectionStat() {
      this.histogram = new Histogram(TimeUnit.SECONDS, new long[]{1L, 5L, 15L, 60L, 300L, 1800L});
   }

   public void reset() {
      this.connectingMax.set(0);
      this.connectErrorCount.set(0L);
      this.errorCount.set(0L);
      this.aliveNanoTotal.set(0L);
      this.aliveNanoMin.set(0L);
      this.aliveNanoMax.set(0L);
      this.lastError = null;
      this.lastErrorTime = 0L;
      this.connectLastTime = 0L;
      this.connectCount.set(0L);
      this.closeCount.set(0L);
      this.transactionStartCount.set(0L);
      this.commitCount.set(0L);
      this.rollbackCount.set(0L);
      this.connectNanoTotal.set(0L);
      this.connectNanoMax.set(0L);
      this.histogram.reset();
   }

   public void beforeConnect() {
      int invoking = this.connectingCount.incrementAndGet();

      int max;
      do {
         max = this.connectingMax.get();
      } while(invoking > max && !this.connectingMax.compareAndSet(max, invoking));

      this.connectCount.incrementAndGet();
      this.connectLastTime = System.currentTimeMillis();
   }

   public void afterConnected(long delta) {
      this.connectingCount.decrementAndGet();
      this.connectNanoTotal.addAndGet(delta);

      long max;
      do {
         max = this.connectNanoMax.get();
      } while(delta > max && !this.connectNanoMax.compareAndSet(max, delta));

      this.activeCount.incrementAndGet();
   }

   public long getConnectNanoMax() {
      return this.connectNanoMax.get();
   }

   public long getConnectMillisMax() {
      return this.connectNanoMax.get() / 1000000L;
   }

   public void setActiveCount(int activeCount) {
      this.activeCount.set(activeCount);

      int max;
      do {
         max = this.activeCountMax.get();
      } while(activeCount > max && !this.activeCountMax.compareAndSet(max, activeCount));

   }

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

   public int getAtiveCountMax() {
      return this.activeCount.get();
   }

   public long getErrorCount() {
      return this.errorCount.get();
   }

   public int getConnectingCount() {
      return this.connectingCount.get();
   }

   public int getConnectingMax() {
      return this.connectingMax.get();
   }

   public long getAliveTotal() {
      return this.aliveNanoTotal.get();
   }

   public long getAliveNanoMin() {
      return this.aliveNanoMin.get();
   }

   public long getAliveMillisMin() {
      return this.aliveNanoMin.get() / 1000000L;
   }

   public long getAliveNanoMax() {
      return this.aliveNanoMax.get();
   }

   public long getAliveMillisMax() {
      return this.aliveNanoMax.get() / 1000000L;
   }

   public void afterClose(long aliveNano) {
      this.activeCount.decrementAndGet();
      this.aliveNanoTotal.addAndGet(aliveNano);

      long max;
      do {
         max = this.aliveNanoMax.get();
      } while(aliveNano > max && !this.aliveNanoMax.compareAndSet(max, aliveNano));

      do {
         max = this.aliveNanoMin.get();
      } while((max == 0L || aliveNano < max) && !this.aliveNanoMin.compareAndSet(max, aliveNano));

      max = aliveNano / 1000000L;
      this.histogram.record(max);
   }

   public Throwable getErrorLast() {
      return this.lastError;
   }

   public Throwable getConnectErrorLast() {
      return this.connectErrorLast;
   }

   public Date getErrorLastTime() {
      return this.lastErrorTime <= 0L ? null : new Date(this.lastErrorTime);
   }

   public void connectError(Throwable error) {
      this.connectErrorCount.incrementAndGet();
      this.connectErrorLast = error;
      this.errorCount.incrementAndGet();
      this.lastError = error;
      this.lastErrorTime = System.currentTimeMillis();
   }

   public void error(Throwable error) {
      this.errorCount.incrementAndGet();
      this.lastError = error;
      this.lastErrorTime = System.currentTimeMillis();
   }

   public long getCloseCount() {
      return this.closeCount.get();
   }

   public long getCommitCount() {
      return this.commitCount.get();
   }

   public long getConnectCount() {
      return this.connectCount.get();
   }

   public long getConnectMillis() {
      return this.connectNanoTotal.get() / 1000000L;
   }

   public int getActiveMax() {
      return this.activeCountMax.get();
   }

   public long getRollbackCount() {
      return this.rollbackCount.get();
   }

   public long getConnectErrorCount() {
      return this.connectErrorCount.get();
   }

   public Date getConnectLastTime() {
      return this.connectLastTime == 0L ? null : new Date(this.connectLastTime);
   }

   public void incrementConnectionCloseCount() {
      this.closeCount.incrementAndGet();
   }

   public void incrementConnectionCommitCount() {
      this.commitCount.incrementAndGet();
   }

   public void incrementConnectionRollbackCount() {
      this.rollbackCount.incrementAndGet();
   }

   public void incrementTransactionStartCount() {
      this.transactionStartCount.incrementAndGet();
   }

   public long getTransactionStartCount() {
      return this.transactionStartCount.get();
   }

   public long[] getHistorgramValues() {
      return this.histogram.toArray();
   }

   public long[] getHistogramRanges() {
      return this.histogram.getRanges();
   }

   public static class Entry implements EntryMBean {
      private long id;
      private long establishTime;
      private long establishNano;
      private Date connectTime;
      private long connectTimespanNano;
      private Exception connectStackTraceException;
      private String lastSql;
      private Exception lastStatementStatckTraceException;
      protected Throwable lastError;
      protected long lastErrorTime;
      private final String dataSource;
      private static String[] indexNames = new String[]{"ID", "ConnectTime", "ConnectTimespan", "EstablishTime", "AliveTimespan", "LastSql", "LastError", "LastErrorTime", "ConnectStatckTrace", "LastStatementStackTrace", "DataSource"};
      private static String[] indexDescriptions;

      public Entry(String dataSource, long connectionId) {
         this.id = connectionId;
         this.dataSource = dataSource;
      }

      public void reset() {
         this.lastSql = null;
         this.lastStatementStatckTraceException = null;
         this.lastError = null;
         this.lastErrorTime = 0L;
      }

      public Date getEstablishTime() {
         return this.establishTime <= 0L ? null : new Date(this.establishTime);
      }

      public void setEstablishTime(long establishTime) {
         this.establishTime = establishTime;
      }

      public long getEstablishNano() {
         return this.establishNano;
      }

      public void setEstablishNano(long establishNano) {
         this.establishNano = establishNano;
      }

      public Date getConnectTime() {
         return this.connectTime;
      }

      public void setConnectTime(Date connectTime) {
         this.connectTime = connectTime;
      }

      public long getConnectTimespanNano() {
         return this.connectTimespanNano;
      }

      public void setConnectTimespanNano(long connectTimespanNano) {
         this.connectTimespanNano = connectTimespanNano;
      }

      public String getLastSql() {
         return this.lastSql;
      }

      public void setLastSql(String lastSql) {
         this.lastSql = lastSql;
      }

      public String getConnectStackTrace() {
         if (this.connectStackTraceException == null) {
            return null;
         } else {
            StringWriter buf = new StringWriter();
            this.connectStackTraceException.printStackTrace(new PrintWriter(buf));
            return buf.toString();
         }
      }

      public void setConnectStackTrace(Exception connectStackTraceException) {
         this.connectStackTraceException = connectStackTraceException;
      }

      public String getLastStatementStatckTrace() {
         if (this.lastStatementStatckTraceException == null) {
            return null;
         } else {
            StringWriter buf = new StringWriter();
            this.lastStatementStatckTraceException.printStackTrace(new PrintWriter(buf));
            return buf.toString();
         }
      }

      public void setLastStatementStatckTrace(Exception lastStatementStatckTrace) {
         this.lastStatementStatckTraceException = lastStatementStatckTrace;
      }

      public void error(Throwable lastError) {
         this.lastError = lastError;
         this.lastErrorTime = System.currentTimeMillis();
      }

      public Date getLastErrorTime() {
         return this.lastErrorTime <= 0L ? null : new Date(this.lastErrorTime);
      }

      public static CompositeType getCompositeType() throws JMException {
         OpenType<?>[] indexTypes = new OpenType[]{SimpleType.LONG, SimpleType.DATE, SimpleType.LONG, SimpleType.DATE, SimpleType.LONG, SimpleType.STRING, JMXUtils.getThrowableCompositeType(), SimpleType.DATE, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING};
         return new CompositeType("ConnectionStatistic", "Connection Statistic", indexNames, indexDescriptions, indexTypes);
      }

      public String getDataSource() {
         return this.dataSource;
      }

      public CompositeDataSupport getCompositeData() throws JMException {
         Map<String, Object> map = new HashMap();
         map.put("ID", this.id);
         map.put("ConnectTime", this.getConnectTime());
         map.put("ConnectTimespan", this.getConnectTimespanNano() / 1000000L);
         map.put("EstablishTime", this.getEstablishTime());
         map.put("AliveTimespan", (System.nanoTime() - this.getEstablishNano()) / 1000000L);
         map.put("LastSql", this.getLastSql());
         map.put("LastError", JMXUtils.getErrorCompositeData(this.lastError));
         map.put("LastErrorTime", this.getLastErrorTime());
         map.put("ConnectStatckTrace", this.getConnectStackTrace());
         map.put("LastStatementStackTrace", this.getLastStatementStatckTrace());
         map.put("DataSource", this.getDataSource());
         return new CompositeDataSupport(getCompositeType(), map);
      }

      static {
         indexDescriptions = indexNames;
      }
   }

   public interface EntryMBean {
      Date getEstablishTime();

      long getEstablishNano();

      Date getConnectTime();

      long getConnectTimespanNano();

      String getLastSql();

      String getConnectStackTrace();

      String getLastStatementStatckTrace();

      Date getLastErrorTime();

      void reset();
   }
}
