/*
 * Copyright 2016-present Facebook, Inc.
 *
 * 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.facebook.buck.log.memory;

import com.facebook.buck.log.LogFormatter;
import com.facebook.buck.log.Logger;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;

import java.io.IOException;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;

/**
 * <code>MemoryHandler</code> maintains a circular buffer of LogRecords. The underlying circular
 * buffer implementation is implemented directly from java.util.logging.MemoryHandler, but this
 * handler extends the default JUL handler by allowing LogRecords to be handled in batch.
 * Logs are only written to file if a LogRecord at or above the push level is recorded.
 */
public class MemoryHandler extends Handler {
  private static final Logger LOG = Logger.get(MemoryHandler.class);
  private static final LogManager LOG_MANAGER = LogManager.getLogManager();

  private static final Level DEFAULT_LEVEL = Level.ALL;
  private static final Level DEFAULT_PUSH_LEVEL = Level.SEVERE;
  private static final Integer DEFAULT_BUFFER_SIZE = 100;
  private static final Formatter DEFAULT_FORMATTER = new LogFormatter();

  private final LogRecord[] buffer;
  private int start;
  private int count;
  private Level pushLevel;

  /**
   * Constructs a <code>MemoryHandler</code> specified by:.
   * <ul>
   * <li>   com.facebook.buck.cli.bootstrapper.MemoryHandler.level
   *        specifies the level for the <tt>Handler</tt>.
   * <li>   com.facebook.buck.cli.bootstrapper.MemoryHandler.size
   *        defines the buffer size.
   * <li>   com.facebook.buck.cli.bootstrapper.MemoryHandler.push
   *        defines the <tt>pushLevel</tt>.
   * <li>   com.facebook.buck.cli.bootstrapper.MemoryHandler.formatter
   *        defines the <tt>formatter</tt> for log records.
   * </ul>
   */
  public MemoryHandler() throws IOException {
    this(
        getLogLevelProperty(),
        getBufferSizeProperty(),
        getPushLevelProperty());
  }

  @VisibleForTesting
  MemoryHandler(
      Level logLevel,
      int bufferSize,
      Level pushLevel) {
    Preconditions.checkState(bufferSize >= 0);
    Preconditions.checkNotNull(pushLevel);

    buffer = new LogRecord[bufferSize];
    this.pushLevel = pushLevel;

    setLevel(logLevel);
    setFormatter(DEFAULT_FORMATTER);
  }

  private static Level getLogLevelProperty() {
    String levelStr = LOG_MANAGER.getProperty(MemoryHandler.class.getName() + ".level");
    if (levelStr != null) {
      return Level.parse(levelStr);
    } else {
      LOG.info("No log level specified so default log level %s will be used", DEFAULT_LEVEL);
      return DEFAULT_LEVEL;
    }
  }

  private static Integer getBufferSizeProperty() {
    String size = LOG_MANAGER.getProperty(MemoryHandler.class.getName() + ".size");
    if (size != null) {
      try {
        int intSize = Integer.parseInt(size);
        if (intSize > 0) {
          return intSize;
        }
      } catch (NumberFormatException e) {
        LOG.warn("Invalid buffer size specified in logging.properties (must be > 0), using " +
            "default buffer size of %s", DEFAULT_BUFFER_SIZE);
      }
    }
    LOG.info("No buffer size specified so default size %s will be used", DEFAULT_BUFFER_SIZE);
    return DEFAULT_BUFFER_SIZE;
  }

  private static Level getPushLevelProperty() {
    String levelStr = LOG_MANAGER.getProperty(MemoryHandler.class.getName() + ".push");
    if (levelStr != null) {
      return Level.parse(levelStr);
    } else {
      LOG.info("No push level specified so default push level %s will be used", DEFAULT_PUSH_LEVEL);
      return DEFAULT_PUSH_LEVEL;
    }
  }

  @Override
  public void publish(LogRecord record) {
    if (!isLoggable(record)) {
      return;
    }
    int ix = (start + count) % buffer.length;
    buffer[ix] = record;
    if (count < buffer.length) {
      count++;
    } else {
      start++;
      start %= buffer.length;
    }
    if (record.getLevel().intValue() >= pushLevel.intValue()) {
      push();
    }
  }

  public void push() {
    start = 0;
    count = 0;
  }

  @Override
  public void flush() {}

  @Override
  public void close() throws SecurityException {}
}

@@ -849,16 +849,67 @@
 eption;%0A
+import java.util.ArrayList;%0Aimport java.util.List;%0A
 import j
@@ -2761,23 +2761,16 @@
 Handler(
-%0A      
 Level lo
@@ -2776,22 +2776,16 @@
 ogLevel,
-%0A     
  int buf
@@ -2792,22 +2792,16 @@
 ferSize,
-%0A     
  Level p
@@ -4473,24 +4473,95 @@
 turn;%0A    %7D%0A
+    List%3CLogRecord%3E recordsToLog = null;%0A    synchronized (buffer) %7B%0A  
     int ix =
@@ -4594,24 +4594,26 @@
 length;%0A    
+  
 buffer%5Bix%5D =
@@ -4621,24 +4621,26 @@
 record;%0A    
+ 
+ 
 if (count %3C 
@@ -4658,24 +4658,26 @@
 th) %7B%0A      
+  
 count++;%0A   
@@ -4669,24 +4669,26 @@
    count++;%0A
+  
     %7D else %7B
@@ -4690,24 +4690,26 @@
 lse %7B%0A      
+  
 start++;%0A   
@@ -4701,24 +4701,26 @@
    start++;%0A
+  
       start 
@@ -4737,26 +4737,30 @@
 length;%0A    
+ 
+ 
 %7D%0A
+  
     if (reco
@@ -4821,85 +4821,292 @@
     
-push();%0A    %7D%0A  %7D%0A%0A  public void push() %7B%0A    start = 0;%0A    count = 0;
+  recordsToLog = new ArrayList%3C%3E();%0A        while (count %3E 0) %7B%0A          LogRecord oldRecord = buffer%5Bstart%5D;%0A          recordsToLog.add(oldRecord);%0A          buffer%5Bstart%5D = null;%0A          start++;%0A          start %25= buffer.length;%0A          count--;%0A        %7D%0A      %7D%0A    %7D
 %0A  %7D%0A%0A
+%0A
   @O

Fix MemoryHandler for LogView Error logger