package com.team254.lib.trajectory;

import edu.wpi.first.wpilibj.Timer;

/**
 * PID + Feedforward controller for following a Trajectory.
 *
 * @author Jared341
 */
public class TrajectoryFollower {
    public static class TrajectoryConfig {
        public double dt;
        public double max_acc;
        public double max_vel;

        @Override
        public String toString() {
            return "dt: " + dt + ", max_acc: " + max_acc + ", max_vel: "
                    + max_vel;
        }
    }

    public static class TrajectorySetpoint {
        public double pos;
        public double vel;
        public double acc;

        @Override
        public String toString() {
            return "pos: " + pos + ", vel: " + vel + ", acc: " + acc;
        }
    }

    private double kp_;
    private double ki_;
    private double kd_;
    private double kv_;
    private double ka_;
    private double last_error_;
    private double error_sum_;
    private boolean reset_ = true;
    private double last_timestamp_;
    private TrajectorySetpoint next_state_ = new TrajectorySetpoint();

    private TrajectoryConfig config_ = new TrajectoryConfig();
    private double goal_position_;
    private TrajectorySetpoint setpoint_ = new TrajectorySetpoint();

    public TrajectoryFollower() {
    }

    public void configure(double kp, double ki, double kd, double kv,
                          double ka, TrajectoryConfig config) {
        kp_ = kp;
        ki_ = ki;
        kd_ = kd;
        kv_ = kv;
        ka_ = ka;
        config_ = config;
    }

    public void setGoal(TrajectorySetpoint current_state, double goal_position) {
        goal_position_ = goal_position;
        setpoint_ = current_state;
        reset_ = true;
        error_sum_ = 0.0;
    }

    public double getGoal() {
        return goal_position_;
    }

    public TrajectoryConfig getConfig() {
        return config_;
    }

    public void setConfig(TrajectoryConfig config) {
        config_ = config;
    }

    public double calculate(double position, double velocity) {
        double dt = config_.dt;
        if (!reset_) {
            double now = Timer.getFPGATimestamp();
            dt = now - last_timestamp_;
            last_timestamp_ = now;
        } else {
            last_timestamp_ = Timer.getFPGATimestamp();
        }

        // DEBUGGING
        if (dt >= config_.dt * 1.75) {
            System.err.println("!! Bad dt (" + dt+ ") at (" + Timer.getFPGATimestamp() +") in " + this.toString());
            StackTraceElement[] st = Thread.currentThread().getStackTrace();
            for (int i = 0; i < 5 && i< st.length; i++) {
                StackTraceElement ste = st[i];
                System.err.println(ste);
            }
        }

        if (isFinishedTrajectory()) {
            setpoint_.pos = goal_position_;
            setpoint_.vel = 0;
            setpoint_.acc = 0;
        } else {
            // Compute the new commanded position, velocity, and acceleration.
            double distance_to_go = goal_position_ - setpoint_.pos;
            double cur_vel = setpoint_.vel;
            double cur_vel2 = cur_vel * cur_vel;
            boolean inverted = false;
            if (distance_to_go < 0) {
                inverted = true;
                distance_to_go *= -1;
                cur_vel *= -1;
            }
            // Compute discriminants of the minimum and maximum reachable
            // velocities over the remaining distance.
            double max_reachable_velocity_disc = cur_vel2 / 2.0
                    + config_.max_acc * distance_to_go;
            double min_reachable_velocity_disc = cur_vel2 / 2.0
                    - config_.max_acc * distance_to_go;
            double cruise_vel = cur_vel;
            if (min_reachable_velocity_disc < 0 || cruise_vel < 0) {
                cruise_vel = Math.min(config_.max_vel,
                        Math.sqrt(max_reachable_velocity_disc));
            }
            double t_start = (cruise_vel - cur_vel) / config_.max_acc; // Accelerate
            // to
            // cruise_vel
            double x_start = cur_vel * t_start + .5 * config_.max_acc * t_start
                    * t_start;
            double t_end = Math.abs(cruise_vel / config_.max_acc); // Decelerate
            // to zero
            // vel.
            double x_end = cruise_vel * t_end - .5 * config_.max_acc * t_end
                    * t_end;
            double x_cruise = Math.max(0, distance_to_go - x_start - x_end);
            double t_cruise = Math.abs(x_cruise / cruise_vel);
            // Figure out where we should be one dt along this trajectory.
            if (t_start >= dt) {
                next_state_.pos = cur_vel * dt + .5 * config_.max_acc
                        * dt * dt;
                next_state_.vel = cur_vel + config_.max_acc * dt;
                next_state_.acc = config_.max_acc;
            } else if (t_start + t_cruise >= dt) {
                next_state_.pos = x_start + cruise_vel * (dt - t_start);
                next_state_.vel = cruise_vel;
                next_state_.acc = 0;
            } else if (t_start + t_cruise + t_end >= dt) {
                double delta_t = dt - t_start - t_cruise;
                next_state_.pos = x_start + x_cruise + cruise_vel * delta_t - .5
                        * config_.max_acc * delta_t * delta_t;
                next_state_.vel = cruise_vel - config_.max_acc * delta_t;
                next_state_.acc = -config_.max_acc;
            } else {
                // Trajectory ends this cycle.
                next_state_.pos = distance_to_go;
                next_state_.vel = 0;
                next_state_.acc = 0;
            }
            if (inverted) {
                next_state_.pos *= -1;
                next_state_.vel *= -1;
                next_state_.acc *= -1;
            }
            setpoint_.pos += next_state_.pos;
            setpoint_.vel = next_state_.vel;
            setpoint_.acc = next_state_.acc;

        }
        double error = setpoint_.pos - position;
        if (reset_) {
            // Prevent jump in derivative term when we have been reset.
            reset_ = false;
            last_error_ = error;
            error_sum_ = 0;
        }
        double output = kp_ * error + kd_
                * ((error - last_error_) / dt - setpoint_.vel)
                + (kv_ * setpoint_.vel + ka_ * setpoint_.acc);
        if (output < 1.0 && output > -1.0) {
            // Only integrate error if the output isn't already saturated.
            error_sum_ += error * dt;
        }
        output += ki_ * error_sum_;

        last_error_ = error;
        return output;
    }

    public boolean isFinishedTrajectory() {
        return Math.abs(setpoint_.pos - goal_position_) < 1E-3
                && Math.abs(setpoint_.vel) < 1E-2;
    }

    public TrajectorySetpoint getCurrentSetpoint() {
        return setpoint_;
    }
}

@@ -2330,432 +2330,8 @@
  %7D%0A%0A
-        // DEBUGGING%0A        if (dt %3E= config_.dt * 1.75) %7B%0A            System.err.println(%22!! Bad dt (%22 + dt+ %22) at (%22 + Timer.getFPGATimestamp() +%22) in %22 + this.toString());%0A            StackTraceElement%5B%5D st = Thread.currentThread().getStackTrace();%0A            for (int i = 0; i %3C 5 && i%3C st.length; i++) %7B%0A                StackTraceElement ste = st%5Bi%5D;%0A                System.err.println(ste);%0A            %7D%0A        %7D%0A%0A
     

Remove debugging timer