using System;
using Microsoft.SPOT;

namespace Logger
{
    // Primary class responsbile for logging.  This class is a combination between a singleton
    // and a regular class to allow for the developers to have more customability.
    public class Logger
    {
        private static Logger logger;
        protected static string[] logLevelDesc = null;

        protected LoggerEventHandler[][] leh;
        protected uint max = 0;
        protected uint levels = 0;
        protected LoggerEventHandler defaultHandler = null;

        // With this constructor the developer is responsible for defining what
        // they want the logger to do (in the defaultHandler).
        public Logger(uint levels, LoggerEventHandler defaultHandler)
        {
            init(levels, defaultHandler);
        }

        // Opens up a standard to file logger with the specified number of log levels.
        public Logger(uint levels, string filename)
        {
            init(levels, new FileLogger(filename));
        }

        // User specifies only the number of log levels they require.  They are still obligated
        // (assuming they want the logger to do something) to add a handler.
        public Logger(uint levels)
        {
            init(levels, null);
        }

        // Takes the current object and places it as the internal singleton reference
        public Logger promoteToStatic()
        {
            logger = this;
            return logger;
        }

        /// The singleton can get set in two ways: 
        // 1.) The developer promotes their class to be the Logger object inside the singleton
        // 2.) We decide for them what their class is going to look like
        public static Logger singleton()
        {
            if (logger == null)
            {
                //use defaults
                // string logFile = DateTime.Now.ToShortDateString().Replace(@"/", @"-").Replace(@"\", @"-") + ".log";
                logger = new Logger(6, logFile);
                logLevelDesc = new string[6];
                logLevelDesc[0] = "V_CRITICAL";
                logLevelDesc[1] = "V_ERROR";
                logLevelDesc[2] = "V_WARN";
                logLevelDesc[3] = "V_INFO";
                logLevelDesc[4] = "V_DEBUG";
                logLevelDesc[5] = "V_ALL";
            }

            return logger;
        }

        // Effectively the constructor (so no code would have to be repeated).  Consequently
        // if one were to try to put this block inside the constructor matching the signature and
        // then referenced it with :this(levels, defaultHandler) in the other constructors MS
        // has a bit of a fit.  Dunno...this works, don't really care.
        private void init(uint levels, LoggerEventHandler defaultHandler)
        {
            this.levels = levels;
            this.defaultHandler = defaultHandler;
            this.max = levels - 1;

            leh = new LoggerEventHandler[levels][];

            LoggerEventHandler[] handler = new LoggerEventHandler[1];

            handler[0] = defaultHandler;

            for (int i = 0; i < levels; i++)
            {
                leh[i] = handler;
            }
        }

        // This is the maximum level which will trigger logging.
        public void setMaximumLogLevel(uint max)
        {
            this.max = max;
        }

        // Retrieve maximum logging level (note: this is not the total number of levels, but rather
        // the upper bound where an action will or won't take placed based on the log level)
        public uint getMaximumLogLevel()
        {
            return max;
        }

        // Retreives the default handler if one is set
        public LoggerEventHandler getDefaultLoggerEventHandler()
        {
            return this.defaultHandler;
        }

        // Adds a customized log handler to each log level
        public void addSpecialLoggerToAllLevels(LoggerEventHandler handler)
        {
            if (handler == null) return;

            for (int level = 0; level < this.levels; level++)
            {
                addSpecialLogger(level, handler);
            }
        }

        // Adds a customized logger handler (e.g. log to file) to a specific level.
        // Note: You can have (n log handlers assuming your system has the resources)
        public void addSpecialLogger(int level, LoggerEventHandler handler)
        {
            if (level < levels)
            {
                if (leh[level] != null)
                {
                    int size = leh[level].Length + 1;
                    LoggerEventHandler[] temp = new LoggerEventHandler[size];

                    for (int i = 0; i < leh[level].Length; i++)
                    {
                        temp[i] = leh[level][i];
                    }

                    temp[size - 1] = handler;

                    leh[level] = temp;
                }
                else
                {
                    leh[level] = new LoggerEventHandler[1];
                    leh[level][0] = handler;
                }
            }
        }

        // Sugared method to add a simple file handler method
        public void addSpecialLogger(int level, string filename)
        {
            addSpecialLogger(level, new FileLogger(filename));
        }

        // Invokes all appropriate log event handlers with the message
        public void log(int level, string tag, string message)
        {
            if ((level <= max) && (level < levels) && (leh[level] != null))
            {
                for (int i = 0; i < leh[level].Length; i++)
                {
                    if (logLevelDesc == null)
                    {
                        if (leh[level][i] != null) leh[level][i].log(tag, level, "", message);
                    }
                    else
                    {
                        if (leh[level][i] != null) leh[level][i].log(tag, level, logLevelDesc[level], message);
                    }
                }
            }
        }

        // Invokes the shutdown method for all log handlers
        public void shutdown()
        {
            //This is not the most efficient, really I'd like to add a collection
            //in this class and loop through it (save some repeats), also allow for some other neet stuff.
            for (int level = 0; level < leh.Length; level++)
            {
                for (int i = 0; i < leh[level].Length; i++)
                {
                    if (leh[level][i] != null) leh[level][i].shutdown();
                }
            }
        }
    }
}

