#ifndef _PAINLESS_MESH_LOGGER_HPP_
#define _PAINLESS_MESH_LOGGER_HPP_

#include <list>

namespace painlessmesh {
namespace logger {
#include <stdarg.h>
#include "Arduino.h"

#define REMOTE_QUEUE_SIZE 10

// Windows defines ERROR as a macro, undefine it to avoid conflicts
#ifdef ERROR
#undef ERROR
#endif

typedef enum {
  ERROR = 1 << 0,
  STARTUP = 1 << 1,
  MESH_STATUS = 1 << 2,
  CONNECTION = 1 << 3,
  SYNC = 1 << 4,
  S_TIME = 1 << 5,
  COMMUNICATION = 1 << 6,
  GENERAL = 1 << 7,
  MSG_TYPES = 1 << 8,
  REMOTE = 1 << 9,  // not yet implemented
  APPLICATION = 1 << 10,
  DEBUG = 1 << 11
} LogLevel;

class LogClass {
 public:
  void setLogLevel(uint16_t newTypes) {
    // set the different kinds of debug messages you want to generate.
    types = newTypes;
    Serial.print(F("\nsetLogLevel:"));
    if (types & ERROR) {
      Serial.print(F(" ERROR |"));
    }
    if (types & STARTUP) {
      Serial.print(F(" STARTUP |"));
    }
    if (types & MESH_STATUS) {
      Serial.print(F(" MESH_STATUS |"));
    }
    if (types & CONNECTION) {
      Serial.print(F(" CONNECTION |"));
    }
    if (types & SYNC) {
      Serial.print(F(" SYNC |"));
    }
    if (types & S_TIME) {
      Serial.print(F(" S_TIME |"));
    }
    if (types & COMMUNICATION) {
      Serial.print(F(" COMMUNICATION |"));
    }
    if (types & GENERAL) {
      Serial.print(F(" GENERAL |"));
    }
    if (types & MSG_TYPES) {
      Serial.print(F(" MSG_TYPES |"));
    }
    if (types & REMOTE) {
      Serial.print(F(" REMOTE |"));
    }
    if (types & APPLICATION) {
      Serial.print(F(" APPLICATION |"));
    }
    if (types & DEBUG) {
      Serial.print(F(" DEBUG |"));
    }
    Serial.println();
    return;
  }
  void operator()(LogLevel type, const char *format...) {
    if (type & types) {  // Print only the message types set for output
      va_list args;
      va_start(args, format);

      vsnprintf(str, 200, format, args);

      if (types) {
        switch (type) {
          case ERROR:
            Serial.print(F("ERROR: "));
            break;
          case STARTUP:
            Serial.print(F("STARTUP: "));
            break;
          case MESH_STATUS:
            Serial.print(F("MESH_STATUS: "));
            break;
          case CONNECTION:
            Serial.print(F("CONNECTION: "));
            break;
          case SYNC:
            Serial.print(F("SYNC: "));
            break;
          case S_TIME:
            Serial.print(F("S_TIME: "));
            break;
          case COMMUNICATION:
            Serial.print(F("COMMUNICATION: "));
            break;
          case GENERAL:
            Serial.print(F("GENERAL: "));
            break;
          case MSG_TYPES:
            Serial.print(F("MSG_TYPES: "));
            break;
          case REMOTE:
            this->remote(format);
            Serial.print(F("REMOTE: "));
            break;
          case APPLICATION:
            Serial.print(F("APPLICATION: "));
            break;
          case DEBUG:
            Serial.print(F("DEBUG: "));
            break;
        }
      }

      Serial.print(str);

      va_end(args);
    }
  }

  void remote(const char *format...) {
    if (REMOTE & types) {  // Store only the message if REMOTE set
      va_list args;
      va_start(args, format);

      vsnprintf(str, 200, format, args);

      remote_queue.push_back(std::pair<uint32_t, TSTRING>(remote_uuid, str));
      ++remote_uuid;
      if (remote_queue.size() > REMOTE_QUEUE_SIZE) {
        // No place to store the reason, but do signify the queue is full,
        // discard any follow up
        remote_queue.pop_front();
      }
    }
  }

  std::list<std::pair<uint32_t, TSTRING>> &get_remote_queue() {
    return remote_queue;
  }

 private:
  uint16_t types = 0;
  char str[200];
  std::list<std::pair<uint32_t, TSTRING>> remote_queue;
  uint32_t remote_uuid;
};

}  // namespace logger
}  // namespace painlessmesh
#endif
