package io.tetrapod.core;

import io.tetrapod.protocol.core.ServiceLogEntry;

import java.util.*;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.*;
import ch.qos.logback.core.AppenderBase;

public class LogBuffer extends AppenderBase<ILoggingEvent> {
   private static final int                MAX_ITEMS       = 1000;
   private static final int                MAX_ERRORS      = 20;

   private static final int                MAX_MESSAGE_LEN = 1024 * 8;

   private final LinkedList<ILoggingEvent> items           = new LinkedList<>();
   private final LinkedList<ILoggingEvent> errors          = new LinkedList<>();
   private final LinkedList<ILoggingEvent> warnings        = new LinkedList<>();

   
   private long                            count           = 0;

   @Override
   protected synchronized void append(ILoggingEvent e) {
      count++;
      items.addLast(e);
      if (items.size() > MAX_ITEMS) {
         items.removeFirst();
      }
      
      if (e.getLevel() == Level.ERROR) {
      	errors.addLast(e);
         if (errors.size() > MAX_ERRORS) {
         	errors.removeFirst();
         }
      }

      if (e.getLevel() == Level.WARN) {
      	warnings.addLast(e);
         if (warnings.size() > MAX_ERRORS) {
         	warnings.removeFirst();
         }
      }
   }

   public synchronized long getItems(long logId, Level level, int maxItems, List<ServiceLogEntry> list) {
      final Iterator<ILoggingEvent> iter = items.descendingIterator();
      int n = 0;
      while (iter.hasNext()) {
         ILoggingEvent item = iter.next();
         if (count - n++ <= logId)
            break;
         if (item.getLevel().isGreaterOrEqual(level)) {
            list.add(logEntryFrom(item));
            if (list.size() >= maxItems) {
               break;
            }
         }
      }
      Collections.reverse(list);
      return count;
   }

   private ServiceLogEntry logEntryFrom(ILoggingEvent e) {
      String msg = e.getFormattedMessage();
      if (e.getThrowableProxy() != null) {
         StringBuilder sb = new StringBuilder();

         sb.append('\n');
         logStack(sb, e.getThrowableProxy());
         msg = sb.toString();
      }
      if (msg.length() > MAX_MESSAGE_LEN) {
         msg = msg.substring(0, MAX_MESSAGE_LEN - 3) + "...";
      }
      return new ServiceLogEntry(msg, convert(e.getLevel()), e.getTimeStamp(), e.getThreadName(), e.getLoggerName());
   }

   private void logStack(StringBuilder sb, IThrowableProxy proxy) {
      sb.append(proxy.getClassName());
      sb.append(": ");
      sb.append(proxy.getMessage());
      sb.append('\n');
      for (StackTraceElementProxy st : proxy.getStackTraceElementProxyArray()) {
         sb.append('\t');
         sb.append(st.getSTEAsString());
         sb.append('\n');
      }
      if (proxy.getCause() != null) {
         sb.append("Caused by: ");
         logStack(sb, proxy.getCause());
      }
   }
   
	public boolean hasErrors() {
		return !errors.isEmpty();
	}

	public boolean hasWarnings() {
		return !warnings.isEmpty();
	}

	public void resetErrorLogs() {
		errors.clear();
		warnings.clear();
	}
	
   public List<ServiceLogEntry> getErrors() {
      final List<ServiceLogEntry> list = new ArrayList<ServiceLogEntry>();
      for(ILoggingEvent error : errors) {
         list.add(logEntryFrom(error));
      }
      return list;
   }

   public List<ServiceLogEntry> getWarnings() {
      final List<ServiceLogEntry> list = new ArrayList<ServiceLogEntry>();
      for(ILoggingEvent warning : warnings) {
         list.add(logEntryFrom(warning));
      }
      return list;
   }
	
   public Level convert(byte level) {
      switch (level) {
         case ServiceLogEntry.LEVEL_ALL:
            return Level.ALL;
         case ServiceLogEntry.LEVEL_TRACE:
            return Level.TRACE;
         case ServiceLogEntry.LEVEL_DEBUG:
            return Level.DEBUG;
         case ServiceLogEntry.LEVEL_INFO:
            return Level.INFO;
         case ServiceLogEntry.LEVEL_WARN:
            return Level.WARN;
         case ServiceLogEntry.LEVEL_ERROR:
            return Level.ERROR;
         case ServiceLogEntry.LEVEL_OFF:
            return Level.OFF;
      }
      return null;
   }

   public byte convert(Level level) {
      switch (level.levelInt) {
         case Level.ALL_INT:
            return ServiceLogEntry.LEVEL_ALL;
         case Level.TRACE_INT:
            return ServiceLogEntry.LEVEL_TRACE;
         case Level.DEBUG_INT:
            return ServiceLogEntry.LEVEL_DEBUG;
         case Level.INFO_INT:
            return ServiceLogEntry.LEVEL_INFO;
         case Level.WARN_INT:
            return ServiceLogEntry.LEVEL_WARN;
         case Level.ERROR_INT:
            return ServiceLogEntry.LEVEL_ERROR;
         case Level.OFF_INT:
            return ServiceLogEntry.LEVEL_OFF;
      }
      return 0;
   }
}

@@ -718,20 +718,16 @@
 t%3C%3E();%0A%0A
-   %0A
    priva
@@ -782,16 +782,16 @@
    = 0;%0A
+
 %0A   @Ove
@@ -972,15 +972,9 @@
   %7D%0A
-      
 %0A
+
     
@@ -1012,25 +1012,27 @@
 OR) %7B%0A      
-%09
+   
 errors.addLa
@@ -1090,17 +1090,19 @@
         
-%09
+   
 errors.r
@@ -1177,25 +1177,27 @@
 RN) %7B%0A      
-%09
+   
 warnings.add
@@ -1259,17 +1259,19 @@
         
+   
-%09
 warnings
@@ -2935,20 +2935,32 @@
   %7D%0A
+%0A
    
-%0A%09
 public 
+synchronized 
 bool
@@ -2973,26 +2973,30 @@
 sErrors() %7B%0A
-%09%09
+      
 return !erro
@@ -3009,27 +3009,44 @@
 mpty();%0A
-%09%7D%0A%0A%09public
+   %7D%0A%0A   public synchronized
  boolean
@@ -3062,18 +3062,22 @@
 ngs() %7B%0A
-%09%09
+      
 return !
@@ -3100,19 +3100,36 @@
 ();%0A
-%09%7D%0A%0A%09public
+   %7D%0A%0A   public synchronized
  voi
@@ -3149,18 +3149,22 @@
 ogs() %7B%0A
-%09%09
+      
 errors.c
@@ -3175,10 +3175,14 @@
 ();%0A
-%09%09
+      
 warn
@@ -3199,12 +3199,13 @@
 ();%0A
-%09
+   
 %7D%0A
-%09
 %0A   
@@ -3203,32 +3203,45 @@
    %7D%0A%0A   public 
+synchronized 
 List%3CServiceLogE
@@ -3336,32 +3336,33 @@
 ry%3E();%0A      for
+ 
 (ILoggingEvent e
@@ -3461,16 +3461,29 @@
  public 
+synchronized 
 List%3CSer
@@ -3596,16 +3596,17 @@
      for
+ 
 (ILoggin
@@ -3712,9 +3712,8 @@
   %7D%0A
-%09
 %0A   

Fix lack of syncronization on LogBuffer