text: //=============================================================================
  //
  // Adventure Game Studio (AGS)
  //
  // Copyright (C) 1999-2011 Chris Jones and 2011-20xx others
  // The full list of copyright holders can be found in the Copyright.txt
  // file, which is part of this source code distribution.
  //
  // The AGS source code is provided under the Artistic License 2.0.
  // A copy of this license can be found in the file License.txt and at
  // http://www.opensource.org/licenses/artistic-license-2.0.php
  //
  //=============================================================================
  #include <stdarg.h>
  #include "debug/debugmanager.h"
  #include "util/string_types.h"
  
  namespace AGS
  {
  namespace Common
  {
  
  DebugOutput::DebugOutput(const String &id, IOutputHandler *handler, MessageType def_verbosity, bool enabled)
      : _id(id)
      , _handler(handler)
      , _enabled(enabled)
      , _defaultVerbosity(def_verbosity)
  {
      _groupFilter.resize(DbgMgr._lastGroupID + 1, _defaultVerbosity);
  }
  
  String DebugOutput::GetID() const
  {
      return _id;
  }
  
  IOutputHandler *DebugOutput::GetHandler() const
  {
      return _handler;
  }
  
  bool DebugOutput::IsEnabled() const
  {
      return _enabled;
  }
  
  void DebugOutput::SetEnabled(bool enable)
  {
      _enabled = enable;
  }
  
  void DebugOutput::SetGroupFilter(DebugGroupID id, MessageType verbosity)
  {
      uint32_t key = DbgMgr.GetGroup(id).UID.ID;
      if (key != kDbgGroup_None)
          _groupFilter[key] = verbosity;
      else
          _unresolvedGroups.insert(std::make_pair(id.SID, verbosity));
  }
  
  void DebugOutput::ResolveGroupID(DebugGroupID id)
  {
      if (!id.IsValid())
          return;
  
      DebugGroupID real_id = DbgMgr.GetGroup(id).UID;
      if (real_id.IsValid())
      {
          if (_groupFilter.size() <= id.ID)
              _groupFilter.resize(id.ID + 1, _defaultVerbosity);
          GroupNameToMTMap::const_iterator it = _unresolvedGroups.find(real_id.SID);
          if (it != _unresolvedGroups.end())
          {
              _groupFilter[real_id.ID] = it->second;
              _unresolvedGroups.erase(it);
          }
      }
  }
  
  bool DebugOutput::TestGroup(DebugGroupID id,  MessageType mt) const
  {
      DebugGroupID real_id = DbgMgr.GetGroup(id).UID;
      if (real_id.ID == kDbgGroup_None || real_id.ID >= _groupFilter.size())
          return false;
      return (_groupFilter[real_id.ID] & mt) != 0;
  }
  
  DebugManager::DebugManager()
  {
      // Add hardcoded groups
      RegisterGroup(DebugGroup(DebugGroupID(kDbgGroup_Main, "main"), ""));
      RegisterGroup(DebugGroup(DebugGroupID(kDbgGroup_SprCache, "sprcache"), "Sprite cache"));
      RegisterGroup(DebugGroup(DebugGroupID(kDbgGroup_Script, "script"), "Script"));
      RegisterGroup(DebugGroup(DebugGroupID(kDbgGroup_ManObj, "manobj"), "Managed obj"));
      _firstFreeGroupID = _groups.size();
      _lastGroupID = _firstFreeGroupID;
  }
  
  DebugGroup DebugManager::GetGroup(DebugGroupID id)
  {
      if (id.ID != kDbgGroup_None)
      {
          return id.ID < _groups.size() ? _groups[id.ID] : DebugGroup();
      }
      else if (!id.SID.IsEmpty())
      {
          GroupByStringMap::const_iterator it = _groupByStrLookup.find(id.SID);
          return it != _groupByStrLookup.end() ? _groups[it->second.ID] : DebugGroup();
      }
      return DebugGroup();
  }
  
  PDebugOutput DebugManager::GetOutput(const String &id)
  {
      OutMap::const_iterator it = _outputs.find(id);
      return it != _outputs.end() ? it->second.Target : PDebugOutput();
  }
  
  DebugGroup DebugManager::RegisterGroup(const String &id, const String &out_name)
  {
      DebugGroup group = GetGroup(id);
      if (group.UID.IsValid())
          return group;
      group = DebugGroup(DebugGroupID(++DbgMgr._lastGroupID, id), out_name);
      _groups.push_back(group);
      _groupByStrLookup[group.UID.SID] = group.UID;
  
      // Resolve group reference on every output target
      for (OutMap::const_iterator it = _outputs.begin(); it != _outputs.end(); ++it)
      {
          it->second.Target->ResolveGroupID(group.UID);
      }
      return group;
  }
  
  void DebugManager::RegisterGroup(const DebugGroup &group)
  {
      _groups.push_back(group);
      _groupByStrLookup[group.UID.SID] = group.UID;
  }
  
  PDebugOutput DebugManager::RegisterOutput(const String &id, IOutputHandler *handler, MessageType def_verbosity, bool enabled)
  {
      _outputs[id].Target = PDebugOutput(new DebugOutput(id, handler, def_verbosity, enabled));
      _outputs[id].Suppressed = false;
      return _outputs[id].Target;
  }
  
  void DebugManager::UnregisterAll()
  {
      _lastGroupID = _firstFreeGroupID;
      _groups.clear();
      _groupByStrLookup.clear();
      _outputs.clear();
  }
  
  void DebugManager::UnregisterGroup(DebugGroupID id)
  {
      DebugGroup group = GetGroup(id);
      if (!group.UID.IsValid())
          return;
      _groups[group.UID.ID] = DebugGroup();
      _groupByStrLookup.erase(group.UID.SID);
  }
  
  void DebugManager::UnregisterOutput(const String &id)
  {
      _outputs.erase(id);
  }
  
  void DebugManager::Print(DebugGroupID group_id, MessageType mt, const String &text)
  {
      const DebugGroup &group = GetGroup(group_id);
      DebugMessage msg(text, group.UID.ID, group.OutputName, mt);
  
      for (OutMap::iterator it = _outputs.begin(); it != _outputs.end(); ++it)
      {
          SendMessage(it->second, msg);
      }
  }
  
  void DebugManager::SendMessage(const String &out_id, const DebugMessage &msg)
  {
      OutMap::iterator it = _outputs.find(out_id);
      if (it != _outputs.end())
          SendMessage(it->second, msg);
  }
  
  void DebugManager::SendMessage(OutputSlot &out, const DebugMessage &msg)
  {
      IOutputHandler *handler = out.Target->GetHandler();
      if (!handler || !out.Target->IsEnabled() || out.Suppressed)
          return;
      if (!out.Target->TestGroup(msg.GroupID, msg.MT))
          return;
      // We suppress current target before the call so that if it makes
      // a call to output system itself, message would not print to the
      // same target
      out.Suppressed = true;
      handler->PrintMessage(msg);
      out.Suppressed = false;
  }
  
  // TODO: move this to the dynamically allocated engine object whenever it is implemented
  DebugManager DbgMgr;
  
  
  namespace Debug
  {
  
  void Printf(const char *fmt, ...)
  {
      va_list argptr;
      va_start(argptr, fmt);
      DbgMgr.Print(kDbgGroup_Main, kDbgMsg_Default, String::FromFormatV(fmt, argptr));
      va_end(argptr);
  }
  
  void Printf(MessageType mt, const char *fmt, ...)
  {
      va_list argptr;
      va_start(argptr, fmt);
      DbgMgr.Print(kDbgGroup_Main, mt, String::FromFormatV(fmt, argptr));
      va_end(argptr);
  }
  
  void Printf(DebugGroupID group, MessageType mt, const char *fmt, ...)
  {
      va_list argptr;
      va_start(argptr, fmt);
      DbgMgr.Print(group, mt, String::FromFormatV(fmt, argptr));
      va_end(argptr);
  }
  
  } // namespace Debug
  
  }   // namespace Common
  }   // namespace AGS
  