text:
/*

 Copyright (c) 2012, SMB Phone Inc.
 All rights reserved.

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:

 1. Redistributions of source code must retain the above copyright notice, this
 list of conditions and the following disclaimer.
 2. Redistributions in binary form must reproduce the above copyright notice,
 this list of conditions and the following disclaimer in the documentation
 and/or other materials provided with the distribution.

 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

 The views and conclusions contained in the software and documentation are those
 of the authors and should not be interpreted as representing official policies,
 either expressed or implied, of the FreeBSD Project.

 */

#include <hookflash/services/internal/services_Helper.h>
#include <hookflash/services/IDNS.h>
#include <cryptopp/osrng.h>
#include <zsLib/Stringize.h>
#include <zsLib/zsHelpers.h>
#include <zsLib/Log.h>
#include <zsLib/ISocket.h>
#include <zsLib/Socket.h>
#include <zsLib/MessageQueueThread.h>
#include <zsLib/Timer.h>
#include <zsLib/Numeric.h>

#include <iostream>
#include <fstream>
#ifndef _WIN32
#include <pthread.h>
#endif //ndef _WIN32

#include <boost/shared_array.hpp>

namespace hookflash { namespace services { ZS_DECLARE_SUBSYSTEM(hookflash_services) } }

#define HOOKFLASH_DEFAULT_OUTGOING_TELNET_PORT (59999)

#define HOOKFLASH_SERVICES_SEQUENCE_ESCAPE                    "\x1B"
#define HOOKFLASH_SERVICES_SEQUENCE_COLOUR_RESET              HOOKFLASH_SERVICES_SEQUENCE_ESCAPE "[0m"
#define HOOKFLASH_SERVICES_SEQUENCE_COLOUR_THREAD             HOOKFLASH_SERVICES_SEQUENCE_COLOUR_RESET HOOKFLASH_SERVICES_SEQUENCE_ESCAPE "[33m"
#define HOOKFLASH_SERVICES_SEQUENCE_COLOUR_TIME               HOOKFLASH_SERVICES_SEQUENCE_COLOUR_RESET HOOKFLASH_SERVICES_SEQUENCE_ESCAPE "[33m"
#define HOOKFLASH_SERVICES_SEQUENCE_COLOUR_SEVERITY_INFO      HOOKFLASH_SERVICES_SEQUENCE_COLOUR_RESET HOOKFLASH_SERVICES_SEQUENCE_ESCAPE "[36m"
#define HOOKFLASH_SERVICES_SEQUENCE_COLOUR_SEVERITY_WARNING   HOOKFLASH_SERVICES_SEQUENCE_COLOUR_RESET HOOKFLASH_SERVICES_SEQUENCE_ESCAPE "[35m"
#define HOOKFLASH_SERVICES_SEQUENCE_COLOUR_SEVERITY_ERROR     HOOKFLASH_SERVICES_SEQUENCE_COLOUR_RESET HOOKFLASH_SERVICES_SEQUENCE_ESCAPE "[31m"
#define HOOKFLASH_SERVICES_SEQUENCE_COLOUR_SEVERITY_FATAL     HOOKFLASH_SERVICES_SEQUENCE_COLOUR_RESET HOOKFLASH_SERVICES_SEQUENCE_ESCAPE "[31m"
#define HOOKFLASH_SERVICES_SEQUENCE_COLOUR_SEVERITY           HOOKFLASH_SERVICES_SEQUENCE_COLOUR_RESET HOOKFLASH_SERVICES_SEQUENCE_ESCAPE "[36m"
#define HOOKFLASH_SERVICES_SEQUENCE_COLOUR_MESSAGE_BASIC      HOOKFLASH_SERVICES_SEQUENCE_COLOUR_RESET HOOKFLASH_SERVICES_SEQUENCE_ESCAPE "[1m" HOOKFLASH_SERVICES_SEQUENCE_ESCAPE "[30m"
#define HOOKFLASH_SERVICES_SEQUENCE_COLOUR_MESSAGE_DETAIL     HOOKFLASH_SERVICES_SEQUENCE_COLOUR_RESET HOOKFLASH_SERVICES_SEQUENCE_ESCAPE "[1m" HOOKFLASH_SERVICES_SEQUENCE_ESCAPE "[30m"
#define HOOKFLASH_SERVICES_SEQUENCE_COLOUR_MESSAGE_DEBUG      HOOKFLASH_SERVICES_SEQUENCE_COLOUR_RESET HOOKFLASH_SERVICES_SEQUENCE_ESCAPE "[30m"
#define HOOKFLASH_SERVICES_SEQUENCE_COLOUR_MESSAGE_TRACE      HOOKFLASH_SERVICES_SEQUENCE_COLOUR_RESET HOOKFLASH_SERVICES_SEQUENCE_ESCAPE "[34m"
#define HOOKFLASH_SERVICES_SEQUENCE_COLOUR_FILENAME           HOOKFLASH_SERVICES_SEQUENCE_COLOUR_RESET HOOKFLASH_SERVICES_SEQUENCE_ESCAPE "[32m"
#define HOOKFLASH_SERVICES_SEQUENCE_COLOUR_LINENUMBER         HOOKFLASH_SERVICES_SEQUENCE_COLOUR_RESET HOOKFLASH_SERVICES_SEQUENCE_ESCAPE "[32m"
#define HOOKFLASH_SERVICES_SEQUENCE_COLOUR_FUNCTION           HOOKFLASH_SERVICES_SEQUENCE_COLOUR_RESET HOOKFLASH_SERVICES_SEQUENCE_ESCAPE "[36m"

namespace hookflash
{
  namespace services
  {
    using zsLib::Stringize;
    using zsLib::Numeric;

    typedef zsLib::BYTE BYTE;
    typedef zsLib::USHORT USHORT;
    typedef zsLib::ULONG ULONG;
    typedef zsLib::CSTR CSTR;
    typedef zsLib::WORD WORD;
    typedef zsLib::String String;
    typedef zsLib::Seconds Seconds;
    typedef zsLib::RecursiveLock RecursiveLock;
    typedef zsLib::AutoRecursiveLock AutoRecursiveLock;
    typedef zsLib::ISocketDelegate ISocketDelegate;
    typedef zsLib::Socket Socket;
    typedef zsLib::ISocket ISocket;
    typedef zsLib::ISocketPtr ISocketPtr;
    typedef zsLib::SocketPtr SocketPtr;
    typedef zsLib::IPAddress IPAddress;
    typedef zsLib::Subsystem Subsystem;
    typedef zsLib::Log Log;
    typedef zsLib::LogPtr LogPtr;
    typedef zsLib::ILogDelegate ILogDelegate;
    typedef zsLib::ITimerDelegate ITimerDelegate;
    typedef zsLib::TimerPtr TimerPtr;
    typedef zsLib::Timer Timer;
    typedef zsLib::Duration Duration;
    typedef zsLib::Time Time;
    typedef zsLib::MessageQueueThread MessageQueueThread;
    typedef zsLib::MessageQueueThreadPtr MessageQueueThreadPtr;
    typedef zsLib::IMessageQueuePtr IMessageQueuePtr;
    typedef zsLib::MessageQueueAssociator MessageQueueAssociator;

    namespace internal
    {
      interaction ITelnetLoggerAsync;
      typedef boost::shared_ptr<ITelnetLoggerAsync> ITelnetLoggerAsyncPtr;
      typedef boost::weak_ptr<ITelnetLoggerAsync> ITelnetLoggerAsyncWeakPtr;
      typedef zsLib::Proxy<ITelnetLoggerAsync> ITelnetLoggerAsyncProxy;

      interaction ITelnetLoggerAsync
      {
        virtual void onStep() = 0;
      };
    }
  }
}

ZS_DECLARE_PROXY_BEGIN(hookflash::services::internal::ITelnetLoggerAsync)
ZS_DECLARE_PROXY_METHOD_0(onStep)
ZS_DECLARE_PROXY_END()

namespace hookflash
{
  namespace services
  {
    namespace internal
    {
      //-----------------------------------------------------------------------
      //-----------------------------------------------------------------------
      //-----------------------------------------------------------------------
      //-----------------------------------------------------------------------
      class GlobalLock
      {
      public:
        GlobalLock() {}
        ~GlobalLock() {}

        static GlobalLock &singleton()
        {
          static GlobalLock lock;
          return lock;
        }

        RecursiveLock &getLock() const
        {
          return mLock;
        }

      private:
        mutable RecursiveLock mLock;
      };

      class GlobalLockInit
      {
      public:
        GlobalLockInit()
        {
          singleton();
        }

        RecursiveLock &singleton()
        {
          return (GlobalLock::singleton()).getLock();
        }
      };
      static GlobalLockInit gGlobalLockInit;

      RecursiveLock &Helper::getGlobalLock()
      {
        return gGlobalLockInit.singleton();
      }

      //-----------------------------------------------------------------------
      //-----------------------------------------------------------------------
      //-----------------------------------------------------------------------
      //-----------------------------------------------------------------------
      String Helper::randomString(UINT lengthInChars)
      {
        static const char *randomCharArray = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
        static size_t randomSize = strlen(randomCharArray);

        BYTE staticBuffer[256];
        char staticOutputBuffer[sizeof(staticBuffer)+1];

        boost::shared_array<BYTE> allocatedBuffer;
        boost::shared_array<char> allocatedOutputBuffer;

        BYTE *buffer = &(staticBuffer[0]);
        char *output = &(staticOutputBuffer[0]);
        if (lengthInChars > sizeof(staticBuffer)) {
          // use the allocated buffer instead
          allocatedBuffer = boost::shared_array<BYTE>(new BYTE[lengthInChars]);
          allocatedOutputBuffer = boost::shared_array<char>(new char[lengthInChars+1]);
          buffer = allocatedBuffer.get();
          output = allocatedOutputBuffer.get();
        }

        CryptoPP::AutoSeededRandomPool rng;
        rng.GenerateBlock(&(buffer[0]), lengthInChars);

        memset(&(output[0]), 0, sizeof(char)*(lengthInChars+1));

        for (UINT loop = 0; loop < lengthInChars; ++loop) {
          output[loop] = randomCharArray[((buffer[loop])%randomSize)];
        }
        return String((CSTR)(&(output[0])));
      }

      //-----------------------------------------------------------------------
      //-----------------------------------------------------------------------
      //-----------------------------------------------------------------------
      //-----------------------------------------------------------------------
      class ServiceThread;
      typedef boost::shared_ptr<ServiceThread> ServiceThreadPtr;
      typedef boost::weak_ptr<ServiceThread> ServiceThreadWeakPtr;

      class ServiceThread
      {
        ServiceThread() {}

