/*! ******************************************************************************
*
* Pentaho Data Integration
*
* Copyright (C) 2002-2017 by Hitachi Vantara : http://www.pentaho.com
*
*******************************************************************************
*
* 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 org.pentaho.di.core.logging;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;

import org.apache.commons.vfs2.FileObject;
import org.apache.log4j.Appender;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Layout;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.pentaho.di.core.Const;
import org.pentaho.di.core.util.Utils;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.exception.KettleFileException;
import org.pentaho.di.core.vfs.KettleVFS;

/**
 * This class handles the logging.
 * 
 * @author Matt
 * @since 25-04-2003
 * 
 * @deprecated As of release 7.0, no longer needed. This class is used only in
 *             {@link org.pentaho.di.core.logging.CentralLogStore} which is never used in project
 */
@Deprecated
public class LogWriter {
  // private static Class<?> PKG = LogWriter.class; // for i18n purposes, needed by Translator2!!

  private static LogWriter logWriter;

  public static final String STRING_PENTAHO_DI_LOGGER_NAME = "org.pentaho.di";

  public static final String STRING_PENTAHO_DI_CONSOLE_APPENDER = "ConsoleAppender:" + STRING_PENTAHO_DI_LOGGER_NAME;
  public static final String STRING_PENTAHO_BASERVER_FILE_APPENDER = "PENTAHOFILE"; //$NON-NLS-1$
  public static final String STRING_PENTAHO_BASERVER_CONSOLE_APPENDER = "PENTAHOCONSOLE"; //$NON-NLS-1$

  // String...
  private int type;

  private String filter;

  // Log4j
  private Logger pentahoLogger;

  private Log4jFileAppender fileAppender;

  private File realFilename;

  private static Layout layout;

  // synchronizing logWriter singleton instance PDI-6840
  public static final synchronized LogWriter getInstance() {
    if ( logWriter != null ) {
      return logWriter;
    }

    logWriter = new LogWriter();

    return logWriter;
  }

  private LogWriter() {
    pentahoLogger = Logger.getLogger( STRING_PENTAHO_DI_LOGGER_NAME );
    pentahoLogger.setAdditivity( false );

    // ensure all messages get logged in this logger since we filtered it above
    // we do not set the level in the rootLogger so the rootLogger can decide by itself (e.g. in the platform)
    //
    pentahoLogger.setLevel( Level.ALL );

    layout = new Log4jKettleLayout();

    /*
     * The console output is provided for by Kettle5 itself
    // Add a console logger to see something on the console as well...
    //
    boolean consoleAppenderFound = false;
    Enumeration<?> appenders = pentahoLogger.getAllAppenders();
    while (appenders.hasMoreElements()) {
      Appender appender = (Appender) appenders.nextElement();
      if (appender instanceof ConsoleAppender) {
        consoleAppenderFound = true;
        break;
      }
    }

    // Play it safe, if another console appender exists for org.pentaho, don't add another one...
    //
    if (!consoleAppenderFound) {
      Layout patternLayout = new PatternLayout("%-5p %d{dd-MM HH:mm:ss,SSS} - %m%n");
      ConsoleAppender consoleAppender = new ConsoleAppender(patternLayout);
      consoleAppender.setName(STRING_PENTAHO_DI_CONSOLE_APPENDER);
      pentahoLogger.addAppender(consoleAppender);
    }

    // BELOW : This is covered in log4j.xml
    // Doing it here is not really the right location.
    //

    // Get rid of the VFS info messages...
    //
    LogManager.getLogger("org.apache.commons.vfs").setLevel(Level.WARN);

    // Hide info messages from Jetty too...
    //
    // LogManager.getLogger("org.mortbay.log").setLevel(Level.WARN);
    // LogManager.getLogger("org.slf4j").setLevel(Level.WARN);
     */

  }

  /**
   * Get a new log instance for the specified file if it is not open yet!
   * @param filename The log file to open
     * @param exact is this an exact filename (false: prefix of name in temp directory)
   * @return the LogWriter object
   */
  // synchronizing logWriter singleton instance PDI-6840
  public static final synchronized LogWriter getInstance( String filename, boolean exact ) throws KettleException {
    if ( logWriter != null ) {
      // OK, see if we have a file appender already for this
      //
      if ( logWriter.pentahoLogger.getAppender( LogWriter.createFileAppenderName( filename, exact ) ) == null ) {
        logWriter.fileAppender = createFileAppender( filename, exact );
        logWriter.addAppender( logWriter.fileAppender );
      }
      return logWriter;
    }

    logWriter = new LogWriter( filename, exact );
    return logWriter;
  }

