/*
 * 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.File;
import java.io.FileWriter;
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 sTimeStampFormat = "HH:mm:ss.SSS ";
	
	private static final String CFG_ENABLELOGTOFILE = "Logging Enable";

	private boolean bLogToFile = 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 = config.getBooleanParameter(CFG_ENABLELOGTOFILE);
		if (bNewLogToFile != bLogToFile) {
			bLogToFile = bNewLogToFile;
			if (bLogToFile)
				Logger.addListener(this);
			else
				Logger.removeListener(this);
		}
	}

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

			boolean overrideLog = System.getProperty("azureus.overridelog") != null;
			if (overrideLog) {
				bLogToFile = true;
				sLogDir = ".";
				iLogFileMaxMB = 2;

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

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

				iLogFileMaxMB = config.getIntParameter("Logging Max Size");

				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]);
					}
				}
			}
		} catch (Throwable t) {
			Debug.printStackTrace(t);
		}
	}

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

		synchronized (Logger.class) {

			SimpleDateFormat format;
			format = new SimpleDateFormat(sTimeStampFormat);

			str = format.format(new Date()) + str;

			PrintWriter pw = null;

			File file_name = new File(sLogDir + File.separator + LOG_FILE_NAME);

			try {
				pw = new PrintWriter(new FileWriter(file_name, true));
				if (pw != null)
					pw.print(str);

			} catch (Throwable e) {

				// can't log this as go recursive!!!!

			} finally {

				if (pw != null) {
					try {
						pw.close();
					} catch (Throwable e) {
						// can't log as go recursive!!!!
					}

					// two files so half
					long lMaxBytes = (iLogFileMaxMB * 1024 * 1024) / 2;

					if (file_name.length() > lMaxBytes) {
						File back_name = new File(sLogDir + File.separator + BAK_FILE_NAME);

						if ((!back_name.exists()) || back_name.delete()) {
							if (!file_name.renameTo(back_name))
								file_name.delete();
						} else {
							file_name.delete();
						}
					}
				}
			} // finally
		} // sync
	}

	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();
		
		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 == null || !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;
	}
}
