#include "effects/Circular.h"
#include "effects/Static.h"
#include "effects/AlexaSwipe.h"
#include "effects/ReverseSwipe.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,
  ROTARY_ENCODER = 1,
  TOUCH_BUTTON_PRESS = 2,
  TOUCH_BUTTON_RELEASE = 3,
  BUTTON_EVENT = 4,
};

enum class TouchButton : uint8_t
{
  LEFT_FOOT = 0,
  RIGHT_FOOT = 1,
  LEFT_EAR = 2,
  RIGHT_EAR = 3
};

#pragma pack(push, 1)
struct MsgRfidTokenRead
{
  uint8_t tagId[5];
};

struct MsgRotaryEncoder
{
  int32_t position;
  uint8_t direction;
};

struct MsgTouchButtonPress
{
  TouchButton button;
};

struct MsgTouchButtonRelease
{
  TouchButton button;
};

struct MsgButtonEvent
{
  uint8_t buttonNr;
  uint8_t eventType;
};

#pragma pack(pop)

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

template <>
struct ClassToMessageType<MsgRotaryEncoder>
{
  static constexpr auto msgType = MessageFwToHost::ROTARY_ENCODER;
};

template <>
struct ClassToMessageType<MsgTouchButtonPress>
{
  static constexpr auto msgType = MessageFwToHost::TOUCH_BUTTON_PRESS;
};

template <>
struct ClassToMessageType<MsgTouchButtonRelease>
{
  static constexpr auto msgType = MessageFwToHost::TOUCH_BUTTON_RELEASE;
};

template <>
struct ClassToMessageType<MsgButtonEvent>
{
  static constexpr auto msgType = MessageFwToHost::BUTTON_EVENT;
};

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

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,
  LED_WHEEL_EFFECT_REVERSE_SWIPE = 5,
  MOUSE_LED_EFFECT_STATIC = 6,
  MOUSE_LED_EFFECT_CIRCULAR = 7,
  MOUSE_LED_EFFECT_RANDOM_TWO_COLOR_INTERPOLATION = 8,
  MOUSE_LED_EFFECT_SWIPE_AND_CHANGE = 9,
  MOUSE_LED_EFFECT_REVERSE_SWIPE = 10,

  PREV_BUTTON_LED = 20,
  NEXT_BUTTON_LED = 21,
};

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 LedTask1, typename LedTask2>
inline void handleIncomingMessagesFromHost(LedTask1 *ledTaskCircle, LedTask2 *ledTaskMouse, uint8_t ledChannelLeft, uint8_t ledChannelRight)
{
  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);
      ledTaskCircle->startEffect(*cfg);
    }
    else if (msgType == MessageHostToFw::LED_WHEEL_EFFECT_ALEXA_SWIPE)
    {
      auto cfg = reinterpret_cast<EffectAlexaSwipeConfig *>(msgBuffer);
      ledTaskCircle->startEffect(*cfg);
    }
    else if (msgType == MessageHostToFw::LED_WHEEL_EFFECT_CIRCULAR)
    {
      auto cfg = reinterpret_cast<EffectCircularConfig *>(msgBuffer);
      ledTaskCircle->startEffect(*cfg);
    }
    else if (msgType == MessageHostToFw::LED_WHEEL_EFFECT_RANDOM_TWO_COLOR_INTERPOLATION)
    {
      auto cfg = reinterpret_cast<EffectRandomTwoColorInterpolationConfig *>(msgBuffer);
      ledTaskCircle->startEffect(*cfg);
    }
    else if (msgType == MessageHostToFw::LED_WHEEL_EFFECT_SWIPE_AND_CHANGE)
    {
      auto cfg = reinterpret_cast<EffectSwipeAndChangeConfig *>(msgBuffer);
      ledTaskCircle->startEffect(*cfg);
    }
    else if (msgType == MessageHostToFw::LED_WHEEL_EFFECT_REVERSE_SWIPE)
    {
      auto cfg = reinterpret_cast<EffectReverseSwipeConfig *>(msgBuffer);
      ledTaskCircle->startEffect(*cfg);
    }
    //
    else if (msgType == MessageHostToFw::MOUSE_LED_EFFECT_STATIC)
    {
      auto cfg = reinterpret_cast<EffectStaticConfig *>(msgBuffer);
      ledTaskMouse->startEffect(*cfg);
    }
    else if (msgType == MessageHostToFw::MOUSE_LED_EFFECT_CIRCULAR)
    {
      auto cfg = reinterpret_cast<EffectCircularConfig *>(msgBuffer);
      ledTaskMouse->startEffect(*cfg);
    }
    else if (msgType == MessageHostToFw::MOUSE_LED_EFFECT_RANDOM_TWO_COLOR_INTERPOLATION)
    {
      auto cfg = reinterpret_cast<EffectRandomTwoColorInterpolationConfig *>(msgBuffer);
      ledTaskMouse->startEffect(*cfg);
    }
    else if (msgType == MessageHostToFw::MOUSE_LED_EFFECT_SWIPE_AND_CHANGE)
    {
      auto cfg = reinterpret_cast<EffectSwipeAndChangeConfig *>(msgBuffer);
      ledTaskMouse->startEffect(*cfg);
    }
    else if (msgType == MessageHostToFw::MOUSE_LED_EFFECT_REVERSE_SWIPE)
    {
      auto cfg = reinterpret_cast<EffectReverseSwipeConfig *>(msgBuffer);
      ledTaskMouse->startEffect(*cfg);
    }
    else if (msgType == MessageHostToFw::PREV_BUTTON_LED)
    {
      float *val = reinterpret_cast<float *>(msgBuffer);
      ledcWrite(ledChannelLeft, uint32_t(255 * (*val)));
    }
    else if (msgType == MessageHostToFw::NEXT_BUTTON_LED)
    {
      float *val = reinterpret_cast<float *>(msgBuffer);
      ledcWrite(ledChannelRight, uint32_t(255 * (*val)));
    }
    else
      Serial.println("Unknown message type");
  }
  else
    Serial.printf("Incoming message too large (or invalid) %d\n", msgSize);
}