        void init()
        {
          mThread = MessageQueueThread::createBasic("com.hookflash.services.serviceThread");
        }

      public:
        ~ServiceThread()
        {
          if (!mThread) return;
          mThread->waitForShutdown();
        }

        static ServiceThreadPtr create()
        {
          ServiceThreadPtr pThis(new ServiceThread);
          pThis->mThisWeak = pThis;
          pThis->init();
          return pThis;
        }

        static ServiceThreadPtr singleton()
        {
          AutoRecursiveLock lock(Helper::getGlobalLock());
          static ServiceThreadPtr thread = ServiceThread::create();
          return thread;
        }

        MessageQueueThreadPtr getThread() const
        {
          return mThread;
        }

      private:
        ServiceThreadWeakPtr mThisWeak;

        MessageQueueThreadPtr mThread;
      };

      IMessageQueuePtr Helper::getServiceQueue()
      {
        ServiceThreadPtr thread = ServiceThread::singleton();
        return thread->getThread();
      }

      static String currentThreadIDAsString()
      {
#ifdef _WIN32
        return Stringize<DWORD>(GetCurrentThreadId());
#else
#ifdef APPLE
        return Stringize<mach_port_t>(pthread_mach_thread_np(pthread_self()));
#else
        return Stringize<pthread_t>(pthread_self());
#endif //APPLE
#endif //_WIN32
      }

      //-----------------------------------------------------------------------
      //-----------------------------------------------------------------------
      //-----------------------------------------------------------------------
      //-----------------------------------------------------------------------
      static String toColorString(
                                  const Subsystem &inSubsystem,
                                  Log::Severity inSeverity,
                                  Log::Level inLevel,
                                  CSTR inMessage,
                                  CSTR inFunction,
                                  CSTR inFilePath,
                                  ULONG inLineNumber
                                  )
      {
        const char *posBackslash = strrchr(inFilePath, '\\');
        const char *posSlash = strrchr(inFilePath, '/');

        const char *fileName = inFilePath;

        if (!posBackslash)
          posBackslash = posSlash;

        if (!posSlash)
          posSlash = posBackslash;

        if (posSlash) {
          if (posBackslash > posSlash)
            posSlash = posBackslash;
          fileName = posSlash + 1;
        }

        std::string current = to_simple_string(zsLib::now()).substr(12);

        const char *colorSeverity = HOOKFLASH_SERVICES_SEQUENCE_COLOUR_SEVERITY_INFO;
        const char *severity = "NONE";
        switch (inSeverity) {
          case Log::Informational:   severity = "i:"; colorSeverity = HOOKFLASH_SERVICES_SEQUENCE_COLOUR_SEVERITY_INFO; break;
          case Log::Warning:         severity = "W:"; colorSeverity = HOOKFLASH_SERVICES_SEQUENCE_COLOUR_SEVERITY_WARNING; break;
          case Log::Error:           severity = "E:"; colorSeverity = HOOKFLASH_SERVICES_SEQUENCE_COLOUR_SEVERITY_ERROR; break;
          case Log::Fatal:           severity = "F:"; colorSeverity = HOOKFLASH_SERVICES_SEQUENCE_COLOUR_SEVERITY_FATAL; break;
        }

        const char *colorLevel = HOOKFLASH_SERVICES_SEQUENCE_COLOUR_MESSAGE_TRACE;
        switch (inLevel) {
          case Log::Basic:           colorLevel = HOOKFLASH_SERVICES_SEQUENCE_COLOUR_MESSAGE_BASIC; break;
          case Log::Detail:          colorLevel = HOOKFLASH_SERVICES_SEQUENCE_COLOUR_MESSAGE_DETAIL; break;
          case Log::Debug:           colorLevel = HOOKFLASH_SERVICES_SEQUENCE_COLOUR_MESSAGE_DEBUG; break;
          case Log::Trace:           colorLevel = HOOKFLASH_SERVICES_SEQUENCE_COLOUR_MESSAGE_TRACE; break;
          case Log::None:            break;
        }

        String result = String(HOOKFLASH_SERVICES_SEQUENCE_COLOUR_TIME) + current
                      + HOOKFLASH_SERVICES_SEQUENCE_COLOUR_RESET + " "
                      + colorSeverity + severity
                      + HOOKFLASH_SERVICES_SEQUENCE_COLOUR_RESET + " "
                      + HOOKFLASH_SERVICES_SEQUENCE_COLOUR_THREAD + "<" + currentThreadIDAsString() + ">"
                      + HOOKFLASH_SERVICES_SEQUENCE_COLOUR_RESET + " "
                      + colorLevel + inMessage
                      + HOOKFLASH_SERVICES_SEQUENCE_COLOUR_RESET + " "
                      + HOOKFLASH_SERVICES_SEQUENCE_COLOUR_FILENAME + "@" + fileName
                      + HOOKFLASH_SERVICES_SEQUENCE_COLOUR_LINENUMBER + "(" + Stringize<ULONG>(inLineNumber).string() + ")"
                      + HOOKFLASH_SERVICES_SEQUENCE_COLOUR_RESET + " "
                      + HOOKFLASH_SERVICES_SEQUENCE_COLOUR_FUNCTION + "[" + inFunction + "]"
                      + HOOKFLASH_SERVICES_SEQUENCE_COLOUR_RESET + "\n";

        return result;
      }

      static String toBWString(
                               const Subsystem &inSubsystem,
                               Log::Severity inSeverity,
                               Log::Level inLevel,
                               CSTR inMessage,
                               CSTR inFunction,
                               CSTR inFilePath,
                               ULONG inLineNumber
                               )
      {
        const char *posBackslash = strrchr(inFilePath, '\\');
        const char *posSlash = strrchr(inFilePath, '/');

        const char *fileName = inFilePath;

        if (!posBackslash)
          posBackslash = posSlash;

        if (!posSlash)
          posSlash = posBackslash;

        if (posSlash) {
          if (posBackslash > posSlash)
            posSlash = posBackslash;
          fileName = posSlash + 1;
        }

        std::string current = to_simple_string(zsLib::now()).substr(12);

        const char *severity = "NONE";
        switch (inSeverity) {
          case Log::Informational:   severity = "i:"; break;
          case Log::Warning:         severity = "W:"; break;
          case Log::Error:           severity = "E:"; break;
          case Log::Fatal:           severity = "F:"; break;
        }

        String result = current + " " + severity + " <"  + currentThreadIDAsString() + "> " + inMessage + " " + "@" + fileName + "(" + Stringize<ULONG>(inLineNumber).string() + ")" + " " + "[" + inFunction + "]" + "\n";
        return result;
      }

      static String toWindowsString(
                                    const Subsystem &inSubsystem,
                                    Log::Severity inSeverity,
                                    Log::Level inLevel,
                                    CSTR inMessage,
                                    CSTR inFunction,
                                    CSTR inFilePath,
                                    ULONG inLineNumber
                                    )
      {
        std::string current = to_simple_string(zsLib::now()).substr(12);

        const char *severity = "NONE";
        switch (inSeverity) {
          case Log::Informational:   severity = "i:"; break;
          case Log::Warning:         severity = "W:"; break;
          case Log::Error:           severity = "E:"; break;
          case Log::Fatal:           severity = "F:"; break;
        }

        String result = String(inFilePath) +  "(" + Stringize<ULONG>(inLineNumber).string() + ") " + severity + current + " : <" + currentThreadIDAsString() + "> " + inMessage + "\n";
        return result;
      }

      //-----------------------------------------------------------------------
      //-----------------------------------------------------------------------
      //-----------------------------------------------------------------------
      //-----------------------------------------------------------------------
      class LogLevelLogger;
      typedef boost::shared_ptr<LogLevelLogger> LogLevelLoggerPtr;
      typedef boost::weak_ptr<LogLevelLogger> LogLevelLoggerWeakPtr;

      class LogLevelLogger : public ILogDelegate
      {
        void init()
        {
          LogPtr log = Log::singleton();
          log->addListener(mThisWeak.lock());
        }

      public:
        LogLevelLogger() :
          mDefaultLogLevelSet(false),
          mDefaultLogLevel(Log::None)
        {}

        static LogLevelLoggerPtr create()
        {
          LogLevelLoggerPtr pThis(new LogLevelLogger());
          pThis->mThisWeak = pThis;
          pThis->init();
          return pThis;
        }

        static LogLevelLoggerPtr singleton() {
          AutoRecursiveLock lock(Helper::getGlobalLock());
          static LogLevelLoggerPtr logger = LogLevelLogger::create();
          return logger;
        }

        void setLogLevel(Log::Level level)
        {
          AutoRecursiveLock lock(mLock);

          mLevels.clear();

          mDefaultLogLevelSet = true;
          mDefaultLogLevel = level;
          for (SubsystemMap::iterator iter = mSubsystems.begin(); iter != mSubsystems.end(); ++iter) {
            Subsystem * &subsystem = (*iter).second;
            (*subsystem).setOutputLevel(level);
          }
        }

        void setLogLevel(const char *component, Log::Level level)
        {
          AutoRecursiveLock lock(mLock);
          mLevels[component] = level;

          SubsystemMap::iterator found = mSubsystems.find(component);
          if (found == mSubsystems.end()) return;

          Subsystem * &subsystem = (*found).second;
          (*subsystem).setOutputLevel(level);
        }

        virtual void onNewSubsystem(Subsystem &inSubsystem)
        {
          AutoRecursiveLock lock(mLock);

          const char *name = inSubsystem.getName();
          mSubsystems[name] = &(inSubsystem);

          LevelMap::iterator found = mLevels.find(name);
          if (found == mLevels.end()) {
            if (!mDefaultLogLevelSet) return;
            inSubsystem.setOutputLevel(mDefaultLogLevel);
            return;
          }

          Log::Level level = (*found).second;
          inSubsystem.setOutputLevel(level);
        }

        // notification of a log event
        virtual void log(
                         const Subsystem &,
                         Log::Severity,
                         Log::Level,
                         CSTR,
                         CSTR,
                         CSTR,
                         ULONG
                         )
        {
        }

      private:
        LogLevelLoggerWeakPtr mThisWeak;

        mutable RecursiveLock mLock;

        typedef std::map<String, Subsystem *> SubsystemMap;
        typedef std::map<String, Log::Level> LevelMap;

        SubsystemMap mSubsystems;
        LevelMap mLevels;

        bool mDefaultLogLevelSet;
        Log::Level mDefaultLogLevel;
      };

