/*
 * Copyright (C) The MX4J Contributors.
 * All rights reserved.
 *
 * This software is distributed under the terms of the MX4J License version 1.0.
 * See the terms of the MX4J License in the documentation provided with this software.
 */

package mx4j.log;

import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.Map;
import javax.management.RuntimeOperationsException;

import mx4j.MX4JSystemKeys;

/**
 * Main class for the log service. <p>
 * The system property 'mx4j.log.priority' controls the priority of the standard logging, and defaults to 'warn'.
 * Possible values are, from least to greatest priority, the following (case insensitive):
 * <ul>
 * <li>trace
 * <li>debug
 * <li>info
 * <li>warn
 * <li>error
 * <li>fatal
 * </ul>
 *
 * @version $Revision: 1.7 $
 */
public class Log
{
   private static Logger m_prototype;
   private static Map m_prototypeMap = new HashMap();
   private static Map m_loggerCache = new HashMap();
   private static int m_defaultPriority;

   static
   {
      // Do not require callers up in the stack to have this permission
      String priority = (String)AccessController.doPrivileged(new PrivilegedAction()
      {
         public Object run()
         {
            return System.getProperty(MX4JSystemKeys.MX4J_LOG_PRIORITY);
         }
      });
      if ("trace".equalsIgnoreCase(priority))
      {
         m_defaultPriority = Logger.TRACE;
      }
      else if ("debug".equalsIgnoreCase(priority))
      {
         m_defaultPriority = Logger.DEBUG;
      }
      else if ("info".equalsIgnoreCase(priority))
      {
         m_defaultPriority = Logger.INFO;
      }
      else if ("warn".equalsIgnoreCase(priority))
      {
         m_defaultPriority = Logger.WARN;
      }
      else if ("error".equalsIgnoreCase(priority))
      {
         m_defaultPriority = Logger.ERROR;
      }
      else if ("fatal".equalsIgnoreCase(priority))
      {
         m_defaultPriority = Logger.FATAL;
      }
      else
      {
         m_defaultPriority = Logger.INFO;
      }

      String prototype = (String)AccessController.doPrivileged(new PrivilegedAction()
      {
         public Object run()
         {
            return System.getProperty(MX4JSystemKeys.MX4J_LOG_PROTOTYPE);
         }
      });
      if (prototype != null && prototype.trim().length() > 0)
      {
         try
         {
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            Class cls = cl.loadClass(prototype);
            redirectTo((Logger)cls.newInstance());
         }
         catch (Exception x)
         {
            x.printStackTrace();
            // Do nothing else: the user will see the exception trace
            // and understand that the property was wrong
         }
      }
   }

   private Log()
   {
   }

   /**
    * Sets the default priority for all loggers.
    *
    * @see #setDefaultPriority
    */
   public static void setDefaultPriority(int priority)
   {
      switch (priority)
      {
         case Logger.TRACE:
            m_defaultPriority = Logger.TRACE;
            break;
         case Logger.DEBUG:
            m_defaultPriority = Logger.DEBUG;
            break;
         case Logger.INFO:
            m_defaultPriority = Logger.INFO;
            break;
         case Logger.WARN:
            m_defaultPriority = Logger.WARN;
            break;
         case Logger.ERROR:
            m_defaultPriority = Logger.ERROR;
            break;
         case Logger.FATAL:
            m_defaultPriority = Logger.FATAL;
            break;
         default:
            m_defaultPriority = Logger.WARN;
            break;
      }
   }

   /**
    * Returns the default priority.
    *
    * @see #setDefaultPriority
    */
   public static int getDefaultPriority()
   {
      return m_defaultPriority;
   }

   /**
    * Returns a new instance of a Logger associated with the given <code>category</code>;
    * if {@link #redirectTo} has been called then a new instance of the prototype Logger, associated with the given
    * <code>category<code>, is returned. This requires the prototype Logger class to have a public parameterless
    * constructor.
    */
   public static Logger getLogger(String category)
   {
      if (category == null)
      {
         throw new RuntimeOperationsException(new IllegalArgumentException("Category cannot be null"));
      }

      synchronized (m_loggerCache)
      {
         Logger logger = (Logger)m_loggerCache.get(category);
         if (logger == null)
         {
            // Try to see if a delegate for this category overrides other settings
            Logger prototype = null;
            synchronized (m_prototypeMap)
            {
               prototype = (Logger)m_prototypeMap.get(category);
            }
            if (prototype == null)
            {
               // Try to see if a prototype for all categories has been set
               if (m_prototype != null)
               {
                  logger = createLogger(m_prototype, category);
               }
               else
               {
                  logger = createLogger(null, category);
               }
            }
            else
            {
               logger = createLogger(prototype, category);
            }

            // cache it
            m_loggerCache.put(category, logger);
         }
         return logger;
      }
   }

   private static Logger createLogger(Logger prototype, String category)
   {
      Logger logger = null;
      try
      {
         logger = prototype == null ? new Logger() : (Logger)prototype.getClass().newInstance();
      }
      catch (Exception x)
      {
         x.printStackTrace();
         logger = new Logger();
      }
      logger.setCategory(category);
      logger.setPriority(m_defaultPriority);
      return logger;
   }

   /**
    * Tells to the log service to use the given <code>delegate</code> Logger to perform logging. <br>
    * Use a null delegate to remove redirection.
    *
    * @see #getLogger
    */
   public static void redirectTo(Logger prototype)
   {
      m_prototype = prototype;

      // Clear the cache, as we want requests for new loggers to be generated again.
      synchronized (m_loggerCache)
      {
         m_loggerCache.clear();
      }
   }

   /**
    * Tells to the log service to use the given <code>delegate</code> Logger to perform logging for the given
    * category (that cannot be null). <br>
    * Settings made using this method overrides the ones made with {@link #redirectTo(Logger) redirectTo}, meaning
    * that it is possible to redirect all the log to a certain delegate but certain categories.
    * Use a null delegate to remove redirection for the specified category.
    *
    * @see #getLogger
    */
   public static void redirectTo(Logger prototype, String category)
   {
      if (category == null)
      {
         throw new RuntimeOperationsException(new IllegalArgumentException("Category cannot be null"));
      }

      if (prototype == null)
      {
         // Remove the redirection
         synchronized (m_prototypeMap)
         {
            m_prototypeMap.remove(category);
         }

         // Clear the cache for this category
         synchronized (m_loggerCache)
         {
            m_loggerCache.remove(category);
         }
      }
      else
      {
         // Put or replace
         synchronized (m_prototypeMap)
         {
            m_prototypeMap.put(category, prototype);
         }

         // Clear the cache for this category
         synchronized (m_loggerCache)
         {
            m_loggerCache.remove(category);
         }
      }
   }
}
