// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.

#pragma once

#include <functional>
#include <map>
#include <span>
#include <string>

#include "wpi/net/raw_uv_ostream.hpp"

#define XRP_TAG_MOTOR 0x12
#define XRP_TAG_SERVO 0x13
#define XRP_TAG_DIO 0x14
#define XRP_TAG_ANALOG 0x15
#define XRP_TAG_GYRO 0x16
#define XRP_TAG_ACCEL 0x17
#define XRP_TAG_ENCODER 0x18

namespace wpi::util {
class json;
}  // namespace wpi::util

namespace wpilibxrp {

using WPILibUpdateFunc = std::function<void(const wpi::util::json&)>;

class XRP {
 public:
  XRP();

  void SetWPILibUpdateFunc(WPILibUpdateFunc func) {
    m_wpilib_update_func = func;
  }

  void HandleWPILibUpdate(const wpi::util::json& data);
  void HandleXRPUpdate(std::span<const uint8_t> packet);

  void SetupXRPSendBuffer(wpi::net::raw_uv_ostream& buf);

 private:
  // To XRP Methods
  void SetupSendHeader(wpi::net::raw_uv_ostream& buf);
  void SetupMotorTag(wpi::net::raw_uv_ostream& buf);
  void SetupServoTag(wpi::net::raw_uv_ostream& buf);
  void SetupDigitalOutTag(wpi::net::raw_uv_ostream& buf);

  // WPILib Sim Update Handlers
  void HandleDriverStationSimValueChanged(const wpi::util::json& data);
  void HandleMotorSimValueChanged(const wpi::util::json& data);
  void HandleServoSimValueChanged(const wpi::util::json& data);
  void HandleDIOSimValueChanged(const wpi::util::json& data);
  void HandleGyroSimValueChanged(const wpi::util::json& data);
  void HandleEncoderSimValueChanged(const wpi::util::json& data);

  // XRP Packet Update Handlers
  void ReadGyroTag(std::span<const uint8_t> packet);
  void ReadDIOTag(std::span<const uint8_t> packet);
  void ReadEncoderTag(std::span<const uint8_t> packet);
  void ReadAnalogTag(std::span<const uint8_t> packet);

  // Robot State
  std::map<uint8_t, bool> m_digital_outputs;
  std::map<uint8_t, float> m_motor_outputs;
  std::map<uint8_t, float> m_servo_outputs;

  // Might not need these
  std::map<uint8_t, bool> m_digital_inputs;
  std::map<uint8_t, float> m_analog_inputs;
  std::map<uint8_t, int32_t> m_encoder_inputs;

  // We need a map from XRP encoder channels (0=left, 1=right etc)
  // to WPILib device ID
  // Key: XRP encoder number, Value: WPILib channel
  // If no encoders are init-ed, this map is empty
  std::map<uint8_t, uint8_t> m_encoder_channel_map;

  uint16_t m_wpilib_bound_seq = 0;
  uint16_t m_xrp_bound_seq = 0;

  bool m_robot_enabled = false;

  std::string m_gyro_name;

  WPILibUpdateFunc m_wpilib_update_func;
};

}  // namespace wpilibxrp
