/*
 * Copyright (C) Azureus Software, Inc, 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.
 * 
 */
package org.gudy.azureus2.core3.logging.impl;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import org.gudy.azureus2.core3.config.COConfigurationListener;
import org.gudy.azureus2.core3.config.ParameterListener;
import org.gudy.azureus2.core3.config.impl.ConfigurationManager;
import org.gudy.azureus2.core3.logging.*;
import org.gudy.azureus2.core3.util.Debug;

/**
 * Log events to a file.
 * 
 * @author TuxPaper
 */
// TODO: Filter
public class FileLogging implements ILogEventListener {
	public static final String LOG_FILE_NAME = "az.log";

	public static final String BAK_FILE_NAME = "az.log.bak";

	public static LogIDs[] configurableLOGIDs = {LogIDs.STDOUT, LogIDs.ALERT, LogIDs.CORE,
			LogIDs.DISK, LogIDs.GUI, LogIDs.NET, LogIDs.NWMAN, LogIDs.PEER,
			LogIDs.PLUGIN, LogIDs.TRACKER, LogIDs.CACHE, LogIDs.PIECES };
	
	private static final String CFG_ENABLELOGTOFILE = "Logging Enable";

	private boolean bLogToFile = false;
	private boolean bLogToFileErrorPrinted = false;

	private String sLogDir = "";

	private int iLogFileMaxMB = 1;

	// List of components we don't log.  
	// Array represents LogTypes (info, warning, error)
	private ArrayList[] ignoredComponents = new ArrayList[3];
	
	private ArrayList listeners = new ArrayList();

	public void initialize() {
		// Shorten from COConfigurationManager To make code more readable
		final ConfigurationManager config = ConfigurationManager.getInstance();
		boolean overrideLog = System.getProperty("azureus.overridelog") != null;

		for (int i = 0; i < ignoredComponents.length; i++) {
			ignoredComponents[i] = new ArrayList();
		}

		if (!overrideLog) {
			config.addListener(new COConfigurationListener() {
				public void configurationSaved() {
					checkLoggingConfig();
				}
			});
		}

		checkLoggingConfig();
		config.addParameterListener(CFG_ENABLELOGTOFILE, new ParameterListener() {
			public void parameterChanged(String parameterName) {
				FileLogging.this.reloadLogToFileParam();
			}
		});
	}

	/**
	 * 
	 */
	protected void reloadLogToFileParam() {
		final ConfigurationManager config = ConfigurationManager.getInstance();
		boolean bNewLogToFile = System.getProperty("azureus.overridelog") != null || config.getBooleanParameter(CFG_ENABLELOGTOFILE);
		if (bNewLogToFile != bLogToFile) {
			bLogToFile = bNewLogToFile;
			if (bLogToFile)
				Logger.addListener(this);
			else{
				Logger.removeListener(this);
				
				synchronized( Logger.class ){
					
						// close existing file
					
					checkAndSwapLog();
				}
			}
		}
	}

	private void checkLoggingConfig() {
		try {
			// Shorten from COConfigurationManager To make code more readable
			final ConfigurationManager config = ConfigurationManager.getInstance();
			
			String timeStampFormat;

			boolean overrideLog = System.getProperty("azureus.overridelog") != null;
			if (overrideLog) {
				
				// Don't set this - reloadLogToFileParam will do it.
				//bLogToFile = true;
				sLogDir = System.getProperty("azureus.overridelogdir", ".");
				iLogFileMaxMB = 2;
				timeStampFormat = "HH:mm:ss.SSS ";

				for (int i = 0; i < ignoredComponents.length; i++) {
					ignoredComponents[i].clear();
				}
				
				reloadLogToFileParam();
			} else {
				reloadLogToFileParam();

				sLogDir = config.getStringParameter("Logging Dir", "");

				iLogFileMaxMB = config.getIntParameter("Logging Max Size");
				
				timeStampFormat = config.getStringParameter("Logging Timestamp")+" ";

				for (int i = 0; i < ignoredComponents.length; i++) {
					ignoredComponents[i].clear();
					int logType = indexToLogType(i);
					for (int j = 0; j < configurableLOGIDs.length; j++) {
						if (!config.getBooleanParameter("bLog." + logType + "."
								+ configurableLOGIDs[j], true))
							ignoredComponents[i].add(configurableLOGIDs[j]);
					}
				}
			}
			
			synchronized (Logger.class) {
				// Create the date format first *before* we do checkAndSwapLog,
				// just in case we end up invoking logToFile...
				format = new SimpleDateFormat(timeStampFormat);
				checkAndSwapLog();
			}
			
			
		} catch (Throwable t) {
			Debug.printStackTrace(t);
		}
	}

	private void logToFile(String str) {
		if (!bLogToFile)
			return;

		String dateStr = format.format(new Date()); 
		
		synchronized (Logger.class) {

			// exception handling is done by FileWriter
			if(logFilePrinter != null)
			{
				logFilePrinter.print(dateStr);
				logFilePrinter.print(str);
				logFilePrinter.flush();
			}
			
			checkAndSwapLog();		
		} // sync
	}
	
