/*
 * TraceOps.cs --
 *
 * Copyright (c) 2007-2012 by Joe Mistachkin.  All rights reserved.
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: $
 */

using System;
using System.Collections.Generic;
using System.Diagnostics;
using Eagle._Attributes;
using Eagle._Components.Public;
using Eagle._Containers.Public;

namespace Eagle._Components.Private
{
    [ObjectId("6dd365ef-005a-4d33-8042-bf5b7d17153e")]
    internal static class TraceOps
    {
        #region Private Data (MUST BE DONE PRIOR TO TOUCHING GlobalState)
        #region Synchronization Objects
        //
        // BUGFIX: This is used for synchronization inside the IsTraceEnabled
        //         method, which is used by the DebugTrace method, which is
        //         used during the initialization of the static GlobalState
        //         class; therefore, it must be initialized before anything
        //         that touches the GlobalState class.
        //
        private static readonly object syncRoot = new object();
        #endregion

        ///////////////////////////////////////////////////////////////////////

        #region Tracing Support Data
        //
        // HACK: This is part of a hack that solves a chicken-and-egg problem
        //       with the diagnostic tracing method used by this library.  We
        //       allow tracing to be disabled via an environment variable
        //       and/or the shell command line.  Unfortunately, by the time we
        //       disable tracing, many messages will have typically already
        //       been written to the trace listeners.  To prevent this noise
        //       (that the user wishes to suppress), we internalize the check
        //       (i.e. we do it from inside the core trace method itself) and
        //       initialize this variable [once] with the result of checking
        //       the environment variable.
        //
        private static bool? isTraceEnabled = null;

        //
        // NOTE: This is the dictionary of trace categories that are currently
        //       "allowed".  If this dictionary is empty, all categories are
        //       considered to be "allowed"; otherwise, only those present in
        //       the dictionary with a non-zero associated value are "allowed".
        //       Any trace messages that are not "allowed" will be silently
        //       dropped.
        //
        private static IntDictionary traceCategories = null;
        #endregion
        #endregion

        ///////////////////////////////////////////////////////////////////////

        #region Tracing Support Methods
        //
        // HACK: This is part of a hack that solves a chicken-and-egg problem
        //       with the diagnostic tracing method used by this library.  We
        //       allow tracing to be disabled via an environment variable
        //       and/or the shell command line.  Unfortunately, by the time we
        //       disable tracing, many messages will have typically already
        //       been written to the trace listeners.  To prevent this noise
        //       (that the user wishes to suppress), we internalize the check
        //       (i.e. we do it from inside the core trace method itself) and
        //       initialize this variable [once] with the result of checking
        //       the environment variable.
        //
        private static bool IsTraceEnabled(
            string category
            )
        {
            bool result;

            //
            // NOTE: Grab the static data lock to prevent a race condition.
            //
            lock (syncRoot)
            {
                if (isTraceEnabled == null)
                {
                    //
                    // NOTE: We cannot use GlobalState.GetEnvironmentVariable
                    //       here because that method calls the DebugTrace
                    //       method (below), which calls us.
                    //
                    isTraceEnabled = (CommonOps.Environment.GetVariable(
                        EnvVars.NoTrace) == null);
                }

                //
                // NOTE: Determine if tracing is globally enabled or disabled.
                //
                result = (bool)isTraceEnabled;

                //
                // NOTE: If tracing has been globally disabled, do not bother
                //       checking any categories.
                //
                if (result)
                {
                    //
                    // NOTE: If the caller specified a null category -OR- there
                    //       are no trace categories specifically enabled (i.e.
                    //       all trace categories are allowed), always allow
                    //       the message through.
                    //
                    if ((category != null) &&
                        (traceCategories != null) &&
                        (traceCategories.Count > 0))
                    {
                        //
                        // NOTE: At this point, the trace category must exist
                        //       in the dictionary and the associated value
                        //       must be non-zero; otherwise, the message is
                        //       not allowed through.
                        //
                        int value;

                        if (!traceCategories.TryGetValue(
                                category, out value) || (value == 0))
                        {
                            result = false;
                        }
                    }
                }
            }

            return result;
        }

        ///////////////////////////////////////////////////////////////////////

        public static IEnumerable<string> ListTraceCategories()
        {
            //
            // NOTE: Grab the static data lock to prevent a race condition.
            //
            lock (syncRoot)
            {
                if (traceCategories == null)
                    return null;

                StringList categories = new StringList();

                foreach (KeyValuePair<string, int> pair in traceCategories)
                    categories.Add(pair.Key, pair.Value.ToString());

                return categories;
            }
        }

        ///////////////////////////////////////////////////////////////////////

        public static void SetTraceCategories(
            IEnumerable<string> categories,
            int value
            )
        {
            //
            // NOTE: Grab the static data lock to prevent a race condition.
            //
            lock (syncRoot)
            {
                //
                // NOTE: If the dictionary of "allowed" trace categories has
                //       not been created yet, do so now.
                //
                if (traceCategories == null)
                    traceCategories = new IntDictionary();

                //
                // NOTE: If there are no trace categories specified, the trace
                //       category dictionary may be created; however, it will
                //       not be added to.
                //
                if (categories != null)
                {
                    foreach (string category in categories)
                    {
                        //
                        // NOTE: Skip null categories.
                        //
                        if (category == null)
                            continue;

                        //
                        // NOTE: Add or modify the trace category.
                        //
                        traceCategories[category] = value;
                    }
                }
            }
        }

        ///////////////////////////////////////////////////////////////////////

        public static void ClearTraceCategories()
        {
            //
            // NOTE: Grab the static data lock to prevent a race condition.
            //
            lock (syncRoot)
            {
                //
                // NOTE: If the trace category dictionary has been created,
                //       clear it now.
                //
                if (traceCategories != null) traceCategories.Clear();
            }
        }

        ///////////////////////////////////////////////////////////////////////

        [Conditional("DEBUG_WRITE")]
        public static void DebugWrite(
            Interpreter interpreter,
            string value,
            bool force
            )
        {
            DebugOps.Write(interpreter, String.Format("{0} ({1}): {2}",
                GlobalState.GetCurrentThreadId(), interpreter, value), force);
        }

        ///////////////////////////////////////////////////////////////////////

        [Conditional("DEBUG_TRACE")]
        public static void DebugTrace(
            string message,
            string category
            )
        {
            //
            // TODO: Redirect these writes to the active IHost, if any?
            //
            if (IsTraceEnabled(category))
                DebugOps.TraceWrite(message, category);
        }
        #endregion
    }
}
