#include "effects/Circular.h"
#include "effects/Static.h"
#include "effects/AlexaSwipe.h"
#include "effects/RandomTwoColorInterpolation.h"
#include "effects/SwipeAndChange.h"

#include "Arduino.h"
#include <cstdint>

constexpr uint32_t MAGIC_TOKEN_HOST_TO_FW = 0x1d6379e3;
constexpr uint32_t MAGIC_TOKEN_FW_TO_HOST = 0x10c65631;

template <typename T>
struct ClassToMessageType
{
};

enum class MessageFwToHost : uint8_t
{
  RFID_TOKEN_READ = 0,
  BUTTON_NORMAL_PRESS = 1,
  ROTARY_ENCODER = 2,
};

struct MsgRfidTokenRead
{
  uint8_t tagId[5];
};

template <>
struct ClassToMessageType<MsgRfidTokenRead>
{
  static constexpr auto msgType = MessageFwToHost::RFID_TOKEN_READ;
};

//----------------------------------------------------------------------------------------------------

enum class MessageHostToFw : uint8_t
{
  LED_WHEEL_EFFECT_STATIC = 0,
  LED_WHEEL_EFFECT_ALEXA_SWIPE = 1,
  LED_WHEEL_EFFECT_CIRCULAR = 2,
  LED_WHEEL_EFFECT_RANDOM_TWO_COLOR_INTERPOLATION = 3,
  LED_WHEEL_EFFECT_SWIPE_AND_CHANGE = 4
};

template <>
struct ClassToMessageType<EffectStaticConfig>
{
  static constexpr auto msgType = MessageHostToFw::LED_WHEEL_EFFECT_STATIC;
};

template <>
struct ClassToMessageType<EffectAlexaSwipeConfig>
{
  static constexpr auto msgType = MessageHostToFw::LED_WHEEL_EFFECT_ALEXA_SWIPE;
};

template <>
struct ClassToMessageType<EffectCircularConfig>
{
  static constexpr auto msgType = MessageHostToFw::LED_WHEEL_EFFECT_CIRCULAR;
};

template <>
struct ClassToMessageType<EffectRandomTwoColorInterpolationConfig>
{
  static constexpr auto msgType = MessageHostToFw::LED_WHEEL_EFFECT_RANDOM_TWO_COLOR_INTERPOLATION;
};

template <>
struct ClassToMessageType<EffectSwipeAndChangeConfig>
{
  static constexpr auto msgType = MessageHostToFw::LED_WHEEL_EFFECT_SWIPE_AND_CHANGE;
};

//----------------------------------------------------------------------------------------------------

template <typename TMessage>
void sendMessageToHost(const TMessage &msg)
{
  Serial.write((uint8_t *)&MAGIC_TOKEN_FW_TO_HOST, sizeof(MAGIC_TOKEN_FW_TO_HOST));
  MessageFwToHost msgType = ClassToMessageType<TMessage>::msgType;
  Serial.write((uint8_t *)&msgType, sizeof(msgType));

  uint16_t msgSize = sizeof(msg);
  Serial.write((uint8_t *)&msgSize, sizeof(msgSize));
  Serial.write((uint8_t *)&msg, sizeof(msg));
}

template <typename LedTask>
inline void handleIncomingMessagesFromHost(LedTask *ledTask)
{
  if (Serial.available() < sizeof(MAGIC_TOKEN_FW_TO_HOST) + sizeof(MessageHostToFw) + sizeof(uint16_t))
    return;

  uint32_t token;
  Serial.readBytes((uint8_t *)(&token), sizeof(token));
  if (token != MAGIC_TOKEN_HOST_TO_FW)
  {
    Serial.println("Received invalid message");
    return;
  }

  MessageHostToFw msgType;
  Serial.readBytes((uint8_t *)(&msgType), sizeof(msgType));

  uint16_t msgSize;
  Serial.readBytes((uint8_t *)(&msgSize), sizeof(msgSize));

  static constexpr int maxIncomingBufferSize = 1024;
  static uint8_t msgBuffer[maxIncomingBufferSize];
  if (msgSize < maxIncomingBufferSize)
  {
    Serial.readBytes(msgBuffer, msgSize);
    if (msgType == MessageHostToFw::LED_WHEEL_EFFECT_STATIC)
    {
      auto cfg = reinterpret_cast<EffectStaticConfig *>(msgBuffer);
      ledTask->startEffect(*cfg);
    }
    else if (msgType == MessageHostToFw::LED_WHEEL_EFFECT_ALEXA_SWIPE)
    {
      auto cfg = reinterpret_cast<EffectAlexaSwipeConfig *>(msgBuffer);
      ledTask->startEffect(*cfg);
    }
    else if (msgType == MessageHostToFw::LED_WHEEL_EFFECT_CIRCULAR)
    {
      auto cfg = reinterpret_cast<EffectCircularConfig *>(msgBuffer);
      ledTask->startEffect(*cfg);
    }
    else if (msgType == MessageHostToFw::LED_WHEEL_EFFECT_RANDOM_TWO_COLOR_INTERPOLATION)
    {
      auto cfg = reinterpret_cast<EffectRandomTwoColorInterpolationConfig *>(msgBuffer);
      ledTask->startEffect(*cfg);
    }
    else if (msgType == MessageHostToFw::LED_WHEEL_EFFECT_SWIPE_AND_CHANGE)
    {
      auto cfg = reinterpret_cast<EffectSwipeAndChangeConfig *>(msgBuffer);
      ledTask->startEffect(*cfg);
    }
    else
      Serial.println("Unknown message type");
  }
  else
    Serial.printf("Incoming message too large (or invalid) %d\n", msgSize);
}