	private SimpleDateFormat format;
	private PrintWriter logFilePrinter;
	
	private void checkAndSwapLog()
	{
		if (!bLogToFile)
		{
			if(logFilePrinter != null)
			{
				logFilePrinter.close();
				logFilePrinter = null;
			}
			return;
		}
			
		
		long lMaxBytes = (iLogFileMaxMB * 1024L * 1024L) / 2;
		File logFile = new File(sLogDir + File.separator + LOG_FILE_NAME);
		
		if (logFile.length() > lMaxBytes && logFilePrinter != null)
		{
			File back_name = new File(sLogDir + File.separator + BAK_FILE_NAME);
			logFilePrinter.close();
			logFilePrinter = null;

			if ((!back_name.exists()) || back_name.delete()){
				if (!logFile.renameTo(back_name)){
						// rename failed, just have to trash the existing one
					logFile.delete();
				}
			}else{
					// failed to delete existing backup, just have to trash existing log
				logFile.delete();
			}
		}
		
		if(logFilePrinter == null)
		{
			try
			{
				logFilePrinter = new PrintWriter(new FileWriter(logFile, true));
			} catch (IOException e)
			{
				if (!bLogToFileErrorPrinted) {
					
					// don't just log errors, as it would cause infinite recursion
					bLogToFileErrorPrinted = true;
					Debug.out("Unable to write to log file: " + logFile);
					Debug.printStackTrace(e);

					/*
					java.io.PrintStream stderr = Logger.getOldStdErr();
					stderr.println("Unable to write to log file: " + logFile);
					e.printStackTrace(stderr);
					*/
				}
				
			}
		}
	}

	private int logTypeToIndex(int entryType) {
		switch (entryType) {
			case LogEvent.LT_INFORMATION:
				return 0;
			case LogEvent.LT_WARNING:
				return 1;
			case LogEvent.LT_ERROR:
				return 2;
		}
		return 0;
	}

	private int indexToLogType(int index) {
		switch (index) {
			case 0:
				return LogEvent.LT_INFORMATION;
			case 1:
				return LogEvent.LT_WARNING;
			case 2:
				return LogEvent.LT_ERROR;
		}
		return LogEvent.LT_INFORMATION;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.gudy.azureus2.core3.logging.ILoggerListener2#log(org.gudy.azureus2.core3.logging.LogEvent)
	 */

	private final static int DEFPADDING = 100;
	private int lastWidth = DEFPADDING;
	public void log(LogEvent event) {
		if (ignoredComponents[logTypeToIndex(event.entryType)]
				.contains(event.logID))
			return;

		StringBuffer text = new StringBuffer(event.text.length());
		
		text.append(event.entryType).append(" ");

		padAndAppend(text, event.logID.toString(), 8, 1);

		//text.append("|");

		if (event.relatedTo != null) {
			lastWidth = padAndAppend(text, event.text, lastWidth, 1);
			if (lastWidth > 200)
				lastWidth = 200;

			for (int i = 0; i < event.relatedTo.length; i++) {
				Object obj = event.relatedTo[i];

				if (obj == null)
					continue;

				if (i > 0)
					text.append("; ");

				if (obj instanceof LogRelation) {
					text.append(((LogRelation) obj).getRelationText());
				} else {
					text.append("RelatedTo[")
					    .append(obj.toString())
					    .append("]");
				}
			}
		} else {
			text.append(event.text);

			lastWidth = DEFPADDING;
		}

		//text.append(event.text);

		if (!event.text.endsWith("\n"))
			text.append("\r\n");

		boolean okToLog = true;
		for (Iterator iter = listeners.iterator(); iter.hasNext() && okToLog;) {
			FileLoggingAdapter listener = (FileLoggingAdapter) iter.next();
			okToLog = listener.logToFile(event, text);
		}

		logToFile(text.toString());
	}

	private int padAndAppend(StringBuffer appendTo, String s, int width, int growBy) {
		if (s == null)
			s = "null";
		appendTo.append(s);

		int sLen = s.length();
		int len = width - sLen;
		while (len <= 0)
			len += growBy;
		
		char[] padding = new char[len];
		if (len > 5) {
			for (int i = 0; i < len; i += 2)
				padding[i] = ' ';
			for (int i = 1; i < len; i += 2)
				padding[i] = '.';
		} else {
			for (int i = 0; i < len; i++)
				padding[i] = ' ';
		}

		appendTo.append(padding);
		
		return len + sLen;
	}
	
	public void addListener(FileLoggingAdapter listener) {
		if (!listeners.contains(listener))
			listeners.add(listener);
	}
	
	public void removeListener(FileLoggingAdapter listener) {
		listeners.remove(listener);
	}
	
	public List getListeners() {
		return listeners;
	}
}