  /**
   * Closes the file appender opened by the getInstance(filename, exact, level) method
   */
  public static final void closeAndRemoveFileAppender() {
    if ( logWriter.fileAppender != null ) {
      logWriter.fileAppender.close();
      logWriter.pentahoLogger.removeAppender( logWriter.fileAppender );
    }
  }

  private LogWriter( String filename, boolean exact ) {
    this();

    try {
      fileAppender = createFileAppender( filename, exact );
      addAppender( fileAppender );
    } catch ( Exception e ) {
      System.out.println( "ERROR OPENING LOG FILE [" + filename + "] --> " + e.toString() );
    }
  }

  /**
   * Create a file appender
   * @param filename The (VFS) filename (URL) to write to.
   * @param exact is this an exact filename of a filename to be stored in "java.io.tmp"
   * @return A new file appender
   * @throws KettleFileException In case there is a problem opening the file.
   */
  public static final Log4jFileAppender createFileAppender( String filename, boolean exact ) throws KettleFileException {
    try {
      FileObject file;
      if ( !exact ) {
        file = KettleVFS.createTempFile( filename, ".log", System.getProperty( "java.io.tmpdir" ) );
      } else {
        file = KettleVFS.getFileObject( filename );
      }

      Log4jFileAppender appender = new Log4jFileAppender( file );
      appender.setLayout( new Log4jKettleLayout( true ) );
      appender.setName( LogWriter.createFileAppenderName( filename, exact ) );

      return appender;
    } catch ( IOException e ) {
      throw new KettleFileException( "Unable to add Kettle file appender to Log4J", e );
    }
  }

  /**
   * Create a file appender
   * @param filename The (VFS) filename (URL) to write to.
   * @param exact is this an exact filename of a filename to be stored in "java.io.tmp"
   * @param append
   * @return A new file appender
   * @throws KettleFileException In case there is a problem opening the file.
   */
  public static final Log4jFileAppender createFileAppender( String filename, boolean exact, boolean append ) throws KettleFileException {
    try {
      FileObject file;
      if ( !exact ) {
        file = KettleVFS.createTempFile( filename, ".log", System.getProperty( "java.io.tmpdir" ) );
      } else {
        file = KettleVFS.getFileObject( filename );
      }

      Log4jFileAppender appender = new Log4jFileAppender( file, append );
      appender.setLayout( new Log4jKettleLayout( true ) );
      appender.setName( LogWriter.createFileAppenderName( filename, exact ) );

      return appender;
    } catch ( IOException e ) {
      throw new KettleFileException( "Unable to add Kettle file appender to Log4J", e );
    }
  }

  public static final String createFileAppenderName( String filename, boolean exact ) {
    if ( !exact ) {
      return "<temp file> : " + filename;
    } else {
      return filename;
    }
  }

  /**
   *
   * @return a new String appender object capable of capturing the log stream.
   * It starts to work the instant you add this appender to the Kettle logger.
   *
   * @deprecated Please use {@link CentralLogStore.getAppender()} instead.  This uses a central logging buffer in stead of a distributed one.
   * It also supports incremental buffer gets, and much more.
   */
  @Deprecated
  public static final Log4jStringAppender createStringAppender() {
    Log4jStringAppender appender = new Log4jStringAppender();
    appender.setLayout( new Log4jKettleLayout( true ) );

    return appender;
  }

  public static void setConsoleAppenderDebug() {
    Enumeration<?> appenders = Logger.getLogger( STRING_PENTAHO_DI_LOGGER_NAME ).getAllAppenders();

    while ( appenders.hasMoreElements() ) {
      Object appender = appenders.nextElement();
      if ( appender instanceof ConsoleAppender || appender instanceof Log4jConsoleAppender ) {
        if ( appender instanceof ConsoleAppender ) {
          ( (ConsoleAppender) appender ).setThreshold( Level.DEBUG );
        }
      }
    }
  }

  public int getType() {
    return type;
  }

  public void setType( int type ) {
    this.type = type;
  }

