/*
 * Created on 22-Sep-2004
 * Created by Paul Gardner
 * 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.util;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.LinkedList;
import java.util.TimeZone;

import org.gudy.azureus2.core3.logging.Logger;

/**
 * @author parg
 *
 */
public class 
AEDiagnosticsLogger 
{
	private static final int	MAX_PENDING = 8*1024;
	
	private String 			name;
	private int				max_size;
	private File			debug_dir;
	private boolean			timestamp_enable				= true;
	private boolean			force;
	
	private boolean			first_file				= true;
	private boolean			first_write			 	= true;
	private PrintWriter		current_writer;
	
	private LinkedList<StringBuilder>	pending;
	private int							pending_size;
	private boolean						direct_writes;
	
	private boolean		close_pws = false;
	
	private static final String	start_date;
	private static final long	timezone_offset;
	
	static{
		long		now = System.currentTimeMillis();
		
		start_date = new SimpleDateFormat().format( new Date(now));
		
		timezone_offset = TimeZone.getDefault().getOffset(now);
	}
	
	protected
	AEDiagnosticsLogger(
		File		_debug_dir,
		String		_name,
		int			_max_size,
		boolean		_direct_writes )
	{
		debug_dir		= _debug_dir;
		name			= _name;
		max_size		= _max_size;
		direct_writes	= _direct_writes;
		
		try{
			File	f1 = getLogFile();
			
			first_file = false;
			
			File	f2 = getLogFile();
			
			first_file = true;

				// if we were writing to the second file, carry on from there
			
			if ( f1.exists() && f2.exists()){
	
				if ( f1.lastModified() < f2.lastModified()){
					
					first_file = false;
				}
			}
		}catch( Throwable ignore ){
			
		}
	}
	
	public void
	setForced(
		boolean		_force )
	{
		force = _force;
	}
	
	public boolean
	isForced()
	{
		return( force );
	}
	
	protected String
	getName()
	{
		return( name );
	}
		
	public void
	setMaxFileSize(
		int		_max_size )
	{
		max_size	= _max_size;
	}
	
	public void
	enableTimeStamp(
		boolean	enable )
	{
		timestamp_enable	= enable;
	}
	
	public void
	log(
		Throwable				e )
	{
		try{
			ByteArrayOutputStream	baos = new ByteArrayOutputStream();
			
			PrintWriter	pw = new PrintWriter( new OutputStreamWriter( baos ));
			
			e.printStackTrace( pw );
			
			pw.close();
			
			log( baos.toString());
			
		}catch( Throwable ignore ){
			
		}
	}
	
	public void
	logAndOut(
		String		str )
	{
		logAndOut( str, false );
	}
	
	public void
	logAndOut(
		String		str,
		boolean		stderr )
	{
		if ( stderr ){
			
			System.err.println( str );

			// Logger dumps the stderr, but if it's not setup, do it outselves
			if (Logger.getOldStdErr() == null) {
				log( str );
			}

		}else{
			
			System.out.println( str );
			log( str );
		}
		
	}
	
	public void
	logAndOut(
		Throwable 	e )
	{
		e.printStackTrace();
		
		log( e );
	}
	
	/*
	public static String
	getTimestamp()
	{
		Calendar now = GregorianCalendar.getInstance();

		String timeStamp = "[" + format(now.get(Calendar.DAY_OF_MONTH))+format(now.get(Calendar.MONTH)+1) + " " + 
						format(now.get(Calendar.HOUR_OF_DAY))+ ":" + format(now.get(Calendar.MINUTE)) + ":" + format(now.get(Calendar.SECOND)) + "] ";        
		
		return( timeStamp );
	}
	*/
	
	public static String
	getTimestamp()
	{
		long time = SystemTime.getCurrentTime();
			
		time += timezone_offset;		// we'll live with this changing...
			
		time /= 1000;
			
	    int secs = (int)time % 60;
	    int mins = (int)(time / 60) % 60;
	    int hours = (int)(time /3600) % 24;
	
	    char[]	chars = new char[11];
	    
	    chars[0] = '[';
	    format( hours, chars, 1 );
	    chars[3] = ':';
	    format( mins, chars, 4 );
	    chars[6] = ':';
	    format( secs, chars, 7 );
	    chars[9] = ']';
	    chars[10] = ' ';
	    
		return( new String( chars ));
	}
	
	private static final void
	format(
		int		num,
		char[]	chars,
		int		pos )
	{
		if ( num < 10 ){
			chars[pos] = '0';
			chars[pos+1] =(char)( '0' + num );
		}else{
			chars[pos] 		= (char)('0' + (num/10));
			chars[pos+1]	= (char)('0' + (num%10));
		}
	}
	
	public void
	log(
		String	_str )
	{
		if ( !AEDiagnostics.loggers_enabled ){
			
			if ( !force ){
			
				return;
			}
		}
		
		StringBuilder str = new StringBuilder( _str.length() + 20 );
		
		final String timeStamp;
		
		if ( timestamp_enable ){	
			
			timeStamp = getTimestamp();
			
		}else{
			
			timeStamp = null;
		}
		
		synchronized( this ){

			if ( first_write ){
				
				first_write = false;
				
				Calendar now = GregorianCalendar.getInstance();

				str.append( "\r\n[" );
				str.append( start_date );
				str.append( "] Log File Opened for " );
				str.append(  Constants.APP_NAME );
				str.append( " " );
				str.append(  Constants.AZUREUS_VERSION );
				str.append( "\r\n" );
			}
			
			if ( timeStamp != null ){
				
				str.append( timeStamp );
			}
			
			str.append( _str );
		
			if ( !direct_writes ){
				
				if ( pending == null ){
					
					pending = new LinkedList<StringBuilder>();
				}
				
				pending.add( str );
				
				pending_size += str.length();
				
				if ( pending_size > MAX_PENDING ){
					
					writePending();
				}
				
				return;
			}
				
			write( str );
		}
	}
	
	private void
	write(
		StringBuilder		str )
	{
		try{	
			File	log_file	= getLogFile();
			
				/**
				 *  log_file.length will return 0 if the file doesn't exist, so we don't need
				 *  to explicitly check for its existence.
				 */
			
			if ( log_file.length() >= max_size ){
				
				if ( current_writer != null ){
					
					current_writer.close();
					
					current_writer = null;
				}
				
				first_file = !first_file;
				
				log_file	= getLogFile();
			
					// If the file doesn't exist, this will just return false.
				
				log_file.delete();
			}
				
			if ( current_writer == null ){
			
				current_writer = new PrintWriter(new FileWriter( log_file, true ));
			}
			
			current_writer.println( str );
			
			current_writer.flush();
			
		}catch( Throwable e ){
			
		}finally{
			
			if ( current_writer != null && close_pws ){
									
				current_writer.close();
				
				current_writer = null;
			}
		}
	}
	
	protected void
	writePending()
	{
		synchronized( this ){
			
			if ( pending == null ){
				
				return;
			}
			
			// System.out.println( getName() + ": flushing " + pending_size );
						
			try{	
				File	log_file	= getLogFile();
				
					/**
					 *  log_file.length will return 0 if the file doesn't exist, so we don't need
					 *  to explicitly check for its existence.
					 */
				
				if ( log_file.length() >= max_size ){
					
					if ( current_writer != null ){
						
						current_writer.close();
						
						current_writer = null;
					}
					
					first_file = !first_file;
					
					log_file	= getLogFile();
				
						// If the file doesn't exist, this will just return false.
					
					log_file.delete();
				}
					
				if ( current_writer == null ){
					
					current_writer = new PrintWriter(new FileWriter( log_file, true ));
				}
				
				for ( StringBuilder str: pending ){
				
					current_writer.println( str );
				}
				
				current_writer.flush();
				
			}catch( Throwable e ){
				
			}finally{
				
				direct_writes 	= true;
				pending			= null;
				
				if ( current_writer != null && close_pws ){
					
					current_writer.close();
					
					current_writer = null;
				}
			}
		}
	}
	
	private File
	getLogFile()
	{
		return( new File( debug_dir, getName() + "_" + (first_file?"1":"2") + ".log" ));
	}
	
	private static String 
	format(
		int 	n ) 
	{
		if (n < 10){
	   	
			return( "0" + n );
	   }
		
	   return( String.valueOf(n));
	}
}