      //-----------------------------------------------------------------------
      //-----------------------------------------------------------------------
      //-----------------------------------------------------------------------
      //-----------------------------------------------------------------------
      class StdOutLogger;
      typedef boost::shared_ptr<StdOutLogger> StdOutLoggerPtr;
      typedef boost::weak_ptr<StdOutLogger> StdOutLoggerWeakPtr;

      class StdOutLogger : public ILogDelegate
      {
        void init()
        {
          LogPtr log = Log::singleton();
          log->addListener(mThisWeak.lock());
        }

      public:
        StdOutLogger(bool colorizeOutput) : mColorizeOutput(colorizeOutput) {}

        static StdOutLoggerPtr create(bool colorizeOutput)
        {
          StdOutLoggerPtr pThis(new StdOutLogger(colorizeOutput));
          pThis->mThisWeak = pThis;
          pThis->init();
          return pThis;
        }

        static StdOutLoggerPtr singleton(bool colorizeOutput, bool reset = false) {
          AutoRecursiveLock lock(Helper::getGlobalLock());
          static StdOutLoggerPtr logger = (reset ? StdOutLoggerPtr() : StdOutLogger::create(colorizeOutput));
          if ((reset) &&
              (logger)) {
            LogPtr log = Log::singleton();
            log->removeListener(logger->mThisWeak.lock());
            logger.reset();
          }
          if ((!reset) &&
              (!logger)) {
            logger = StdOutLogger::create(colorizeOutput);
          }
          return logger;
        }

        virtual void onNewSubsystem(Subsystem &)
        {
        }

        // notification of a log event
        virtual void log(
                         const Subsystem &inSubsystem,
                         Log::Severity inSeverity,
                         Log::Level inLevel,
                         CSTR inMessage,
                         CSTR inFunction,
                         CSTR inFilePath,
                         ULONG inLineNumber
                         )
        {
          if (mColorizeOutput) {
            std:: cout << toColorString(inSubsystem, inSeverity, inLevel, inMessage, inFunction, inFilePath, inLineNumber);
          } else {
            std:: cout << toBWString(inSubsystem, inSeverity, inLevel, inMessage, inFunction, inFilePath, inLineNumber);
          }
        }

      private:
        StdOutLoggerWeakPtr mThisWeak;
        bool mColorizeOutput;
      };

      //-----------------------------------------------------------------------
      //-----------------------------------------------------------------------
      //-----------------------------------------------------------------------
      //-----------------------------------------------------------------------
      class FileLogger;
      typedef boost::shared_ptr<FileLogger> FileLoggerPtr;
      typedef boost::weak_ptr<FileLogger> FileLoggerWeakPtr;

      class FileLogger : public ILogDelegate
      {
        void init(const char *fileName)
        {
          mFile.open(fileName, std::ios::out | std::ios::binary);
          LogPtr log = Log::singleton();
          log->addListener(mThisWeak.lock());
        }

      public:
        FileLogger(bool colorizeOutput) : mColorizeOutput(colorizeOutput) {}

        static FileLoggerPtr create(const char *fileName, bool colorizeOutput)
        {
          FileLoggerPtr pThis(new FileLogger(colorizeOutput));
          pThis->mThisWeak = pThis;
          pThis->init(fileName);
          return pThis;
        }

        static FileLoggerPtr singleton(const char *fileName, bool colorizeOutput, bool reset = false) {
          AutoRecursiveLock lock(Helper::getGlobalLock());
          static FileLoggerPtr logger = (reset ? FileLoggerPtr() : FileLogger::create(fileName, colorizeOutput));
          if ((reset) &&
              (logger)) {
            LogPtr log = Log::singleton();
            log->removeListener(logger->mThisWeak.lock());
            logger.reset();
          }
          if ((!reset) &&
              (!logger)) {
            logger = FileLogger::create(fileName, colorizeOutput);
          }
          return logger;
        }

        virtual void onNewSubsystem(Subsystem &)
        {
        }

        // notification of a log event
        virtual void log(
                         const Subsystem &inSubsystem,
                         Log::Severity inSeverity,
                         Log::Level inLevel,
                         CSTR inMessage,
                         CSTR inFunction,
                         CSTR inFilePath,
                         ULONG inLineNumber
                         )
        {
          if (mFile.is_open()) {
            String output;
            if (mColorizeOutput) {
              output = toColorString(inSubsystem, inSeverity, inLevel, inMessage, inFunction, inFilePath, inLineNumber);
            } else {
              output = toBWString(inSubsystem, inSeverity, inLevel, inMessage, inFunction, inFilePath, inLineNumber);
            }
            mFile << output;
            mFile.flush();
          }
        }

      private:
        FileLoggerWeakPtr mThisWeak;
        bool mColorizeOutput;

        std::ofstream mFile;
      };

      //-----------------------------------------------------------------------
      //-----------------------------------------------------------------------
      //-----------------------------------------------------------------------
      //-----------------------------------------------------------------------
      class WindowsDebuggerLogger;
      typedef boost::shared_ptr<WindowsDebuggerLogger> WindowsDebuggerLoggerPtr;
      typedef boost::weak_ptr<WindowsDebuggerLogger> WindowsDebuggerLoggerWeakPtr;

      class WindowsDebuggerLogger : public ILogDelegate
      {
        void init()
        {
          LogPtr log = Log::singleton();
          log->addListener(mThisWeak.lock());
        }

      public:
        WindowsDebuggerLogger() {}

        static WindowsDebuggerLoggerPtr create()
        {
          WindowsDebuggerLoggerPtr pThis(new WindowsDebuggerLogger());
          pThis->mThisWeak = pThis;
          pThis->init();
          return pThis;
        }

        static WindowsDebuggerLoggerPtr singleton(bool reset = false)
        {
#ifdef _WIN32
          AutoRecursiveLock lock(Helper::getGlobalLock());
          static WindowsDebuggerLoggerPtr logger = (reset ? WindowsDebuggerLoggerPtr() : WindowsDebuggerLogger::create());
          if ((reset) &&
              (logger)) {
            LogPtr log = Log::singleton();
            log->removeListener(logger->mThisWeak.lock());
            logger.reset();
          }
          if ((!reset) &&
              (!logger)) {
            logger = WindowsDebuggerLogger::create();
          }
          return logger;
#else
          return WindowsDebuggerLoggerPtr();
#endif //_WIN32
        }

        virtual void onNewSubsystem(Subsystem &)
        {
        }

        // notification of a log event
        virtual void log(
                         const Subsystem &inSubsystem,
                         Log::Severity inSeverity,
                         Log::Level inLevel,
                         CSTR inMessage,
                         CSTR inFunction,
                         CSTR inFilePath,
                         ULONG inLineNumber
                         )
        {
#ifdef _WIN32
          String output = toWindowsString(inSubsystem, inSeverity, inLevel, inMessage, inFunction, inFilePath, inLineNumber);
          OutputDebugStringW(output.wstring().c_str());
#endif //_WIN32
        }

      private:
        WindowsDebuggerLoggerWeakPtr mThisWeak;
      };

      //-----------------------------------------------------------------------
      //-----------------------------------------------------------------------
      //-----------------------------------------------------------------------
      //-----------------------------------------------------------------------
      class TelnetLogger;
      typedef boost::shared_ptr<TelnetLogger> TelnetLoggerPtr;
      typedef boost::weak_ptr<TelnetLogger> TelnetLoggerWeakPtr;

