/*
 * Copyright (C) 2005, 2006 Aelitis, All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * AELITIS, SAS au capital de 46,603.30 euros
 * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
 * 
 */
package org.gudy.azureus2.core3.logging.impl;

import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;

import org.gudy.azureus2.core3.config.ParameterListener;
import org.gudy.azureus2.core3.config.impl.ConfigurationManager;
import org.gudy.azureus2.core3.internat.MessageText;
import org.gudy.azureus2.core3.logging.ILogAlertListener;
import org.gudy.azureus2.core3.logging.ILogEventListener;
import org.gudy.azureus2.core3.logging.LogAlert;
import org.gudy.azureus2.core3.logging.LogEvent;
import org.gudy.azureus2.core3.logging.LogIDs;
import org.gudy.azureus2.core3.logging.Logger;
import org.gudy.azureus2.core3.util.AEDiagnostics;
import org.gudy.azureus2.core3.util.AEDiagnosticsLogger;
import org.gudy.azureus2.core3.util.Debug;

/**
 * Logging tool
 * 
 * @author TuxPaper
 * @since 2.3.0.7
 */

public class LoggerImpl {
	private final int MAXHISTORY = 256;

	private boolean bLogToStdOut = System.getProperty("azureus.log.stdout") != null;

	private boolean bEventLoggingEnabled = false;

	private PrintStream psOldOut = null;

	private PrintStream psOldErr = null;

	private PrintStream psOut;

	private PrintStream psErr;

	private List logListeners = new ArrayList();

	private AEDiagnosticsLogger alertLogger;

	private List alertListeners = new ArrayList();

	private List alertHistory = new ArrayList();
	
	private boolean logToStdErrAllowed = true;

	/**
	 * Initializes the Logger and sets up a file logger.
	 */
	public LoggerImpl() {
		doRedirects();
	}

	/**
	 * Must be seperate from constructor, because the code may call a Logger.*
	 * method, which requires a loggerImpl to be not null.
	 *
	 */
	public void init() {
		// temporarily set to true, to log any errors between now and setting
		// bEnabled properly.
		bEventLoggingEnabled = true;
		
		// Shorten from COConfigurationManager To make code more readable
		final ConfigurationManager config = ConfigurationManager.getInstance();

		boolean overrideLog = System.getProperty("azureus.overridelog") != null;
		if (overrideLog) {
			bEventLoggingEnabled = true;
		} else {
			bEventLoggingEnabled = config.getBooleanParameter("Logger.Enabled");

			config.addParameterListener("Logger.Enabled", new ParameterListener() {
				public void parameterChanged(String parameterName) {
					bEventLoggingEnabled = config.getBooleanParameter("Logger.Enabled");
				}
			});
		}
	}

	/**
	 * Set up stdout/stderr redirects
	 */
	public void doRedirects() {
		try {
			if (System.out != psOut) {
				if (psOldOut == null)
					psOldOut = System.out;

				psOut = new PrintStream(new RedirectorStream(psOldOut, LogIDs.STDOUT,
						LogEvent.LT_INFORMATION));

				System.setOut(psOut);
			}

			if (System.err != psErr) {
				if (psOldErr == null)
					psOldErr = System.err;

				psErr = new PrintStream(new RedirectorStream(psOldErr, LogIDs.STDERR,
						LogEvent.LT_ERROR));

				System.setErr(psErr);
			}

		} catch (Throwable e) {
			Debug.printStackTrace(e);
		}
	}

	public boolean isEnabled() {
		return bEventLoggingEnabled;
	}

	/**
	 * Redirects any incoming text to the logger
	 */
	private class RedirectorStream extends OutputStream {
		protected PrintStream ps;

		protected StringBuffer buffer = new StringBuffer(1024);

		protected LogIDs logID;

		protected int logType;

		protected RedirectorStream(PrintStream _ps, LogIDs _logID, int _logType) {
			ps = _ps;
			logType = _logType;
			logID = _logID;
		}

		public void write(int data) {
			char c = (char) data;

			if (c == '\n') {
				if (!bLogToStdOut) {
					ps.println(buffer);
				}
				log(new LogEvent(logID, logType, buffer.toString()));
				buffer.setLength(0);
			} else if (c != '\r') {
				buffer.append(c);
			}
		}

