/*
 * Copyright (c) 2006-2008 MiniMe. Code released under The MIT/X Window System
 * License. Full license text can be found in license.txt
 */
package minime;

import java.util.Enumeration;
import java.util.Vector;
import java.util.Hashtable;
import javax.microedition.lcdui.*;
import javax.microedition.midlet.MIDlet;

import minime.core.Runtime;

import java.io.*;

/**
 * The Logger class is used to record debug logs, First, it should create a file
 * name log4mm.properties in folder rsc, the log4mm.properties file will be like this:
 *      minime.Composite=DEBUG
 *      #minime.ui.GraphicMenu=DEBUG
 *      #minime.EditField=DEBUG
 *      ......
 *      minime.ui.Icon=DEBUG
 *      minime.ui.Menu=DEBUG
 * 
 * the value left to "=" is the class name,the value right to "=" can be one of DEBUG,
 * INFO,WARN,ERROR, QUIET.DEBUG will record debug information, INFO will record program
 * running information, WARN will record warning information, ERROR will record error
 * information, QUIET will record nothing.If want to don't record info. one class, just
 * add a prefix "#" at the according class name in log4mm.properties, for example, 
 * #minime.EditField=DEBUG will don't record debugging information in minime.EditField class.
 * 
 * @author aaa
 *
 */
public class Logger
{
    private static final int DEBUG = 4;
    private static final int INFO = 3;
    private static final int WARN = 2;
    private static final int ERROR = 1;
    private static final int QUIET = 0;
    private static final int NO_DEFINED = -1;
	
	public static final int DEFAULT_CLATHPATH_DISPLAY_DEEP=3;
    private static boolean bootstraped = false;
    private static Hashtable settings = null;
    private static final String PROPERTIES_FILE = "/rsc/log4mm.properties";
    private static LogStatementStore statementStore ;
    private static final String STR_GLOBAL_VERBOSITY = "global";
    //private static final String[] LABELS = { "", "ERROR", "WARN", "INFO", "DEBUG" };
    private static final String[] LABELS = { "", "E", "W", "I", "D" };
    
    private static Vector listeners ;
    private String classPath;
    private int globalVerbosity;
    private static int logIndex = 0;
    
    
    private Logger(String classPath )
    {
        this.classPath = classPath;
        globalVerbosity = Logger.getLogConfig(STR_GLOBAL_VERBOSITY).getVerbosity();
    }

    /**
     * Returns a Logger reference instance by given absolute class path, and this class
     * path should be be in log4mm.properties file, otherwise it can't log.
     * @param classPath 
     *              the class path,it's an absolute path.
     * 
     * @return a Logger reference instance.
     */
    public static Logger getLogger(String classPath, int logDeep)
    {
        if ( !bootstraped )
        {
            bootstrap();
        }
        if (statementStore==null)
            statementStore = new LogStatementStore(logDeep) ;
        if (listeners == null)
        	listeners = new Vector();
        return new Logger( classPath );
    }
    
    public static Logger getLogger(String classPath)
    {
    	return getLogger(classPath, LogStatementStore.DEFAULT_LOG_STORE_SIZE);
    }
    
    public static void addLogListener(LoggerListener listener)
    {
    	if(listener != null)
    		listeners.addElement(listener);
    }
    
    public static void removeLogListener(LoggerListener listener)
    {
    	if( listener != null)
    		listeners.removeElement(listener);
    }
    
    public static LogStatementStore getLogStatementStore()
    {
        return  statementStore ;
    }
    
    static private String b = "";
    
    /**
     * Initializes Logger instance the first time it's called.
     */
    private static void bootstrap()
    {
        settings = new Hashtable();
        System.out.println( "[Logger] reading : " + PROPERTIES_FILE );
        InputStream in = b.getClass().getResourceAsStream( PROPERTIES_FILE ); // TODO pass the file name with a ant property
        System.out.println( "[Logger] in : " + in );
        if ( in != null )
        {
            InputStreamReader reader = new InputStreamReader( in );
            try
            {
                String line = Util.readLine( reader );
                System.out.println( "[Logger] line : " + line );

                while ( line != null )
                {
                    String[] nameValue = Util.extractNameValue( line );
                    if ( nameValue != null )
                    {
                        String name = nameValue[0];
                        String valueVerbosity = nameValue[1];
                        String shortName = nameValue[2];
                        int verbosity = DEBUG;
                        if ( valueVerbosity.equals( "DEBUG" ) )
                        {
                            verbosity = DEBUG;
                        }
                        else if ( valueVerbosity.equals( "INFO" ) )
                        {
                            verbosity = INFO;
                        }
                        else if ( valueVerbosity.equals( "WARN" ) )
                        {
                            verbosity = WARN;
                        }
                        else if ( valueVerbosity.equals( "ERROR" ) )
                        {
                            verbosity = ERROR;
                        }
                        else if ( valueVerbosity.equals( "QUIET" ) )
                        {
                            verbosity = QUIET;
                        }
                        else
                        {
                            System.out.println( "[Logger] invalid verbosity level : " + line );
                        }

                        settings.put( name,  new LogConfig( verbosity, shortName) );
                        System.out.println( "[Logger] setVerbosityLevel for " + name + ":" + verbosity);
                    }
                    else
                    {
                        System.out.println( "[Logger] malformed line : " + line );
                    }
                    line = Util.readLine( reader );
                    System.out.println( "[Logger] line : " + line );
                }
            }
            catch ( IOException ioe )
            {
                System.out.println( "[Logger] Exception occured while reading log4mm.properties" );
            }
        }
        
        bootstraped = true;
    }