      class TelnetLogger : public ILogDelegate,
                           public MessageQueueAssociator,
                           public ISocketDelegate,
                           public IDNSDelegate,
                           public ITimerDelegate,
                           public ITelnetLoggerAsync
      {
        //---------------------------------------------------------------------
        void init(
                  USHORT listenPort,
                  ULONG maxSecondsWaitForSocketToBeAvailable
                  )
        {
          mListenPort = listenPort;
          mMaxWaitTimeForSocketToBeAvailable = Seconds(maxSecondsWaitForSocketToBeAvailable);

          LogPtr log = Log::singleton();
          log->addListener(mThisWeak.lock());

          listen();
        }

        //---------------------------------------------------------------------
        void init(
                  const char *serverHostWithPort,
                  const char *sendStringUponConnection
                  )
        {
          mOutgoingMode = true;
          mBackupStringToSendUponConnection = (sendStringUponConnection ? sendStringUponConnection : "");
          mServerLookupName = (serverHostWithPort ? serverHostWithPort : "");

          String::size_type pos = mServerLookupName.find(":");
          if (pos != mServerLookupName.npos) {
            String portStr = mServerLookupName.substr(pos+1);
            mServerLookupName = mServerLookupName.substr(0, pos);

            try {
              mListenPort = Numeric<WORD>(portStr);
            } catch(Numeric<WORD>::ValueOutOfRange &) {
            }
          }

          if (0 == mListenPort) {
            mListenPort = HOOKFLASH_DEFAULT_OUTGOING_TELNET_PORT;
          }

          // do this from outside the stack to prevent this from happening during any kind of lock
          ITelnetLoggerAsyncProxy::create(mThisWeak.lock())->onStep();

          LogPtr log = Log::singleton();
          log->addListener(mThisWeak.lock());
        }

        //---------------------------------------------------------------------
        void listen()
        {
          if (!mListenSocket) {
            mListenSocket = Socket::createTCP();
            try {
              mListenSocket->setOptionFlag(ISocket::SetOptionFlag::IgnoreSigPipe, true);
            } catch(ISocket::Exceptions::UnsupportedSocketOption &) {
            }
            mListenSocket->setOptionFlag(Socket::SetOptionFlag::NonBlocking, true);
          }

          IPAddress any = IPAddress::anyV4();
          any.setPort(mListenPort);

          int error = 0;

          std::cout << "TELNET LOGGER: Attempting to listen for client connections on port: " << mListenPort << " (start time=" << Stringize<Time>(mStartListenTime).string() << ")...\n";
          mListenSocket->bind(any, &error);

          Time tick = zsLib::now();

          if (0 != error) {
            mListenSocket->close();
            mListenSocket.reset();

            if (mStartListenTime + mMaxWaitTimeForSocketToBeAvailable < tick) {
              std::cout << "TELNET LOGGER: ***ABANDONED***\n";
              if (mListenTimer) {
                mListenTimer->cancel();
                mListenTimer.reset();
              }
              return;
            }
            if (!mListenTimer) {
              mListenTimer = Timer::create(mThisWeak.lock(), Seconds(1));
            }
            std::cout << "TELNET LOGGER: Failed to listen...\n";
            return;
          } else {
            std::cout << "TELNET LOGGER: Succeeded.\n\n";
          }

          if (mListenTimer) {
            mListenTimer->cancel();
            mListenTimer.reset();
          }

          mListenSocket->setDelegate(mThisWeak.lock());
          mListenSocket->listen();
        }

      public:
        //---------------------------------------------------------------------
        TelnetLogger(
                     MessageQueueThreadPtr thread,
                     bool colorizeOutput
                     ) :
          MessageQueueAssociator(thread),
          mThread(thread),
          mColorizeOutput(colorizeOutput),
          mOutgoingMode(false),
          mConnected(false),
          mClosed(false),
          mListenPort(0),
          mMaxWaitTimeForSocketToBeAvailable(Seconds(60)),
          mStartListenTime(zsLib::now())
        {}

        //---------------------------------------------------------------------
        ~TelnetLogger()
        {
          close();
        }

        //---------------------------------------------------------------------
        void close()
        {
          MessageQueueThreadPtr thread;

          {
            AutoRecursiveLock lock(mLock);

            mClosed = true;

            mBufferedList.clear();
            mConnected = false;

            if (mOutgoingServerQuery) {
              mOutgoingServerQuery->cancel();
              mOutgoingServerQuery.reset();
            }

            mServers.reset();

            if (mTelnetSocket) {
              mTelnetSocket->close();
              mTelnetSocket.reset();
            }
            if (mListenSocket) {
              mListenSocket->close();
              mListenSocket.reset();
            }
            if (mListenTimer) {
              mListenTimer->cancel();
              mListenTimer.reset();
            }
            if (mThread) {
              thread = mThread;
              mThread.reset();
            }
          }

          if (thread) {
            thread->waitForShutdown();
          }
        }

        //---------------------------------------------------------------------
        static TelnetLoggerPtr create(USHORT listenPort, ULONG maxSecondsWaitForSocketToBeAvailable, bool colorizeOutput)
        {
          MessageQueueThreadPtr thread = MessageQueueThread::createBasic();
          TelnetLoggerPtr pThis(new TelnetLogger(thread, colorizeOutput));
          pThis->mThisWeak = pThis;
          pThis->init(listenPort, maxSecondsWaitForSocketToBeAvailable);
          return pThis;
        }

        //---------------------------------------------------------------------
        static TelnetLoggerPtr create(
                                      const char *serverHostWithPort,
                                      bool colorizeOutput,
                                      const char *sendStringUponConnection
                                      )
        {
          MessageQueueThreadPtr thread = MessageQueueThread::createBasic();
          TelnetLoggerPtr pThis(new TelnetLogger(thread, colorizeOutput));
          pThis->mThisWeak = pThis;
          pThis->init(serverHostWithPort, sendStringUponConnection);
          return pThis;
        }

        //---------------------------------------------------------------------
        static TelnetLoggerPtr singleton(USHORT listenPort, ULONG maxSecondsWaitForSocketToBeAvailable, bool colorizeOutput, bool reset = false) {
          AutoRecursiveLock lock(Helper::getGlobalLock());
          static TelnetLoggerPtr logger = (reset ? TelnetLoggerPtr() : TelnetLogger::create(listenPort, maxSecondsWaitForSocketToBeAvailable, colorizeOutput));
          if ((reset) &&
              (logger)) {
            LogPtr log = Log::singleton();
            log->removeListener(logger->mThisWeak.lock());
            logger->close();
            logger.reset();
          }
          if ((!reset) &&
              (!logger)) {
            logger = TelnetLogger::create(listenPort, maxSecondsWaitForSocketToBeAvailable, colorizeOutput);
          }
          return logger;
        }

        //---------------------------------------------------------------------
        static TelnetLoggerPtr singleton(
                                         const char *serverHostWithPort,
                                         bool colorizeOutput,
                                         const char *sendStringUponConnection
                                         )
        {
          bool reset = (NULL == serverHostWithPort);

          AutoRecursiveLock lock(Helper::getGlobalLock());
          static TelnetLoggerPtr logger = (reset ? TelnetLoggerPtr() : TelnetLogger::create(serverHostWithPort, colorizeOutput, sendStringUponConnection));
          if ((reset) &&
              (logger)) {
            LogPtr log = Log::singleton();
            log->removeListener(logger->mThisWeak.lock());
            logger->close();
            logger.reset();
          }

          if ((!reset) &&
              (!logger)) {
            logger = TelnetLogger::create(serverHostWithPort, colorizeOutput, sendStringUponConnection);
          }
          return logger;
        }

        //---------------------------------------------------------------------
        virtual void onNewSubsystem(Subsystem &)
        {
        }

        //---------------------------------------------------------------------
        virtual void log(
                         const Subsystem &inSubsystem,
                         Log::Severity inSeverity,
                         Log::Level inLevel,
                         CSTR inMessage,
                         CSTR inFunction,
                         CSTR inFilePath,
                         ULONG inLineNumber
                         )
        {
          AutoRecursiveLock lock(mLock);

          if (!mTelnetSocket)
            return;

          bool wouldBlock = false;
          ULONG sent = 0;

          String output;
          if (mColorizeOutput) {
            output = toColorString(inSubsystem, inSeverity, inLevel, inMessage, inFunction, inFilePath, inLineNumber);
          } else {
            output = toBWString(inSubsystem, inSeverity, inLevel, inMessage, inFunction, inFilePath, inLineNumber);
          }

          bool okayToSend = mBufferedList.size() < 1;

          if (mOutgoingMode) {
            if (!mConnected) return;
          }

          if (okayToSend) {
            int errorCode = 0;
            sent = mTelnetSocket->send((const BYTE *)(output.c_str()), output.length(), &wouldBlock, 0, &errorCode);
            if (!wouldBlock) {
              if (0 != errorCode) {
                connectOutgoingAgain();
                return;
              }
            }
          }

          if (sent < output.length()) {
            // we need to buffer the data for later...
            ULONG length = (output.length() - sent);
            BufferedData data;
            boost::shared_array<BYTE> buffer(new BYTE[length]);
            memcpy(&(buffer[0]), output.c_str() + sent, length);

            data.first = buffer;
            data.second = length;

            mBufferedList.push_back(data);
          }
        }

        //---------------------------------------------------------------------
        virtual void onReadReady(ISocketPtr inSocket)
        {
          AutoRecursiveLock lock(mLock);

          if (mOutgoingMode) {
            if (!mConnected) return;
          }

          if (inSocket == mListenSocket) {
            if (mTelnetSocket)
            {
              mTelnetSocket->close();
              mTelnetSocket.reset();
            }

            IPAddress ignored;
            int noThrowError = 0;
            mTelnetSocket = mListenSocket->accept(ignored, &noThrowError);
            if (!mTelnetSocket)
              return;

            try {
              mTelnetSocket->setOptionFlag(ISocket::SetOptionFlag::IgnoreSigPipe, true);
            } catch(ISocket::Exceptions::UnsupportedSocketOption &) {
            }

            mTelnetSocket->setOptionFlag(Socket::SetOptionFlag::NonBlocking, true);
            mTelnetSocket->setDelegate(mThisWeak.lock());
          }

          if (inSocket == mTelnetSocket) {
            char buffer[1024+1];
            memset(&(buffer[0]), 0, sizeof(buffer));
            ULONG length = 0;

            bool wouldBlock = false;
            int errorCode = 0;
            length = mTelnetSocket->receive((BYTE *)(&(buffer[0])), sizeof(buffer)-sizeof(buffer[0]), &wouldBlock, 0, &errorCode);

            if (length < 1) return;

            mCommand += (CSTR)(&buffer[0]);
            if (mCommand.size() > (sizeof(buffer)*3)) {
              mCommand.clear();
            }
            while (true) {
              const char *posLineFeed = strchr(mCommand, '\n');
              const char *posCarrageReturn = strchr(mCommand, '\r');

              if ((NULL == posLineFeed) &&
                  (NULL == posCarrageReturn)) {
                return;
              }

              if (NULL == posCarrageReturn)
                posCarrageReturn = posLineFeed;
              if (NULL == posLineFeed)
                posLineFeed = posCarrageReturn;

              if (posCarrageReturn < posLineFeed)
                posLineFeed = posCarrageReturn;

              String command = mCommand.substr(0, (posLineFeed - mCommand.c_str()));
              mCommand = mCommand.substr((posLineFeed - mCommand.c_str()) + 1);

              if (command.size() > 0) {
                handleCommand(command);
              }
            }
          }
        }

        //---------------------------------------------------------------------
        virtual void onWriteReady(ISocketPtr socket)
        {
          AutoRecursiveLock lock(mLock);
          if (socket != mTelnetSocket) return;

          if (mOutgoingMode) {
            mConnected = true;
          }

          if (!mStringToSendUponConnection.isEmpty()) {

            ULONG length = mStringToSendUponConnection.length();

            BufferedData data;
            boost::shared_array<BYTE> buffer(new BYTE[length]);
            memcpy(&(buffer[0]), mStringToSendUponConnection.c_str(), length);

            data.first = buffer;
            data.second = length;

            mBufferedList.push_front(data);

            mStringToSendUponConnection.clear();
          }

          while (mBufferedList.size() > 0) {
            BufferedData &data = mBufferedList.front();
            bool wouldBlock = false;
            ULONG sent = 0;

            int errorCode = 0;
            sent = mTelnetSocket->send(data.first.get(), data.second, &wouldBlock, 0, &errorCode);
            if (!wouldBlock) {
              if (0 != errorCode) {
                connectOutgoingAgain();
                return;
              }
            }

            if (sent == data.second) {
              mBufferedList.pop_front();
              continue;
            }

            ULONG length = (data.second - sent);
            memcpy(data.first.get() + sent, data.first.get(), length);
            data.second = length;
            break;
          }
        }

        //---------------------------------------------------------------------
        virtual void onException(ISocketPtr inSocket)
        {
          AutoRecursiveLock lock(mLock);
          if (inSocket == mListenSocket) {
            mListenSocket->close();
            mListenSocket.reset();
            if (!mClosed) {
              mStartListenTime = zsLib::now();
              listen();
            }
          }
          if (inSocket == mTelnetSocket) {
            connectOutgoingAgain();
          }
        }

        //---------------------------------------------------------------------
        virtual void onLookupCompleted(IDNSQueryPtr query)
        {
          AutoRecursiveLock lock(mLock);
          IDNS::AResult::IPAddressList list;
          IDNS::AResultPtr resultA = query->getA();
          if (resultA) {
            list = resultA->mIPAddresses;
          }
          IDNS::AAAAResultPtr resultAAAA = query->getAAAA();
          if (resultAAAA) {
            if (list.size() < 1) {
              list = resultAAAA->mIPAddresses;
            } else if (resultAAAA->mIPAddresses.size() > 0) {
              list.merge(resultAAAA->mIPAddresses);
            }
          }

          mOutgoingServerQuery.reset();

          if (list.size() < 1) return;

          mServers = IDNS::convertIPAddressesToSRVResult("logger", "tcp", list, mListenPort);

          connectOutgoingAgain();
        }

        //---------------------------------------------------------------------
        virtual void onStep()
        {
          String serverName;

          {
            AutoRecursiveLock lock(mLock);
            if (mServerLookupName) {
              serverName = mServerLookupName;
              mServerLookupName.clear();
            }
          }

          if (serverName.isEmpty()) return;

          // DNS is not created during any kind of lock at all...
          IDNSQueryPtr query = IDNS::lookupAorAAAA(mThisWeak.lock(), serverName);

          {
            AutoRecursiveLock lock(mLock);
            // we can guarentee result will not happen until after "onStep"
            // exits because both occupy the same thread queue...
            mOutgoingServerQuery = query;
          }
        }

        virtual void onTimer(TimerPtr timer)
        {
          if (timer != mListenTimer) {
            return;
          }
          listen();
        }

      protected:
        //---------------------------------------------------------------------
        void connectOutgoingAgain()
        {
          mConnected = false;
          mBufferedList.clear();

          if (mTelnetSocket) {
            mTelnetSocket->close();
            mTelnetSocket.reset();
          }

          if (!mOutgoingMode) return;

          if (!mServers) return;

          IPAddress result;
          if (!IDNS::extractNextIP(mServers, result)) {
            mServers.reset();
            return;
          }

          mStringToSendUponConnection = mBackupStringToSendUponConnection;

          mTelnetSocket = Socket::createTCP();
          try {
            mTelnetSocket->setOptionFlag(ISocket::SetOptionFlag::IgnoreSigPipe, true);
          } catch(ISocket::Exceptions::UnsupportedSocketOption &) {
          }
          mTelnetSocket->setBlocking(false);
          mTelnetSocket->setDelegate(mThisWeak.lock());

          bool wouldBlock = false;
          int errorCode = 0;
          mTelnetSocket->connect(result, &wouldBlock, &errorCode);
          if (0 != errorCode) {
          }
        }

        //---------------------------------------------------------------------
        void handleCommand(String command)
        {
          String input = command;

          typedef std::list<String> StringList;
          StringList split;

          // split the command by the space character...
          while (true) {
            const char *posSpace = strchr(command, ' ');
            if (NULL == posSpace) {
              if (command.size() > 0) {
                split.push_back(command);
              }
              break;
            }
            String sub = command.substr(0, (posSpace - command.c_str()));
            command = command.substr((posSpace - command.c_str()) + 1);

            if (sub.size() > 0) {
              split.push_back(sub);
            }
          }

          bool output = false;
          String subsystem;
          String level;

          if (split.size() > 0) {
            command = split.front(); split.pop_front();
            if ((command == "set") && (split.size() > 0)) {
              command = split.front(); split.pop_front();
              if ((command == "log") && (split.size() > 0)) {
                level = split.front(); split.pop_front();
                output = true;
                if (level == "trace") {
                  IHelper::setLogLevel(Log::Trace);
                } else if (level == "debug") {
                  IHelper::setLogLevel(Log::Debug);
                } else if (level == "detail") {
                  IHelper::setLogLevel(Log::Detail);
                } else if (level == "basic") {
                  IHelper::setLogLevel(Log::Basic);
                } else if (level == "none") {
                  IHelper::setLogLevel(Log::None);
                } else if (split.size() > 0) {
                  subsystem = level;
                  level = split.front(); split.pop_front();
                  if (level == "trace") {
                    IHelper::setLogLevel(subsystem, Log::Trace);
                  } else if (level == "debug") {
                    IHelper::setLogLevel(subsystem, Log::Debug);
                  } else if (level == "detail") {
                    IHelper::setLogLevel(subsystem, Log::Detail);
                  } else if (level == "basic") {
                    IHelper::setLogLevel(subsystem, Log::Basic);
                  } else if (level == "none") {
                    IHelper::setLogLevel(subsystem, Log::None);
                  } else {
                    output = false;
                  }
                } else {
                  output = false;
                }
              }
            }
          }

          String echo;
          if (output) {
            if (subsystem.size() > 0) {
              echo = "==> Setting log level for \"" + subsystem + "\" to \"" + level + "\"\n";
            } else {
              echo = "==> Setting all log compoment levels to \"" + level + "\"\n";
            }
          } else {
            echo = "==> Command not recognized \"" + input + "\"\n";
          }
          bool wouldBlock = false;
          int errorCode = 0;
          mTelnetSocket->send((const BYTE *)(echo.c_str()), echo.length(), &wouldBlock, 0, &errorCode);
        }

      private:
        mutable RecursiveLock mLock;

        MessageQueueThreadPtr mThread;

        TelnetLoggerWeakPtr mThisWeak;
        bool mColorizeOutput;

        SocketPtr mListenSocket;
        ISocketPtr mTelnetSocket;

        bool mClosed;

        String mCommand;

        typedef std::pair< boost::shared_array<BYTE>, ULONG> BufferedData;
        typedef std::list<BufferedData> BufferedDataList;

        BufferedDataList mBufferedList;

        WORD mListenPort;
        Duration mMaxWaitTimeForSocketToBeAvailable;
        Time mStartListenTime;
        TimerPtr mListenTimer;

        bool mOutgoingMode;
        bool mConnected;
        IDNSQueryPtr mOutgoingServerQuery;
        String mStringToSendUponConnection;
        String mBackupStringToSendUponConnection;

        String mServerLookupName;
        IDNS::SRVResultPtr mServers;
      };
    }

