/*
 * 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.log4j.lf5;

import org.apache.log4j.lf5.util.Resource;
import org.apache.log4j.lf5.viewer.LogBrokerMonitor;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.spi.LocationInfo;
import org.apache.log4j.spi.LoggingEvent;

import java.awt.*;

/**
 * <code>LF5Appender</code> logs events to a swing based logging
 * console. The swing console supports turning categories on and off,
 * multiple detail level views, as well as full text searching and many
 * other capabilties.
 *
 * @author Brent Sprecher
 */

// Contributed by ThoughtWorks Inc.

public class LF5Appender extends AppenderSkeleton {
  //--------------------------------------------------------------------------
  // Constants:
  //--------------------------------------------------------------------------

  //--------------------------------------------------------------------------
  // Protected Variables:
  //--------------------------------------------------------------------------

  protected LogBrokerMonitor _logMonitor;
  protected static LogBrokerMonitor _defaultLogMonitor;
  protected static AppenderFinalizer _finalizer;

  //--------------------------------------------------------------------------
  // Private Variables:
  //--------------------------------------------------------------------------

  //--------------------------------------------------------------------------
  // Constructors:
  //--------------------------------------------------------------------------

  /**
   * Constructs a <code>LF5Appender</code> using the default instance of
   * the <code>LogBrokerMonitor</code>. This constructor should <bold>always
   * </bold> be  preferred over the
   * <code>LF5Appender(LogBrokerMonitor monitor)</code>
   * constructor, unless you need to spawn additional log monitoring
   * windows.
   */
  public LF5Appender() {
    this(getDefaultInstance());
  }

  /**
   * Constructs a <code>LF5Appender<code> using an instance of
   * a <code>LogBrokerMonitor<code> supplied by the user. This
   * constructor should only be used when you need to spawn
   * additional log monitoring windows.
   *
   * @param monitor An instance of a <code>LogBrokerMonitor<code>
   * created by the user.
   */
  public LF5Appender(LogBrokerMonitor monitor) {

    if (monitor != null) {
      _logMonitor = monitor;
    }
  }

  //--------------------------------------------------------------------------
  // Public Methods:
  //--------------------------------------------------------------------------

  /**
   * Appends a <code>LoggingEvent</code> record to the
   * <code>LF5Appender</code>.
   * @param event The <code>LoggingEvent</code>
   * to be appended.
   */
  public void append(LoggingEvent event) {
    // Retrieve the information from the log4j LoggingEvent.
    String category = event.getLoggerName();
    String logMessage = event.getRenderedMessage();
    String nestedDiagnosticContext = event.getNDC();
    String threadDescription = event.getThreadName();
    String level = event.getLevel().toString();
    long time = event.timeStamp;
    LocationInfo locationInfo = event.getLocationInformation();

    // Add the logging event information to a LogRecord
    Log4JLogRecord record = new Log4JLogRecord();

    record.setCategory(category);
    record.setMessage(logMessage);
    record.setLocation(locationInfo.fullInfo);
    record.setMillis(time);
    record.setThreadDescription(threadDescription);

    if (nestedDiagnosticContext != null) {
      record.setNDC(nestedDiagnosticContext);
    } else {
      record.setNDC("");
    }

    if (event.getThrowableInformation() != null) {
      record.setThrownStackTrace(event.getThrowableInformation());
    }

    try {
      record.setLevel(LogLevel.valueOf(level));
    } catch (LogLevelFormatException e) {
      // If the priority level doesn't match one of the predefined
      // log levels, then set the level to warning.
      record.setLevel(LogLevel.WARN);
    }

    if (_logMonitor != null) {
      _logMonitor.addMessage(record);
    }
  }

  /**
   * This method is an empty implementation of the close() method inherited
   * from the <code>org.apache.log4j.Appender</code> interface.
   */
  public void close() {
  }

  /**
   * Returns a value that indicates whether this appender requires a
   * <code>Layout</code>. This method always returns false.
   * No layout is required for the <code>LF5Appender</code>.
   */
  public boolean requiresLayout() {
    return false;
  }

  /**
   * This method is used to set the property that controls whether
   * the <code>LogBrokerMonitor</code> is hidden or closed when a user
   * exits
   * the monitor. By default, the <code>LogBrokerMonitor</code> will hide
   * itself when the log window is exited, and the swing thread will
   * continue to run in the background. If this property is
   * set to true, the <code>LogBrokerMonitor</code> will call System.exit(0)
   * and will shut down swing thread and the virtual machine.
   *
   * @param callSystemExitOnClose A boolean value indicating whether
   * to call System.exit(0) when closing the log window.
   */
  public void setCallSystemExitOnClose(boolean callSystemExitOnClose) {
    _logMonitor.setCallSystemExitOnClose(callSystemExitOnClose);
  }

  /**
   * The equals method compares two LF5Appenders and determines whether
   * they are equal. Two <code>Appenders</code> will be considered equal
   * if, and only if, they both contain references to the same <code>
   * LogBrokerMonitor</code>.
   *
   * @param compareTo A boolean value indicating whether
   * the two LF5Appenders are equal.
   */
  public boolean equals(LF5Appender compareTo) {
    // If both reference the same LogBrokerMonitor, they are equal.
    return _logMonitor == compareTo.getLogBrokerMonitor();
  }

  public LogBrokerMonitor getLogBrokerMonitor() {
    return _logMonitor;
  }

  public static void main(String[] args) {
    new LF5Appender();
  }

  public void setMaxNumberOfRecords(int maxNumberOfRecords) {
    _defaultLogMonitor.setMaxNumberOfLogRecords(maxNumberOfRecords);
  }
  //--------------------------------------------------------------------------
  // Protected Methods:
  //--------------------------------------------------------------------------

  /**
   * @return The default instance of the <code>LogBrokerMonitor</code>.
   */
  protected static synchronized LogBrokerMonitor getDefaultInstance() {
    if (_defaultLogMonitor == null) {
      try {
        _defaultLogMonitor =
            new LogBrokerMonitor(LogLevel.getLog4JLevels());
        _finalizer = new AppenderFinalizer(_defaultLogMonitor);

        _defaultLogMonitor.setFrameSize(getDefaultMonitorWidth(),
            getDefaultMonitorHeight());
        _defaultLogMonitor.setFontSize(12);
        _defaultLogMonitor.show();

      } catch (SecurityException e) {
        _defaultLogMonitor = null;
      }
    }

    return _defaultLogMonitor;
  }

  /**
   * @return the screen width from Toolkit.getScreenSize()
   * if possible, otherwise returns 800
   * @see java.awt.Toolkit
   */
  protected static int getScreenWidth() {
    try {
      return Toolkit.getDefaultToolkit().getScreenSize().width;
    } catch (Throwable t) {
      return 800;
    }
  }

  /**
   * @return the screen height from Toolkit.getScreenSize()
   * if possible, otherwise returns 600
   * @see java.awt.Toolkit
   */
  protected static int getScreenHeight() {
    try {
      return Toolkit.getDefaultToolkit().getScreenSize().height;
    } catch (Throwable t) {
      return 600;
    }
  }

  protected static int getDefaultMonitorWidth() {
    return (3 * getScreenWidth()) / 4;
  }

  protected static int getDefaultMonitorHeight() {
    return (3 * getScreenHeight()) / 4;
  }
  //--------------------------------------------------------------------------
  // Private Methods:
  //--------------------------------------------------------------------------


  //--------------------------------------------------------------------------
  // Nested Top-Level Classes or Interfaces:
  //--------------------------------------------------------------------------

}
