// 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.

#include "wpi/hardware/expansionhub/ExpansionHubMotor.hpp"

#include "wpi/hardware/expansionhub/ExpansionHubPositionConstants.hpp"
#include "wpi/hardware/expansionhub/ExpansionHubVelocityConstants.hpp"
#include "wpi/system/Errors.hpp"
#include "wpi/system/SystemServer.hpp"

static constexpr int kPercentageMode = 0;
static constexpr int kVoltageMode = 1;
static constexpr int kPositionMode = 2;
static constexpr int kVelocityMode = 3;
static constexpr int kFollowerMode = 4;

using namespace wpi;

ExpansionHubMotor::ExpansionHubMotor(int usbId, int channel)
    : m_hub{usbId},
      m_channel{channel},
      m_velocityConstants{usbId, channel},
      m_positionConstants{usbId, channel} {
  WPILIB_AssertMessage(channel >= 0 && channel < ExpansionHub::NumMotorPorts,
                       "ExHub Motor Channel {} out of range", channel);

  if (!m_hub.CheckAndReserveMotor(channel)) {
    throw WPILIB_MakeError(err::ResourceAlreadyAllocated, "Channel {}",
                           channel);
  }

  m_hub.ReportUsage(fmt::format("ExHubMotor[{}]", channel), "ExHubMotor");

  auto systemServer = SystemServer::GetSystemServer();

  wpi::nt::PubSubOptions options;
  options.sendAll = true;
  options.keepDuplicates = true;
  options.periodic = 0.005;

  m_encoderSubscriber = systemServer
                            .GetDoubleTopic(fmt::format(
                                "/rhsp/{}/motor{}/encoder", usbId, channel))
                            .Subscribe(0, options);
  m_encoderVelocitySubscriber =
      systemServer
          .GetDoubleTopic(
              fmt::format("/rhsp/{}/motor{}/encoderVelocity", usbId, channel))
          .Subscribe(0, options);
  m_currentSubscriber = systemServer
                            .GetDoubleTopic(fmt::format(
                                "/rhsp/{}/motor{}/current", usbId, channel))
                            .Subscribe(0, options);

  m_setpointPublisher = systemServer
                            .GetDoubleTopic(fmt::format(
                                "/rhsp/{}/motor{}/setpoint", usbId, channel))
                            .Publish(options);

  m_distancePerCountPublisher =
      systemServer
          .GetDoubleTopic(
              fmt::format("/rhsp/{}/motor{}/distancePerCount", usbId, channel))
          .Publish(options);

  m_floatOn0Publisher = systemServer
                            .GetBooleanTopic(fmt::format(
                                "/rhsp/{}/motor{}/floatOn0", usbId, channel))
                            .Publish(options);
  m_enabledPublisher = systemServer
                           .GetBooleanTopic(fmt::format(
                               "/rhsp/{}/motor{}/enabled", usbId, channel))
                           .Publish(options);

  m_modePublisher =
      systemServer
          .GetIntegerTopic(fmt::format("/rhsp/{}/motor{}/mode", usbId, channel))
          .Publish(options);

  m_reversedPublisher = systemServer
                            .GetBooleanTopic(fmt::format(
                                "/rhsp/{}/motor{}/reversed", usbId, channel))
                            .Publish(options);

  m_resetEncoderPublisher =
      systemServer
          .GetBooleanTopic(
              fmt::format("/rhsp/{}/motor{}/resetEncoder", usbId, channel))
          .Publish(options);
}

ExpansionHubMotor::~ExpansionHubMotor() noexcept {
  m_hub.UnreserveMotor(m_channel);
}

void ExpansionHubMotor::SetThrottle(double throttle) {
  SetEnabled(true);
  m_modePublisher.Set(kPercentageMode);
  m_setpointPublisher.Set(throttle);
}

void ExpansionHubMotor::SetVoltage(wpi::units::volt_t voltage) {
  SetEnabled(true);
  m_modePublisher.Set(kVoltageMode);
  m_setpointPublisher.Set(voltage.value());
}

void ExpansionHubMotor::SetPositionSetpoint(double setpoint) {
  SetEnabled(true);
  m_modePublisher.Set(kPositionMode);
  m_setpointPublisher.Set(setpoint);
}

void ExpansionHubMotor::SetVelocitySetpoint(double setpoint) {
  SetEnabled(true);
  m_modePublisher.Set(kVelocityMode);
  m_setpointPublisher.Set(setpoint);
}

void ExpansionHubMotor::SetEnabled(bool enabled) {
  m_enabledPublisher.Set(enabled);
}

void ExpansionHubMotor::SetFloatOn0(bool floatOn0) {
  m_floatOn0Publisher.Set(floatOn0);
}

wpi::units::ampere_t ExpansionHubMotor::GetCurrent() const {
  return wpi::units::ampere_t{m_currentSubscriber.Get(0)};
}

void ExpansionHubMotor::SetDistancePerCount(double perCount) {
  m_distancePerCountPublisher.Set(perCount);
}

double ExpansionHubMotor::GetEncoderVelocity() const {
  return m_encoderVelocitySubscriber.Get(0);
}

double ExpansionHubMotor::GetEncoderPosition() const {
  return m_encoderSubscriber.Get(0);
}

void ExpansionHubMotor::SetReversed(bool reversed) {
  m_reversedPublisher.Set(true);
}

void ExpansionHubMotor::ResetEncoder() {
  m_resetEncoderPublisher.Set(true);
}

ExpansionHubVelocityConstants& ExpansionHubMotor::GetVelocityConstants() {
  return m_velocityConstants;
}

ExpansionHubPositionConstants& ExpansionHubMotor::GetPositionConstants() {
  return m_positionConstants;
}

void ExpansionHubMotor::Follow(const ExpansionHubMotor& leader,
                               FollowDirection direction) {
  if (m_hub.GetUsbId() != leader.m_hub.GetUsbId()) {
    throw WPILIB_MakeError(err::InvalidParameter,
                           "Cannot follow motor on different hub");
  }
  if (m_channel == leader.m_channel) {
    throw WPILIB_MakeError(err::InvalidParameter, "Cannot follow self");
  }
  m_hub.AddFollower(leader.m_channel, m_channel);
  SetEnabled(true);
  m_modePublisher.Set(kFollowerMode);
  if (direction == FollowDirection::Opposed) {
    m_setpointPublisher.Set(leader.m_channel + 4);
  } else {
    m_setpointPublisher.Set(leader.m_channel);
  }
}

void ExpansionHubMotor::Unfollow() {
  m_hub.RemoveFollower(m_channel);
  SetEnabled(false);
  m_modePublisher.Set(kPercentageMode);
  m_setpointPublisher.Set(0);
}