    //-----------------------------------------------------------------------
    //-----------------------------------------------------------------------
    //-----------------------------------------------------------------------
    //-----------------------------------------------------------------------
    String IHelper::randomString(UINT lengthInChars)
    {
      return internal::Helper::randomString(lengthInChars);
    }

    IMessageQueuePtr IHelper::getServiceQueue()
    {
      return internal::Helper::getServiceQueue();
    }

    void IHelper::installStdOutLogger(bool colorizeOutput)
    {
      internal::StdOutLogger::singleton(colorizeOutput);
    }

    void IHelper::installFileLogger(const char *fileName, bool colorizeOutput)
    {
      internal::FileLogger::singleton(fileName, colorizeOutput);
    }

    void IHelper::installTelnetLogger(WORD listenPort, ULONG maxSecondsWaitForSocketToBeAvailable,  bool colorizeOutput)
    {
      internal::TelnetLogger::singleton(listenPort, maxSecondsWaitForSocketToBeAvailable, colorizeOutput);
    }

    void IHelper::installOutgoingTelnetLogger(
                                              const char *serverHostWithPort,
                                              bool colorizeOutput,
                                              const char *sendStringUponConnection
                                              )
    {
      internal::TelnetLogger::singleton(serverHostWithPort, colorizeOutput, sendStringUponConnection);
    }

    void IHelper::installWindowsDebuggerLogger()
    {
      internal::WindowsDebuggerLogger::singleton();
    }

    void IHelper::uninstallStdOutLogger()
    {
      internal::StdOutLogger::singleton(false, true);
    }

    void IHelper::uninstallFileLogger()
    {
      internal::FileLogger::singleton(NULL, false, true);
    }

    void IHelper::uninstallTelnetLogger()
    {
      internal::TelnetLogger::singleton(0, 0, false, true);
    }

    void IHelper::uninstallOutgoingTelnetLogger()
    {
      internal::TelnetLogger::singleton((const char *)NULL, false, (const char *)NULL);
    }

    void IHelper::uninstallWindowsDebuggerLogger()
    {
      internal::WindowsDebuggerLogger::singleton(true);
    }

    void IHelper::setLogLevel(Log::Level logLevel)
    {
      internal::LogLevelLoggerPtr logger = internal::LogLevelLogger::singleton();
      logger->setLogLevel(logLevel);
    }

    void IHelper::setLogLevel(const char *component, Log::Level logLevel)
    {
      internal::LogLevelLoggerPtr logger = internal::LogLevelLogger::singleton();
      logger->setLogLevel(component, logLevel);
    }

  }
}

max_stars_repo_name: ilin-in/OP

max_issues_repo_name: ilin-in/OP

max_forks_repo_name: ilin-in/OP


text:
#define CATCH_CONFIG_MAIN
#include "../../vendor/catch/catch.hpp"

#include "event_queue.hpp"
#include <boost/optional/optional_io.hpp>

#define ENQUEUE_EVENT(QUEUE, DEVICE_ID, TIME_STAMP, EVENT, EVENT_TYPE, ORIGINAL_EVENT)           \
  QUEUE.emplace_back_event(krbn::device_id(DEVICE_ID),                                           \
                           krbn::event_queue::event_time_stamp(krbn::absolute_time(TIME_STAMP)), \
                           EVENT,                                                                \
                           krbn::event_type::EVENT_TYPE,                                         \
                           ORIGINAL_EVENT)

#define PUSH_BACK_ENTRY(VECTOR, DEVICE_ID, TIME_STAMP, EVENT, EVENT_TYPE, ORIGINAL_EVENT)                         \
  VECTOR.push_back(krbn::event_queue::entry(krbn::device_id(DEVICE_ID),                                           \
                                            krbn::event_queue::event_time_stamp(krbn::absolute_time(TIME_STAMP)), \
                                            EVENT,                                                                \
                                            krbn::event_type::EVENT_TYPE,                                         \
                                            ORIGINAL_EVENT))

namespace {
krbn::event_queue::event a_event(krbn::key_code::a);
krbn::event_queue::event b_event(krbn::key_code::b);
krbn::event_queue::event caps_lock_event(krbn::key_code::caps_lock);
krbn::event_queue::event escape_event(krbn::key_code::escape);
krbn::event_queue::event left_control_event(krbn::key_code::left_control);
krbn::event_queue::event left_shift_event(krbn::key_code::left_shift);
krbn::event_queue::event right_control_event(krbn::key_code::right_control);
krbn::event_queue::event right_shift_event(krbn::key_code::right_shift);
krbn::event_queue::event spacebar_event(krbn::key_code::spacebar);
krbn::event_queue::event tab_event(krbn::key_code::tab);

krbn::event_queue::event mute_event(krbn::consumer_key_code::mute);

krbn::event_queue::event button2_event(krbn::pointing_button::button2);

krbn::event_queue::event caps_lock_state_changed_1_event(krbn::event_queue::event::type::caps_lock_state_changed, 1);
krbn::event_queue::event caps_lock_state_changed_0_event(krbn::event_queue::event::type::caps_lock_state_changed, 0);

auto device_keys_and_pointing_buttons_are_released_event = krbn::event_queue::event::make_device_keys_and_pointing_buttons_are_released_event();
} // namespace

