#pragma once
#include <stdio.h>
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <windows.h>
#include <time.h>
#include <string>
#include <BWAPI.h>
#include "../Scheduler/MP/BackgroundWorker.h"
#include <boost/thread.hpp>

struct LoggingTypes
{
  LoggingTypes();

  class EventType
  {
  public:
    const unsigned int code;
    const WORD color;
    static const WORD DEFAULT_COLOR;
    const std::string cssClass;
    static const unsigned int DEFAULT_HTML_COLOR;
    operator unsigned int() const 
    {
      return code;
    }
  protected:
    friend struct LoggingTypes;
    EventType(unsigned int _code, WORD _color, const std::string& _cssClass)
      : code(_code), color(_color), cssClass(_cssClass)
    {
    }
    EventType(unsigned int _code)
      : code(_code), color(DEFAULT_COLOR), cssClass("_")
    {
    }

  private:
    //Prevents using the assignment operator
    EventType& operator=(const EventType&);
    EventType(const EventType &);
  };

  const EventType
    ON_FRAME,
    ON_MORPH,
    ON_RENEGADE,
    ON_DESTROY,
    ON_DISCOVER,
    ON_EVADE,
    ON_SHOW,
    ON_HIDE,
    ON_CREATE,
    ON_COMPLETE,
    ON_NUKE_DETECT,
    GENERAL,
    LAYER_1_DECISION,
    LAYER_2_DECISION,
    LAYER_3_DECISION,
    LAYER_4_DECISION,
    LAYER_1_SITUATION_ANALYSIS,
    LAYER_2_SITUATION_ANALYSIS,
    LAYER_3_SITUATION_ANALYSIS,
    LAYER_4_SITUATION_ANALYSIS,
    PERCEPTUAL_STATE,
    GENERAL_WARNING,
    GENERAL_ERROR,
    GENERAL_INFO; // Can't name that ERROR due to stupid windows.h

  const unsigned int
    CALLBACKS,
    LAYERS,
    ALL;

  private:
  //Prevents using the assignment operator
  LoggingTypes& operator=(const LoggingTypes&);
  LoggingTypes(const LoggingTypes &);
};
extern LoggingTypes LogTypes;

namespace Log
{
  struct Event
  {
    std::string info;
    std::string text;
    const LoggingTypes::EventType * type;

    Event() : type(&LogTypes.GENERAL) {}
    Event(const std::string &te, const LoggingTypes::EventType &ty, const std::string &in) 
      : text(te), type(&ty), info(in) {}
  };
}

/**
 * @brief The logger prints color-enabled informative messages.
 *        Before Spar destruction, it uses a dedicated thread to print out messages.
 *        After Spar destruction, it prints out messages synchronously (i.e. in the same thread).
 */
class SparLogger : public BackgroundWorker<Log::Event>
{
  static SparLogger s_logger;

  unsigned int m_enabledLogger;

  FILE* m_logFile;
  HANDLE m_console;

  static HANDLE CreateConsoleLog(const char *winTitle);

  void handle(const Log::Event& ev) const;

  void run();
public:  
  SparLogger();
  ~SparLogger();

  static SparLogger& get()
  {
    return s_logger;
  }
  void activate(unsigned int mask)
  {
    m_enabledLogger |= mask;
  }
  void deactivate(unsigned int mask)
  {
    m_enabledLogger &= ~mask;
  }
  bool isActive(unsigned int mask)
  {
    return (m_enabledLogger & mask) == mask;
  }

  void post(const std::string &text, const LoggingTypes::EventType &type);

  void stopThread();
};

//
// from http://cnicholson.net/2009/03/stupid-c-tricks-dowhile0-and-c4127/
//
#define MULTI_LINE_MACRO_BEGIN do {
#define MULTI_LINE_MACRO_END \
	__pragma(warning(push)) \
	__pragma(warning(disable:4127)) \
	} while(0) \
	__pragma(warning(pop))
//
// --
//
#ifdef USE_SPAR_LOG
  #define SPAR_LOGGER_ACTIVATE_EVENT(mask) SparLogger::get()->activate(mask)
  #define SPAR_LOGGER_DEACTIVATE_EVENT(mask) SparLogger::get()->deactivate(mask)

  inline void SPAR_LOG(const LoggingTypes::EventType &eventType, const char* format, ...)
  {
    if(SparLogger::get().isActive(eventType)) 
    { 
      // string is not guaranteed to be contiguous in C++03 and as such cannot be used as a char buffer
      std::vector<char> xs(100); // somewhat arbitrary value
      va_list args;
      bool done = false;
      while(!done)
      {
        va_start(args, format);
        int n = vsnprintf(&xs[0], xs.size(), format, args);
        va_end(args);
        if(n == -1)
        {
          xs.resize(xs.size() * 2); // failure, increase buffer size and try again
        }
        else
        {
          xs.resize(n); // success, trim excess characters and post the result
          SparLogger::get().post(std::string(xs.begin(), xs.end()), eventType);
          done = true;
        }
      }
    } 
  }
#else
  #define SPAR_LOGGER_ACTIVATE_EVENT(type) 
  #define SPAR_LOGGER_DEACTIVATE_EVENT(type) 
  #define SPAR_LOG(format, ...) 
#endif