		public void write(byte b[], int off, int len) {
			for (int i = off; i < off + len; i++) {
				int d = b[i];
				if (d < 0)
					d += 256;
				write(d);
			}
		}
	}

	// Log Event Functions
	// ===================

	/**
	 * Log an event
	 * 
	 * @param event
	 *            event to log
	 */
	public void log(LogEvent event) {
		
		/**
		 * This highlights bits of code which log, but don't bother
		 * to check whether logging is enabled or not.
		 */ 
		//if (!bEventLoggingEnabled) {
		//	new Exception("No logging check done!").printStackTrace(psOldErr);
		//}
		
		if (bLogToStdOut && psOldOut != null)
			psOldOut.println(event.text);

		if (event.entryType == LogEvent.LT_ERROR) {
			if ( AEDiagnostics.isStartupComplete()){
					// more recursive horrors here if we try and log too early
				Debug.outDiagLoggerOnly("[" + event.logID + "] " + event.text);
			}
			if (logToStdErrAllowed && psOldErr != null && event.logID != LogIDs.STDERR) {
				psOldErr.println("[" + event.logID + "] " + event.text);
			}
		}
		if (bEventLoggingEnabled)
			for (int i = 0; i < logListeners.size(); i++) {
				try {
					Object listener = logListeners.get(i);
					if (listener instanceof ILogEventListener)
						((ILogEventListener) listener).log(event);
				} catch (Throwable e) {
					if (logToStdErrAllowed && psOldErr != null) {
						psOldErr.println("Error while logging: " + e.getMessage());
						e.printStackTrace(psOldErr);
					}
				}
			}

		// Write error to stderr, which will eventually get back here
		if (event.err != null && event.entryType == LogEvent.LT_ERROR){
			Debug.printStackTrace(event.err);
		}
	}

	public void logTextResource(LogEvent event) {
		event.text = MessageText.getString(event.text);
		log(event);
	}

	public void logTextResource(LogEvent event, String params[]) {
		event.text = MessageText.getString(event.text, params);
		log(event);
	}

	public void addListener(ILogEventListener aListener) {
		logListeners.add(aListener);
	}

	public void removeListener(ILogEventListener aListener) {
		logListeners.remove(aListener);
	}

	// Log Alert Functions
	// ===================

	public void log(LogAlert alert) {
		String logText = "Alert:" + alert.entryType + ":" + alert.text;

		// Log all Alerts as Events
		LogEvent alertEvent = new LogEvent(LogIDs.ALERT, alert.entryType,
				logText);
		alertEvent.err = alert.err;
		Logger.log(alertEvent);

		synchronized (this) {
			if (alertLogger == null) {
				alertLogger = AEDiagnostics.getLogger("alerts");
			}
		}
		
		Throwable error = alert.getError();
		
		if ( error != null ){
			
			logText += " (" + Debug.getNestedExceptionMessage( error ) + ")";
		}
		
		alertLogger.log(logText);

		alertHistory.add(alert);

		if (alertHistory.size() > MAXHISTORY)
			alertHistory.remove(0);

		for (int i = 0; i < alertListeners.size(); i++) {
			try {
				Object listener = alertListeners.get(i);
				if (listener instanceof ILogAlertListener)
					((ILogAlertListener) listener).alertRaised(alert);
			} catch (Throwable f) {
				if (psOldErr != null) {
					psOldErr.println("Error while alerting: " + f.getMessage());
					f.printStackTrace(psOldErr);
				}
			}
		}
	}

	public void logTextResource(LogAlert alert) {
		alert.text = MessageText.getString(alert.text);
		log(alert);
	}

	public void logTextResource(LogAlert alert, String params[]) {
		alert.text = MessageText.getString(alert.text, params);
		log(alert);
	}

	public void addListener(ILogAlertListener l) {
		alertListeners.add(l);

		for (int i = 0; i < alertHistory.size(); i++) {
			LogAlert alert = (LogAlert) alertHistory.get(i);
			l.alertRaised(alert);
		}
	}

	public void removeListener(ILogAlertListener l) {
		alertListeners.remove(l);
	}
	
	public PrintStream getOldStdErr() {
		return psOldErr;
	}
	
	public void allowLoggingToStdErr(boolean allowed) {
		this.logToStdErrAllowed = allowed;
	}
}