TEST_CASE("event_time_stamp") {
  {
    krbn::event_queue::event_time_stamp event_time_stamp1(krbn::absolute_time(100),
                                                          krbn::absolute_time_duration(10));
    krbn::event_queue::event_time_stamp event_time_stamp2(krbn::absolute_time(200),
                                                          krbn::absolute_time_duration(10));
    krbn::event_queue::event_time_stamp event_time_stamp3(krbn::absolute_time(100),
                                                          krbn::absolute_time_duration(20));
    krbn::event_queue::event_time_stamp event_time_stamp4(krbn::absolute_time(200),
                                                          krbn::absolute_time_duration(20));

    REQUIRE(event_time_stamp1.get_time_stamp() == krbn::absolute_time(100));
    REQUIRE(event_time_stamp1.get_input_delay_duration() == krbn::absolute_time_duration(10));

    REQUIRE(event_time_stamp2.get_time_stamp() == krbn::absolute_time(200));
    REQUIRE(event_time_stamp2.get_input_delay_duration() == krbn::absolute_time_duration(10));

    REQUIRE(event_time_stamp3.get_time_stamp() == krbn::absolute_time(100));
    REQUIRE(event_time_stamp3.get_input_delay_duration() == krbn::absolute_time_duration(20));

    REQUIRE(event_time_stamp4.get_time_stamp() == krbn::absolute_time(200));
    REQUIRE(event_time_stamp4.get_input_delay_duration() == krbn::absolute_time_duration(20));

    REQUIRE(event_time_stamp1 == event_time_stamp1);
    REQUIRE(event_time_stamp1 != event_time_stamp2);
    REQUIRE(event_time_stamp1 != event_time_stamp3);
    REQUIRE(event_time_stamp1 != event_time_stamp4);

    krbn::event_queue::event_time_stamp copy1(event_time_stamp1);
    auto copy2 = event_time_stamp1;

    REQUIRE(event_time_stamp1 == copy1);
    REQUIRE(event_time_stamp1 == copy2);
  }
}

TEST_CASE("entry") {
  krbn::event_queue::entry entry1(krbn::device_id(1),
                                  krbn::event_queue::event_time_stamp(krbn::absolute_time(100),
                                                                      krbn::absolute_time_duration(10)),
                                  krbn::event_queue::event(krbn::key_code::a),
                                  krbn::event_type::key_down,
                                  krbn::event_queue::event(krbn::key_code::a),
                                  false);
  auto entry2 = entry1;
  REQUIRE(entry1 == entry2);

  entry2.set_lazy(true);
  REQUIRE(entry1 != entry2);
}

TEST_CASE("json") {
  {
    nlohmann::json expected;
    expected["type"] = "key_code";
    expected["key_code"] = "a";
    auto json = a_event.to_json();
    REQUIRE(json == expected);
    auto event_from_json = krbn::event_queue::event::make_from_json(json);
    REQUIRE(json == event_from_json.to_json());
  }
  {
    nlohmann::json expected;
    expected["type"] = "consumer_key_code";
    expected["consumer_key_code"] = "mute";
    auto json = mute_event.to_json();
    REQUIRE(json == expected);
    auto event_from_json = krbn::event_queue::event::make_from_json(json);
    REQUIRE(json == event_from_json.to_json());
  }
  {
    nlohmann::json expected;
    expected["type"] = "pointing_button";
    expected["pointing_button"] = "button2";
    auto json = button2_event.to_json();
    REQUIRE(json == expected);
    auto event_from_json = krbn::event_queue::event::make_from_json(json);
    REQUIRE(json == event_from_json.to_json());
  }
  {
    nlohmann::json expected;
    expected["type"] = "pointing_motion";
    expected["pointing_motion"] = nlohmann::json::object();
    expected["pointing_motion"]["x"] = 1;
    expected["pointing_motion"]["y"] = 2;
    expected["pointing_motion"]["vertical_wheel"] = 3;
    expected["pointing_motion"]["horizontal_wheel"] = 4;

    krbn::pointing_motion pointing_motion;
    pointing_motion.set_x(1);
    pointing_motion.set_y(2);
    pointing_motion.set_vertical_wheel(3);
    pointing_motion.set_horizontal_wheel(4);

    auto e = krbn::event_queue::event(pointing_motion);

    auto json = e.to_json();
    REQUIRE(json == expected);
    auto event_from_json = krbn::event_queue::event::make_from_json(json);
    REQUIRE(json == event_from_json.to_json());
  }
  {
    nlohmann::json expected;
    expected["type"] = "caps_lock_state_changed";
    expected["integer_value"] = 1;
    auto json = caps_lock_state_changed_1_event.to_json();
    REQUIRE(json == expected);
    auto event_from_json = krbn::event_queue::event::make_from_json(json);
    REQUIRE(json == event_from_json.to_json());
  }
  {
    nlohmann::json expected;
    expected["type"] = "shell_command";
    expected["shell_command"] = "open https://pqrs.org";
    auto json = krbn::event_queue::event::make_shell_command_event("open https://pqrs.org").to_json();
    REQUIRE(json == expected);
    auto event_from_json = krbn::event_queue::event::make_from_json(json);
    REQUIRE(json == event_from_json.to_json());
  }
  {
    nlohmann::json expected;
    expected["type"] = "select_input_source";
    expected["input_source_selectors"] = nlohmann::json::array();
    expected["input_source_selectors"].push_back(nlohmann::json::object());
    expected["input_source_selectors"].back()["language"] = "en";
    auto e = krbn::event_queue::event::make_select_input_source_event({krbn::input_source_selector(std::string("en"),
                                                                                boost::none,
                                                                                boost::none)});
    auto json = e.to_json();
    REQUIRE(json == expected);
    auto event_from_json = krbn::event_queue::event::make_from_json(json);
    REQUIRE(json == event_from_json.to_json());
  }
  {
    nlohmann::json expected;
    expected["type"] = "set_variable";
    expected["set_variable"]["name"] = "example1";
    expected["set_variable"]["value"] = 100;
    auto json = krbn::event_queue::event::make_set_variable_event(std::make_pair("example1", 100)).to_json();
    REQUIRE(json == expected);
    auto event_from_json = krbn::event_queue::event::make_from_json(json);
    REQUIRE(json == event_from_json.to_json());
  }
  {
    nlohmann::json expected;
    expected["type"] = "frontmost_application_changed";
    expected["frontmost_application"]["bundle_identifier"] = "com.apple.Terminal";
    expected["frontmost_application"]["file_path"] = "/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal";
    auto e = krbn::event_queue::event::make_frontmost_application_changed_event("com.apple.Terminal",
                                                                                "/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal");
    auto json = e.to_json();
    REQUIRE(json == expected);
    auto event_from_json = krbn::event_queue::event::make_from_json(json);
    REQUIRE(json == event_from_json.to_json());
  }
  {
    nlohmann::json expected;
    expected["type"] = "input_source_changed";
    expected["input_source_identifiers"]["language"] = "en";
    expected["input_source_identifiers"]["input_source_id"] = "com.apple.keylayout.US";
    auto e = krbn::event_queue::event::make_input_source_changed_event({std::string("en"),
                                                                        std::string("com.apple.keylayout.US"),
                                                                        boost::none});
    auto json = e.to_json();
    REQUIRE(json == expected);
    auto event_from_json = krbn::event_queue::event::make_from_json(json);
    REQUIRE(json == event_from_json.to_json());
  }
  {
    nlohmann::json expected;
    expected["type"] = "keyboard_type_changed";
    expected["keyboard_type"] = "iso";
    auto e = krbn::event_queue::event::make_keyboard_type_changed_event("iso");
    auto json = e.to_json();
    REQUIRE(json == expected);
    auto event_from_json = krbn::event_queue::event::make_from_json(json);
    REQUIRE(json == event_from_json.to_json());
  }
  {
    nlohmann::json expected;
    expected["type"] = "device_keys_and_pointing_buttons_are_released";
    auto json = device_keys_and_pointing_buttons_are_released_event.to_json();
    REQUIRE(json == expected);
    auto event_from_json = krbn::event_queue::event::make_from_json(json);
    REQUIRE(json == event_from_json.to_json());
  }
}

TEST_CASE("get_key_code") {
  REQUIRE(spacebar_event.get_key_code() == krbn::key_code::spacebar);
  REQUIRE(button2_event.get_key_code() == boost::none);
  REQUIRE(caps_lock_state_changed_1_event.get_key_code() == boost::none);
  REQUIRE(caps_lock_state_changed_0_event.get_key_code() == boost::none);
  REQUIRE(device_keys_and_pointing_buttons_are_released_event.get_key_code() == boost::none);
}

TEST_CASE("get_consumer_key_code") {
  REQUIRE(spacebar_event.get_consumer_key_code() == boost::none);
  REQUIRE(mute_event.get_consumer_key_code() == krbn::consumer_key_code::mute);
}

TEST_CASE("get_frontmost_application_bundle_identifier") {
  REQUIRE(a_event.get_frontmost_application() == boost::none);

  {
    std::string bundle_identifier = "org.pqrs.example";
    std::string file_path = "/opt/bin/examle";
    auto e = krbn::event_queue::event::make_frontmost_application_changed_event(bundle_identifier,
                                                                                file_path);
    REQUIRE(e.get_frontmost_application() != boost::none);
    REQUIRE(e.get_frontmost_application()->get_bundle_identifier() == bundle_identifier);
    REQUIRE(e.get_frontmost_application()->get_file_path() == file_path);
  }
}

