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

import wpilib
import wpilib.simulation
import wpimath

import constants


class Elevator:
    """Represents the elevator subsystem."""

    def __init__(self) -> None:
        # This gearbox represents a gearbox containing 4 Vex 775pro motors.
        self.elevatorGearbox = wpimath.DCMotor.vex775Pro(4)

        # Standard classes for controlling our elevator
        self.controller = wpimath.ProfiledPIDController(
            constants.kElevatorKp,
            constants.kElevatorKi,
            constants.kElevatorKd,
            wpimath.TrapezoidProfile.Constraints(2.45, 2.45),
        )
        self.feedforward = wpimath.ElevatorFeedforward(
            constants.kElevatorkS,
            constants.kElevatorkG,
            constants.kElevatorkV,
            constants.kElevatorkA,
        )
        self.encoder = wpilib.Encoder(
            constants.kEncoderAChannel, constants.kEncoderBChannel
        )
        self.motor = wpilib.PWMSparkMax(constants.kMotorPort)

        # Simulation classes help us simulate what's going on, including gravity.
        self.elevatorSim = wpilib.simulation.ElevatorSim(
            self.elevatorGearbox,
            constants.kElevatorGearing,
            constants.kCarriageMass,
            constants.kElevatorDrumRadius,
            constants.kMinElevatorHeight,
            constants.kMaxElevatorHeight,
            True,
            0,
            [0.01, 0.0],
        )
        self.encoderSim = wpilib.simulation.EncoderSim(self.encoder)
        self.motorSim = wpilib.simulation.PWMMotorControllerSim(self.motor)

        # Create a Mechanism2d visualization of the elevator
        self.mech2d = wpilib.Mechanism2d(20, 50)
        self.mech2dRoot = self.mech2d.getRoot("Elevator Root", 10, 0)
        self.elevatorMech2d = self.mech2dRoot.appendLigament(
            "Elevator", self.elevatorSim.getPosition(), 90
        )

        self.encoder.setDistancePerPulse(constants.kElevatorEncoderDistPerPulse)

        # Publish Mechanism2d to SmartDashboard
        # To view the Elevator visualization, select Network Tables -> SmartDashboard -> Elevator Sim
        wpilib.SmartDashboard.putData("Elevator Sim", self.mech2d)

    def simulationPeriodic(self) -> None:
        # In this method, we update our simulation of what our elevator is doing
        # First, we set our "inputs" (voltages)
        self.elevatorSim.setInputVoltage(
            self.motorSim.getThrottle() * wpilib.RobotController.getBatteryVoltage()
        )

        # Next, we update it. The standard loop time is 20ms.
        self.elevatorSim.update(0.020)

        # Finally, we set our simulated encoder's readings and simulated battery voltage
        self.encoderSim.setDistance(self.elevatorSim.getPosition())
        # SimBattery estimates loaded battery voltages
        wpilib.simulation.RoboRioSim.setVInVoltage(
            wpilib.simulation.BatterySim.calculate([self.elevatorSim.getCurrentDraw()])
        )

    def reachGoal(self, goal: float) -> None:
        """Run control loop to reach and maintain goal.

        :param goal: the position to maintain
        """

        self.controller.setGoal(goal)

        # With the setpoint value we run PID control like normal
        pidOutput = self.controller.calculate(self.encoder.getDistance())
        feedforwardOutput = self.feedforward.calculate(
            self.controller.getSetpoint().velocity
        )
        self.motor.setVoltage(pidOutput + feedforwardOutput)

    def stop(self) -> None:
        """Stop the control loop and motor output."""
        self.controller.setGoal(0.0)
        self.motor.setThrottle(0.0)

    def updateTelemetry(self) -> None:
        """Update telemetry, including the mechanism visualization."""
        # Update elevator visualization with position
        self.elevatorMech2d.setLength(self.encoder.getDistance())
