/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.hadoop.hive.ql.log;

import java.io.File;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.Writer;

import org.apache.log4j.FileAppender;
import org.apache.log4j.Layout;
import org.apache.log4j.helpers.CountingQuietWriter;
import org.apache.log4j.helpers.LogLog;
import org.apache.logging.log4j.core.util.OptionConverter;
import org.apache.log4j.spi.LoggingEvent;

public class NoDeleteRollingFileAppender extends FileAppender {
  /**
   * The default maximum file size is 10MB.
   */
  protected long maxFileSize = 10 * 1024 * 1024;

  private long nextRollover = 0;

  /**
   * The default constructor simply calls its {@link FileAppender#FileAppender
   * parents constructor}.
   */
  public NoDeleteRollingFileAppender() {
  }

  /**
   * Instantiate a RollingFileAppender and open the file designated by
   * <code>filename</code>. The opened filename will become the output
   * destination for this appender.
   * <p>
   * If the <code>append</code> parameter is true, the file will be appended to.
   * Otherwise, the file designated by <code>filename</code> will be truncated
   * before being opened.
   */
  public NoDeleteRollingFileAppender(Layout layout, String filename,
      boolean append) throws IOException {
    super(layout, filename, append);
  }

  /**
   * Instantiate a FileAppender and open the file designated by
   * <code>filename</code>. The opened filename will become the output
   * destination for this appender.
   * <p>
   * The file will be appended to.
   */
  public NoDeleteRollingFileAppender(Layout layout, String filename)
      throws IOException {
    super(layout, filename);
  }

  /**
   * Get the maximum size that the output file is allowed to reach before being
   * rolled over to backup files.
   */
  public long getMaximumFileSize() {
    return maxFileSize;
  }

  /**
   * Implements the usual roll over behavior.
   * <p>
   * <code>File</code> is renamed <code>File.yyyyMMddHHmmss</code> and closed. A
   * new <code>File</code> is created to receive further log output.
   */
  // synchronization not necessary since doAppend is already synced
  public void rollOver() {
    if (qw != null) {
      long size = ((CountingQuietWriter) qw).getCount();
      LogLog.debug("rolling over count=" + size);
      // if operation fails, do not roll again until
      // maxFileSize more bytes are written
      nextRollover = size + maxFileSize;
    }

    this.closeFile(); // keep windows happy.

    int p = fileName.lastIndexOf(".");
    String file = p > 0 ? fileName.substring(0, p) : fileName;
    try {
      // This will also close the file. This is OK since multiple
      // close operations are safe.
      this.setFile(file, false, bufferedIO, bufferSize);
      nextRollover = 0;
    } catch (IOException e) {
      if (e instanceof InterruptedIOException) {
        Thread.currentThread().interrupt();
      }
      LogLog.error("setFile(" + file + ", false) call failed.", e);
    }
  }

  public synchronized void setFile(String fileName, boolean append,
      boolean bufferedIO, int bufferSize) throws IOException {
    String newFileName = getLogFileName(fileName);
    super.setFile(newFileName, append, this.bufferedIO, this.bufferSize);
    if (append) {
      File f = new File(newFileName);
      ((CountingQuietWriter) qw).setCount(f.length());
    }
  }

  /**
   * Set the maximum size that the output file is allowed to reach before being
   * rolled over to backup files.
   * <p>
   * This method is equivalent to {@link #setMaxFileSize} except that it is
   * required for differentiating the setter taking a <code>long</code> argument
   * from the setter taking a <code>String</code> argument by the JavaBeans
   * {@link java.beans.Introspector Introspector}.
   *
   * @see #setMaxFileSize(String)
   */
  public void setMaximumFileSize(long maxFileSize) {
    this.maxFileSize = maxFileSize;
  }

  /**
   * Set the maximum size that the output file is allowed to reach before being
   * rolled over to backup files.
   * <p>
   * In configuration files, the <b>MaxFileSize</b> option takes an long integer
   * in the range 0 - 2^63. You can specify the value with the suffixes "KB",
   * "MB" or "GB" so that the integer is interpreted being expressed
   * respectively in kilobytes, megabytes or gigabytes. For example, the value
   * "10KB" will be interpreted as 10240.
   */
  public void setMaxFileSize(String value) {
    maxFileSize = OptionConverter.toFileSize(value, maxFileSize + 1);
  }

  protected void setQWForFiles(Writer writer) {
    this.qw = new CountingQuietWriter(writer, errorHandler);
  }

  /**
   * This method differentiates RollingFileAppender from its super class.
   */
  protected void subAppend(LoggingEvent event) {
    super.subAppend(event);

    if (fileName != null && qw != null) {
      long size = ((CountingQuietWriter) qw).getCount();
      if (size >= maxFileSize && size >= nextRollover) {
        rollOver();
      }
    }
  }

  // Mangled file name. Append the current timestamp
  private static String getLogFileName(String oldFileName) {
    return oldFileName + "." + Long.toString(System.currentTimeMillis());
  }
}