TEST_CASE("emplace_back_event") {
  // Normal order
  {
    krbn::event_queue::queue event_queue;

    ENQUEUE_EVENT(event_queue, 1, 100, a_event, key_down, a_event);

    REQUIRE(event_queue.get_modifier_flag_manager().is_pressed(krbn::modifier_flag::left_shift) == false);

    ENQUEUE_EVENT(event_queue, 1, 200, left_shift_event, key_down, left_shift_event);

    REQUIRE(event_queue.get_modifier_flag_manager().is_pressed(krbn::modifier_flag::left_shift) == true);

    ENQUEUE_EVENT(event_queue, 1, 300, button2_event, key_down, button2_event);

    REQUIRE(event_queue.get_pointing_button_manager().is_pressed(krbn::pointing_button::button2) == true);

    ENQUEUE_EVENT(event_queue, 1, 400, left_shift_event, key_up, left_shift_event);

    REQUIRE(event_queue.get_modifier_flag_manager().is_pressed(krbn::modifier_flag::left_shift) == false);

    ENQUEUE_EVENT(event_queue, 1, 500, a_event, key_up, a_event);

    REQUIRE(event_queue.get_modifier_flag_manager().is_pressed(krbn::modifier_flag::left_shift) == false);

    ENQUEUE_EVENT(event_queue, 1, 600, button2_event, key_up, button2_event);

    REQUIRE(event_queue.get_pointing_button_manager().is_pressed(krbn::pointing_button::button2) == false);

    std::vector<krbn::event_queue::entry> expected;
    PUSH_BACK_ENTRY(expected, 1, 100, a_event, key_down, a_event);
    PUSH_BACK_ENTRY(expected, 1, 200, left_shift_event, key_down, left_shift_event);
    PUSH_BACK_ENTRY(expected, 1, 300, button2_event, key_down, button2_event);
    PUSH_BACK_ENTRY(expected, 1, 400, left_shift_event, key_up, left_shift_event);
    PUSH_BACK_ENTRY(expected, 1, 500, a_event, key_up, a_event);
    PUSH_BACK_ENTRY(expected, 1, 600, button2_event, key_up, button2_event);
    REQUIRE(event_queue.get_entries() == expected);

    REQUIRE(event_queue.get_entries()[0].get_valid() == true);
    REQUIRE(event_queue.get_entries()[0].get_lazy() == false);
  }

  // Reorder events
  {
    krbn::event_queue::queue event_queue;

    // Push `a, left_control, left_shift (key_down)` at the same time.

    ENQUEUE_EVENT(event_queue, 1, 100, left_control_event, key_down, left_control_event);
    ENQUEUE_EVENT(event_queue, 1, 100, a_event, key_down, a_event);
    ENQUEUE_EVENT(event_queue, 1, 100, left_shift_event, key_down, left_shift_event);
    ENQUEUE_EVENT(event_queue, 1, 200, spacebar_event, key_down, spacebar_event);

    // Push `a, left_control, left_shift (key_up)` at the same time.

    ENQUEUE_EVENT(event_queue, 1, 300, left_shift_event, key_up, left_shift_event);
    ENQUEUE_EVENT(event_queue, 1, 300, a_event, key_up, a_event);
    ENQUEUE_EVENT(event_queue, 1, 300, left_control_event, key_up, left_control_event);

    // Other events (not key_code) order are preserved.

    ENQUEUE_EVENT(event_queue, 1, 400, left_shift_event, key_down, left_shift_event);
    ENQUEUE_EVENT(event_queue, 1, 400, device_keys_and_pointing_buttons_are_released_event, single, device_keys_and_pointing_buttons_are_released_event);

    ENQUEUE_EVENT(event_queue, 1, 500, device_keys_and_pointing_buttons_are_released_event, single, device_keys_and_pointing_buttons_are_released_event);
    ENQUEUE_EVENT(event_queue, 1, 500, left_shift_event, key_down, left_shift_event);

    std::vector<krbn::event_queue::entry> expected;
    PUSH_BACK_ENTRY(expected, 1, 100, left_control_event, key_down, left_control_event);
    PUSH_BACK_ENTRY(expected, 1, 100, left_shift_event, key_down, left_shift_event);
    PUSH_BACK_ENTRY(expected, 1, 100, a_event, key_down, a_event);
    PUSH_BACK_ENTRY(expected, 1, 200, spacebar_event, key_down, spacebar_event);
    PUSH_BACK_ENTRY(expected, 1, 300, a_event, key_up, a_event);
    PUSH_BACK_ENTRY(expected, 1, 300, left_shift_event, key_up, left_shift_event);
    PUSH_BACK_ENTRY(expected, 1, 300, left_control_event, key_up, left_control_event);
    PUSH_BACK_ENTRY(expected, 1, 400, left_shift_event, key_down, left_shift_event);
    PUSH_BACK_ENTRY(expected, 1, 400, device_keys_and_pointing_buttons_are_released_event, single, device_keys_and_pointing_buttons_are_released_event);
    PUSH_BACK_ENTRY(expected, 1, 500, device_keys_and_pointing_buttons_are_released_event, single, device_keys_and_pointing_buttons_are_released_event);
    PUSH_BACK_ENTRY(expected, 1, 500, left_shift_event, key_down, left_shift_event);
    REQUIRE(event_queue.get_entries() == expected);
  }
  {
    krbn::event_queue::queue event_queue;

    ENQUEUE_EVENT(event_queue, 1, 100, a_event, key_down, a_event);
    ENQUEUE_EVENT(event_queue, 1, 100, left_shift_event, key_down, left_shift_event);
    ENQUEUE_EVENT(event_queue, 1, 100, b_event, key_down, b_event);
    ENQUEUE_EVENT(event_queue, 1, 100, left_control_event, key_down, left_control_event);

    std::vector<krbn::event_queue::entry> expected;
    PUSH_BACK_ENTRY(expected, 1, 100, left_shift_event, key_down, left_shift_event);
    PUSH_BACK_ENTRY(expected, 1, 100, left_control_event, key_down, left_control_event);
    PUSH_BACK_ENTRY(expected, 1, 100, a_event, key_down, a_event);
    PUSH_BACK_ENTRY(expected, 1, 100, b_event, key_down, b_event);
    REQUIRE(event_queue.get_entries() == expected);
  }
  {
    krbn::event_queue::queue event_queue;

    ENQUEUE_EVENT(event_queue, 1, 100, b_event, key_down, b_event);
    ENQUEUE_EVENT(event_queue, 1, 100, a_event, key_down, a_event);
    ENQUEUE_EVENT(event_queue, 1, 100, left_control_event, key_down, left_control_event);
    ENQUEUE_EVENT(event_queue, 1, 100, left_shift_event, key_down, left_shift_event);

    std::vector<krbn::event_queue::entry> expected;
    PUSH_BACK_ENTRY(expected, 1, 100, left_control_event, key_down, left_control_event);
    PUSH_BACK_ENTRY(expected, 1, 100, left_shift_event, key_down, left_shift_event);
    PUSH_BACK_ENTRY(expected, 1, 100, b_event, key_down, b_event);
    PUSH_BACK_ENTRY(expected, 1, 100, a_event, key_down, a_event);
    REQUIRE(event_queue.get_entries() == expected);
  }
  {
    krbn::event_queue::queue event_queue;

    ENQUEUE_EVENT(event_queue, 1, 100, b_event, key_up, b_event);
    ENQUEUE_EVENT(event_queue, 1, 100, a_event, key_up, a_event);
    ENQUEUE_EVENT(event_queue, 1, 100, left_control_event, key_down, left_control_event);
    ENQUEUE_EVENT(event_queue, 1, 100, left_shift_event, key_down, left_shift_event);

    std::vector<krbn::event_queue::entry> expected;
    PUSH_BACK_ENTRY(expected, 1, 100, left_control_event, key_down, left_control_event);
    PUSH_BACK_ENTRY(expected, 1, 100, left_shift_event, key_down, left_shift_event);
    PUSH_BACK_ENTRY(expected, 1, 100, b_event, key_up, b_event);
    PUSH_BACK_ENTRY(expected, 1, 100, a_event, key_up, a_event);
    REQUIRE(event_queue.get_entries() == expected);
  }
  {
    krbn::event_queue::queue event_queue;

    ENQUEUE_EVENT(event_queue, 1, 100, b_event, key_up, b_event);
    ENQUEUE_EVENT(event_queue, 1, 100, a_event, key_up, a_event);
    ENQUEUE_EVENT(event_queue, 1, 100, device_keys_and_pointing_buttons_are_released_event, single, device_keys_and_pointing_buttons_are_released_event);
    ENQUEUE_EVENT(event_queue, 1, 100, left_control_event, key_down, left_control_event);
    ENQUEUE_EVENT(event_queue, 1, 100, left_shift_event, key_down, left_shift_event);

    std::vector<krbn::event_queue::entry> expected;
    PUSH_BACK_ENTRY(expected, 1, 100, b_event, key_up, b_event);
    PUSH_BACK_ENTRY(expected, 1, 100, a_event, key_up, a_event);
    PUSH_BACK_ENTRY(expected, 1, 100, device_keys_and_pointing_buttons_are_released_event, single, device_keys_and_pointing_buttons_are_released_event);
    PUSH_BACK_ENTRY(expected, 1, 100, left_control_event, key_down, left_control_event);
    PUSH_BACK_ENTRY(expected, 1, 100, left_shift_event, key_down, left_shift_event);
    REQUIRE(event_queue.get_entries() == expected);
  }
}

TEST_CASE("needs_swap") {
  krbn::event_queue::entry spacebar_down(krbn::device_id(1),
                                         krbn::event_queue::event_time_stamp(
                                             krbn::absolute_time(100)),
                                         spacebar_event,
                                         krbn::event_type::key_down,
                                         spacebar_event);
  krbn::event_queue::entry right_shift_down(krbn::device_id(1),
                                            krbn::event_queue::event_time_stamp(
                                                krbn::absolute_time(100)),
                                            right_shift_event,
                                            krbn::event_type::key_down,
                                            right_shift_event);
  krbn::event_queue::entry escape_down(krbn::device_id(1),
                                       krbn::event_queue::event_time_stamp(
                                           krbn::absolute_time(200)),
                                       escape_event,
                                       krbn::event_type::key_down,
                                       escape_event);
  krbn::event_queue::entry spacebar_up(krbn::device_id(1),
                                       krbn::event_queue::event_time_stamp(
                                           krbn::absolute_time(300)),
                                       spacebar_event,
                                       krbn::event_type::key_up,
                                       spacebar_event);
  krbn::event_queue::entry right_shift_up(krbn::device_id(1),
                                          krbn::event_queue::event_time_stamp(
                                              krbn::absolute_time(300)),
                                          right_shift_event,
                                          krbn::event_type::key_up,
                                          right_shift_event);

  REQUIRE(krbn::event_queue::queue::needs_swap(spacebar_down, spacebar_down) == false);
  REQUIRE(krbn::event_queue::queue::needs_swap(spacebar_down, escape_down) == false);
  REQUIRE(krbn::event_queue::queue::needs_swap(escape_down, spacebar_down) == false);

  REQUIRE(krbn::event_queue::queue::needs_swap(spacebar_down, right_shift_down) == true);
  REQUIRE(krbn::event_queue::queue::needs_swap(right_shift_down, spacebar_down) == false);

  REQUIRE(krbn::event_queue::queue::needs_swap(spacebar_down, right_shift_up) == false);
  REQUIRE(krbn::event_queue::queue::needs_swap(right_shift_up, spacebar_down) == false);

  REQUIRE(krbn::event_queue::queue::needs_swap(spacebar_up, right_shift_up) == false);
  REQUIRE(krbn::event_queue::queue::needs_swap(right_shift_up, spacebar_up) == true);

  REQUIRE(krbn::event_queue::queue::needs_swap(spacebar_up, right_shift_down) == false);
  REQUIRE(krbn::event_queue::queue::needs_swap(right_shift_down, spacebar_up) == false);
}

