package com.airlock.iam.servicecontainer.app.infrastructure.server;

import com.airlock.iam.servicecontainer.app.application.configuration.Server;
import java.io.StringWriter;
import java.util.Date;
import java.util.Map;
import java.util.Properties;
import java.util.Map.Entry;

public final class ServerStatistics {
   private static final long SECONDS_IN_A_MINUTE = 60L;
   private static final long MINUTES_IN_AN_HOUR = 60L;
   private static final long HOURS_IN_A_DAY = 24L;
   private static Runtime runtime = Runtime.getRuntime();

   private ServerStatistics() {
   }

   public static String getServerUptimeString(Server server) {
      long uptime = server.getServerUptimeMillis();
      long millis = uptime % 1000L;
      uptime /= 1000L;
      long secs = uptime % 60L;
      uptime /= 60L;
      long mins = uptime % 60L;
      uptime /= 60L;
      long hours = uptime % 24L;
      uptime /= 24L;
      long days = uptime;
      return days + "d " + hours + "h " + mins + "mins " + secs + "s " + millis + "ms";
   }

   private static long getTotalMemory() {
      return runtime.totalMemory();
   }

   public static String getTotalMemoryString() {
      return getHumanReadableByteAmount(getTotalMemory());
   }

   private static long getFreeMemory() {
      return runtime.freeMemory();
   }

   public static String getFreeMemoryString() {
      return getHumanReadableByteAmount(getFreeMemory());
   }

   private static long getUsedMemory() {
      return runtime.totalMemory() - runtime.freeMemory();
   }

   public static String getUsedMemoryString() {
      return getHumanReadableByteAmount(getUsedMemory());
   }

   private static String getHumanReadableByteAmount(long bytes) {
      StringBuilder sb = new StringBuilder();
      if (bytes < 4096L) {
         sb.append(bytes);
         appendSpaces(sb);
         return sb.append(" Bytes").toString();
      } else if (bytes < 4194304L) {
         sb.append(Math.round(bytes / 1024.0));
         appendSpaces(sb);
         return sb.append(" KB").toString();
      } else if (bytes < 4294967296L) {
         sb.append(Math.round(bytes / 1048576.0));
         appendSpaces(sb);
         return sb.append(" MB").toString();
      } else {
         sb.append(Math.round(bytes / 1.0737418E9F));
         appendSpaces(sb);
         return sb.append(" GB").toString();
      }
   }

   private static void appendSpaces(StringBuilder sb) {
      while (sb.length() < 4) {
         sb.append(' ');
      }
   }

   public static void printStatistics(Server server, StringWriter sw) {
      sw.write("SERVER STATISTICS");
      sw.write("");
      sw.write("  Current Time            : " + new Date());
      sw.write("  Server Uptime           : " + getServerUptimeString(server));
      sw.write("  Loaded Services         : " + server.getNumberOfLoadedServices());
      sw.write("  Running Services        : " + server.getNumberOfRunningServices());
      sw.write("  Total Memory            : " + getTotalMemoryString());
      sw.write("  Used Memory             : " + getUsedMemoryString());
      sw.write("  Free Memory             : " + getFreeMemoryString());
      sw.write("");
      sw.write("  Total Number of Threads : " + server.getNumberOfThreads());
      sw.write("  Number of idle Threads  : " + server.getNumberOfSleepingThreads());
      sw.write("  Number of busy Threads  : " + server.getNumberOfWorkingThreads());
      sw.write("");
   }

   public static void printThreadInformation(StringWriter sw) {
      sw.write(" THREAD INFORMATION");
      Map<Thread, StackTraceElement[]> stackTraces = Thread.getAllStackTraces();

      for (Entry<Thread, StackTraceElement[]> e : stackTraces.entrySet()) {
         Thread t = e.getKey();
         if (t != null) {
            sw.write("   ----------------------------------------------------------------------------");
            sw.write("   Thread Name     : " + t.getName());
            sw.write("   Thread Id       : " + t.threadId());
            sw.write("   Thread State    : " + t.getState());
            sw.write("   Thread Priority : " + t.getPriority());
            sw.write("   Thread alive ?  : " + t.isAlive());
            sw.write("   Daemon Thread ? : " + t.isDaemon());
            sw.write("   Interrupted ?   : " + t.isInterrupted());
            StackTraceElement[] st = e.getValue();
            if (st != null && st.length != 0) {
               sw.write("   Stack Trace     :" + st[0].toString());

               for (int i = 1; i < st.length; i++) {
                  sw.write("                    " + st[i].toString());
               }
            }
         }
      }
   }

   public static void printSystemProperties(StringWriter sw) {
      sw.write(" SYSTEM PROPERTIES (sorted by key)");
      Properties p = System.getProperties();
      p.stringPropertyNames().stream().sorted().forEach(key -> sw.write("   " + key + " = " + p.getProperty(key)));
   }
}