  public boolean close() {
    boolean isNotEmbedded = true;
    try {
      // Close all appenders only if we are not embedded (ie. running a report in BA Server
      // that has a PDI data source is considered embedded)
      Logger logger = Logger.getLogger( STRING_PENTAHO_DI_LOGGER_NAME );
      Enumeration<?> appenders = logger.getAllAppenders();
      while ( appenders.hasMoreElements() ) {
        final Appender appender = (Appender) appenders.nextElement();
        // Check to see if we have registered BA Server appenders
        if ( ( appender.getName().compareTo( STRING_PENTAHO_BASERVER_FILE_APPENDER ) == 0 )
          || ( appender.getName().compareTo( STRING_PENTAHO_BASERVER_CONSOLE_APPENDER ) == 0 ) ) {
          isNotEmbedded = false;
          break;
        }
      }

      // If we are not embedded, we can safely close all appenders.
      if ( isNotEmbedded == true ) {
        pentahoLogger.removeAllAppenders();
        LogWriter.unsetLogWriter();
      }
    } catch ( Exception e ) {
      isNotEmbedded = false;
    }

    return isNotEmbedded;
  }

  // synchronizing logWriter singleton instance PDI-6840
  private static synchronized void unsetLogWriter() {
    logWriter = null;
  }

  /**
   *
   * @param logMessage
   * @param channelLogLevel
   */
  public void println( LogMessageInterface logMessage, LogLevel channelLogLevel ) {
    String subject = null;

    LogLevel logLevel = logMessage.getLevel();

    if ( !logLevel.isVisible( channelLogLevel ) ) {
      return; // not for our eyes.
    }

    if ( subject == null ) {
      subject = "Kettle";
    }

    // Are the message filtered?
    //
    if ( !logLevel.isError() && !Utils.isEmpty( filter ) ) {
      if ( subject.indexOf( filter ) < 0 && logMessage.toString().indexOf( filter ) < 0 ) {
        return; // "filter" not found in row: don't show!
      }
    }

    switch ( logLevel ) {
      case ERROR:
        pentahoLogger.error( logMessage );
        break;
      case ROWLEVEL:
      case DEBUG:
        pentahoLogger.debug( logMessage );
        break;
      default:
        pentahoLogger.info( logMessage );
        break;
    }
  }

  public void println( LogMessageInterface message, Throwable e, LogLevel channelLogLevel ) {
    println( message, channelLogLevel );

    String stackTrace = Const.getStackTracker( e );
    LogMessage traceMessage = new LogMessage( stackTrace, message.getLogChannelId(), LogLevel.ERROR );
    println( traceMessage, channelLogLevel );
  }

  public void setFilter( String filter ) {
    this.filter = filter;
  }

  public String getFilter() {
    return filter;
  }

  /**
   * This is not thread safe: please try to get the file appender yourself using the static constructor and work from there
   */
  public InputStream getFileInputStream() throws IOException {
    return KettleVFS.getInputStream( fileAppender.getFile() );
  }

  /**
   * This is not thread safe: please try to get the file appender yourself using the static constructor and work from there
   */
  public FileObject getFileAppenderFile() throws IOException {
    return fileAppender.getFile();
  }

  /**
   * Get the file input stream for a certain appender.
   * The appender is looked up using the filename
   * @param filename The exact filename (with path: c:\temp\logfile.txt) or just a filename (spoon.log)
   * @param exact true if this is the exact filename or just the last part of the complete path.
   * @return The file input stream of the appender
   * @throws IOException in case the appender ocan't be found
   */
  public FileInputStream getFileInputStream( String filename, boolean exact ) throws IOException {
    Appender appender = pentahoLogger.getAppender( createFileAppenderName( filename, exact ) );
    if ( appender == null ) {
      throw new IOException( "Unable to find appender for file: " + filename + " (exact=" + exact + ")" );
    }
    return new FileInputStream( ( (Log4jFileAppender) appender ).getFile().getName().getPathDecoded() );
  }

  /**
   * @return Returns the realFilename.
   */
  public File getRealFilename() {
    return realFilename;
  }

  /**
   * @param realFilename The realFilename to set.
   */
  public void setRealFilename( File realFilename ) {
    this.realFilename = realFilename;
  }

  public void addAppender( Appender appender ) {
    pentahoLogger.addAppender( appender );
  }

  public void removeAppender( Appender appender ) {
    pentahoLogger.removeAppender( appender );
  }

  public static void setLayout( Layout layout ) {
    LogWriter.layout = layout; // save for later creation of new files...

    Enumeration<?> appenders = logWriter.pentahoLogger.getAllAppenders();
    while ( appenders.hasMoreElements() ) {
      Appender appender = (Appender) appenders.nextElement();
      if ( appender instanceof Log4jConsoleAppender || appender instanceof Log4jFileAppender ) {
        appender.setLayout( layout );
      }
    }
  }

  public static Layout getLayout() {
    return layout;
  }

  public Logger getPentahoLogger() {
    return pentahoLogger;
  }
}