TEST_CASE("increase_time_stamp_delay") {
  {
    krbn::event_queue::queue event_queue;

    ENQUEUE_EVENT(event_queue, 1, 100, tab_event, key_down, tab_event);

    event_queue.increase_time_stamp_delay(krbn::absolute_time_duration(10));

    ENQUEUE_EVENT(event_queue, 1, 200, tab_event, key_up, tab_event);

    ENQUEUE_EVENT(event_queue, 1, 300, tab_event, key_down, tab_event);

    ENQUEUE_EVENT(event_queue, 1, 400, tab_event, key_up, tab_event);

    std::vector<krbn::event_queue::entry> expected;
    PUSH_BACK_ENTRY(expected, 1, 100, tab_event, key_down, tab_event);
    PUSH_BACK_ENTRY(expected, 1, 210, tab_event, key_up, tab_event);
    PUSH_BACK_ENTRY(expected, 1, 310, tab_event, key_down, tab_event);
    PUSH_BACK_ENTRY(expected, 1, 410, tab_event, key_up, tab_event);
    REQUIRE(event_queue.get_entries() == expected);

    while (!event_queue.empty()) {
      event_queue.erase_front_event();
    }
    REQUIRE(event_queue.get_time_stamp_delay() == krbn::absolute_time_duration(0));
  }
}

TEST_CASE("caps_lock_state_changed") {
  {
    krbn::event_queue::queue event_queue;

    // modifier_flag_manager's caps lock state will not be changed by key_down event.
    ENQUEUE_EVENT(event_queue, 1, 100, caps_lock_event, key_down, caps_lock_event);

    REQUIRE(event_queue.get_modifier_flag_manager().is_pressed(krbn::modifier_flag::caps_lock) == false);

    ENQUEUE_EVENT(event_queue, 1, 100, caps_lock_state_changed_1_event, single, caps_lock_state_changed_1_event);

    REQUIRE(event_queue.get_modifier_flag_manager().is_pressed(krbn::modifier_flag::caps_lock) == true);

    // Send twice

    ENQUEUE_EVENT(event_queue, 1, 100, caps_lock_state_changed_1_event, single, caps_lock_state_changed_1_event);

    REQUIRE(event_queue.get_modifier_flag_manager().is_pressed(krbn::modifier_flag::caps_lock) == true);

    ENQUEUE_EVENT(event_queue, 1, 100, caps_lock_state_changed_0_event, single, caps_lock_state_changed_0_event);

    REQUIRE(event_queue.get_modifier_flag_manager().is_pressed(krbn::modifier_flag::caps_lock) == false);

    ENQUEUE_EVENT(event_queue, 1, 100, caps_lock_state_changed_1_event, single, caps_lock_state_changed_1_event);

    REQUIRE(event_queue.get_modifier_flag_manager().is_pressed(krbn::modifier_flag::caps_lock) == true);
  }
}

TEST_CASE("make_entries") {
  std::vector<krbn::hid_value> hid_values;

  hid_values.emplace_back(krbn::hid_value(krbn::absolute_time(1000),
                                          1,
                                          *krbn::types::make_hid_usage_page(krbn::key_code::spacebar),
                                          *krbn::types::make_hid_usage(krbn::key_code::spacebar)));
  hid_values.emplace_back(krbn::hid_value(krbn::absolute_time(2000),
                                          0,
                                          *krbn::types::make_hid_usage_page(krbn::key_code::spacebar),
                                          *krbn::types::make_hid_usage(krbn::key_code::spacebar)));

  hid_values.emplace_back(krbn::hid_value(krbn::absolute_time(3000),
                                          1,
                                          *krbn::types::make_hid_usage_page(krbn::consumer_key_code::mute),
                                          *krbn::types::make_hid_usage(krbn::consumer_key_code::mute)));
  hid_values.emplace_back(krbn::hid_value(krbn::absolute_time(4000),
                                          0,
                                          *krbn::types::make_hid_usage_page(krbn::consumer_key_code::mute),
                                          *krbn::types::make_hid_usage(krbn::consumer_key_code::mute)));

  hid_values.emplace_back(krbn::hid_value(krbn::absolute_time(5000),
                                          10,
                                          krbn::hid_usage_page::generic_desktop,
                                          krbn::hid_usage::gd_x));
  hid_values.emplace_back(krbn::hid_value(krbn::absolute_time(5000),
                                          20,
                                          krbn::hid_usage_page::generic_desktop,
                                          krbn::hid_usage::gd_y));
  hid_values.emplace_back(krbn::hid_value(krbn::absolute_time(5000),
                                          30,
                                          krbn::hid_usage_page::generic_desktop,
                                          krbn::hid_usage::gd_wheel));
  hid_values.emplace_back(krbn::hid_value(krbn::absolute_time(5000),
                                          40,
                                          krbn::hid_usage_page::consumer,
                                          krbn::hid_usage::csmr_acpan));

  hid_values.emplace_back(krbn::hid_value(krbn::absolute_time(6000),
                                          -10,
                                          krbn::hid_usage_page::generic_desktop,
                                          krbn::hid_usage::gd_x));
  hid_values.emplace_back(krbn::hid_value(krbn::absolute_time(6000),
                                          -20,
                                          krbn::hid_usage_page::generic_desktop,
                                          krbn::hid_usage::gd_y));
  hid_values.emplace_back(krbn::hid_value(krbn::absolute_time(6000),
                                          -30,
                                          krbn::hid_usage_page::generic_desktop,
                                          krbn::hid_usage::gd_wheel));
  hid_values.emplace_back(krbn::hid_value(krbn::absolute_time(6000),
                                          -40,
                                          krbn::hid_usage_page::consumer,
                                          krbn::hid_usage::csmr_acpan));

  hid_values.emplace_back(krbn::hid_value(krbn::absolute_time(7000),
                                          10,
                                          krbn::hid_usage_page::generic_desktop,
                                          krbn::hid_usage::gd_x));

  hid_values.emplace_back(krbn::hid_value(krbn::absolute_time(8000),
                                          0,
                                          krbn::hid_usage_page::generic_desktop,
                                          krbn::hid_usage::gd_x));

  auto entrys = krbn::event_queue::queue::make_entries(hid_values,
                                                       krbn::device_id(1));
  REQUIRE(entrys.size() == 8);
  REQUIRE(*(entrys[0].first) == hid_values[0]);
  REQUIRE(*(entrys[1].first) == hid_values[1]);
  REQUIRE(*(entrys[2].first) == hid_values[2]);
  REQUIRE(*(entrys[3].first) == hid_values[3]);
  REQUIRE(!entrys[4].first);
  REQUIRE(!entrys[5].first);
  REQUIRE(!entrys[6].first);
  REQUIRE(!entrys[7].first);

  REQUIRE(entrys[0].second.get_device_id() == krbn::device_id(1));
  REQUIRE(entrys[0].second.get_event_time_stamp().get_time_stamp() == krbn::absolute_time(1000));
  REQUIRE(entrys[0].second.get_event().get_key_code() == krbn::key_code::spacebar);
  REQUIRE(entrys[0].second.get_event_type() == krbn::event_type::key_down);

  REQUIRE(entrys[1].second.get_device_id() == krbn::device_id(1));
  REQUIRE(entrys[1].second.get_event_time_stamp().get_time_stamp() == krbn::absolute_time(2000));
  REQUIRE(entrys[1].second.get_event().get_key_code() == krbn::key_code::spacebar);
  REQUIRE(entrys[1].second.get_event_type() == krbn::event_type::key_up);

  REQUIRE(entrys[2].second.get_device_id() == krbn::device_id(1));
  REQUIRE(entrys[2].second.get_event_time_stamp().get_time_stamp() == krbn::absolute_time(3000));
  REQUIRE(entrys[2].second.get_event().get_consumer_key_code() == krbn::consumer_key_code::mute);
  REQUIRE(entrys[2].second.get_event_type() == krbn::event_type::key_down);

  REQUIRE(entrys[3].second.get_device_id() == krbn::device_id(1));
  REQUIRE(entrys[3].second.get_event_time_stamp().get_time_stamp() == krbn::absolute_time(4000));
  REQUIRE(entrys[3].second.get_event().get_consumer_key_code() == krbn::consumer_key_code::mute);
  REQUIRE(entrys[3].second.get_event_type() == krbn::event_type::key_up);

  REQUIRE(entrys[4].second.get_device_id() == krbn::device_id(1));
  REQUIRE(entrys[4].second.get_event_time_stamp().get_time_stamp() == krbn::absolute_time(5000));
  REQUIRE(entrys[4].second.get_event().get_pointing_motion() == krbn::pointing_motion(10, 20, 30, 40));
  REQUIRE(entrys[4].second.get_event_type() == krbn::event_type::single);

  REQUIRE(entrys[5].second.get_device_id() == krbn::device_id(1));
  REQUIRE(entrys[5].second.get_event_time_stamp().get_time_stamp() == krbn::absolute_time(6000));
  REQUIRE(entrys[5].second.get_event().get_pointing_motion() == krbn::pointing_motion(-10, -20, -30, -40));
  REQUIRE(entrys[5].second.get_event_type() == krbn::event_type::single);

  REQUIRE(entrys[6].second.get_device_id() == krbn::device_id(1));
  REQUIRE(entrys[6].second.get_event_time_stamp().get_time_stamp() == krbn::absolute_time(7000));
  REQUIRE(entrys[6].second.get_event().get_pointing_motion() == krbn::pointing_motion(10, 0, 0, 0));
  REQUIRE(entrys[6].second.get_event_type() == krbn::event_type::single);

  REQUIRE(entrys[7].second.get_device_id() == krbn::device_id(1));
  REQUIRE(entrys[7].second.get_event_time_stamp().get_time_stamp() == krbn::absolute_time(8000));
  REQUIRE(entrys[7].second.get_event().get_pointing_motion() == krbn::pointing_motion(0, 0, 0, 0));
  REQUIRE(entrys[7].second.get_event_type() == krbn::event_type::single);
}

TEST_CASE("hash") {
  REQUIRE(hash_value(krbn::event_queue::event(krbn::key_code::a)) !=
          hash_value(krbn::event_queue::event(krbn::key_code::b)));
}

ext: cpp

max_stars_repo_name: cyrusccy/Karabiner-Elements

max_issues_repo_name: cyrusccy/Karabiner-Elements

max_forks_repo_name: cyrusccy/Karabiner-Elements