    private static LogConfig getLogConfig( String id )
    {
        LogConfig logConfig = (LogConfig) settings.get( id );
        while ( logConfig == null )
        {
            int dot = id.lastIndexOf( '.' );
            if ( dot != -1 )
            {
                id = id.substring( 0, dot );
            }
            else
            {
                break;
            }
            logConfig = (LogConfig) settings.get( id );
        }
        if ( null == logConfig )
        {
            return new LogConfig(NO_DEFINED, id);
        }
        else
        {
            return logConfig;
        }
    }
    
    
//    private String getSubClassPathName(String classPath, int deep)
//    {
//    	if(deep < 1 || classPath.length() == 0)
//    		return "";
//    	int index = classPath.length();
//    	while(deep>0)
//    	{
//    		index = classPath.substring(0,index).lastIndexOf('.');
//    		if(index != -1)
//    		{
//    			deep--;   			
//    		}
//    		else
//    		{
//    			break;
//    		}
//    	}
//    	return classPath.substring(index+1);
//    }
    /**
     * Logs the message based on the severity type, the type should be one of DEBUG,
     * INFO, WARN, ERROR.
     * 
     * @param severity 
     *              the logger type, one of DEBUG,INFO, WARN, ERROR.
     * @param msg
     *              the log message.
     */
    public void log( int severity, String msg )
    {
        LogConfig logConfig =  Logger.getLogConfig( classPath );
        if ( NO_DEFINED == logConfig.getVerbosity() )
        {
            logConfig.setVerbosity(globalVerbosity);
        }

        if ( severity > QUIET && severity <= logConfig.getVerbosity() )
        {
        	logIndex++;
        	String shortName = logConfig.getShortName();
        	//String subClassPath =getSubClassPathName(classPath, logConfig.getDisplayDeep());
            String statement = logIndex+" "+shortName + " - " + LABELS[severity] + ":" + msg;
            System.out.println( statement );
            statementStore.addLogString(statement) ;
            for(Enumeration e=listeners.elements(); e.hasMoreElements(); )
            {
            	LoggerListener listener = (LoggerListener)e.nextElement();
            	if(listener != null)
            		listener.notifyLogString();
            }
        }
    }

    /**
     * Logs the DEBUG type message.
     * @param msg
     *              the debugging message.
     */
    public void debug( String msg )
    {
        log( DEBUG, msg );
    }

    /**
     * Logs the INFO type message.
     * @param msg
     *              the INFO type message.
     */
    public void info( String msg )
    {
        log( INFO, msg );
    }

    /**
     * Logs the WARN type message.
     * @param msg
     *              the warning message
     */
    public void warn( String msg )
    {
        log( WARN, msg );
    }

    /**
     * Logs the ERROR type message.
     * @param msg
     *              the error message
     */
    public void error( String msg )
    {
        log( ERROR, msg );
    }
	public static String getAllString() {
		return statementStore.getAllString();
	}
	
	static class LogConfig
	{
		private int verbosity;
		private String shortName;
		public LogConfig(int verbosity, String shortName)
		{
			this.verbosity = verbosity;
			this.shortName = shortName;
		}
		public int getVerbosity() {
			return verbosity;
		}
		public String getShortName() {
			return shortName;
		}
		public void setVerbosity(int verbosity) {
			this.verbosity = verbosity;
		}
		public void setShortName(String shortName) {
			this.shortName = shortName;
		}
	}
	
	/***
	 * The LogStatementStore used to store the log in a cycle string array. you can specify logDeep, if the logDeep
	 * you specified is greater than MAX_LOG_DEEP, it will use MAX_LOG_DEEP. 
	 * 
	 * @author David
	 *
	 */
	
    static class LogStatementStore
    {
    	private static final int DEFAULT_LOG_STORE_SIZE = 40;
    	private static final int MAX_LOG_STORE_SIZE = 200;
        private String[] loggerStrings;
        private int currentIndex=0;
        
        public LogStatementStore(int size)
        {
        	if(size > MAX_LOG_STORE_SIZE)
        		size = MAX_LOG_STORE_SIZE;
        	loggerStrings = new String[size] ;
        }
        
        public void addLogString(String str)
        {
            loggerStrings[currentIndex]=str;
            currentIndex = (currentIndex+1) % loggerStrings.length;
        }
        public String getAllString()
        {
        	StringBuffer sb = new StringBuffer();
        	int len = loggerStrings.length;
        	for( int i=currentIndex;  i<currentIndex+len; i++)
        	{
        		String elem = loggerStrings[i%loggerStrings.length];
        		if(elem != null)
        		{
        			sb.append(elem );
        			if( !elem.endsWith("\n"))
        				sb.append("\n");
        		}
        	}
        	return sb.toString();
        }
    }
}

