#include "Copter.h"

Mode::AutoYaw Mode::auto_yaw;

// roi_yaw - returns heading towards location held in roi
float Mode::AutoYaw::roi_yaw() const
{
    return get_bearing_cd(copter.inertial_nav.get_position_xy_cm(), roi.xy());
}

// returns a yaw in degrees, direction of vehicle travel:
float Mode::AutoYaw::look_ahead_yaw()
{
    const Vector3f& vel = copter.inertial_nav.get_velocity_neu_cms();
    const float speed_sq = vel.xy().length_squared();
    // Commanded Yaw to automatically look ahead.
    if (copter.position_ok() && (speed_sq > (YAW_LOOK_AHEAD_MIN_SPEED * YAW_LOOK_AHEAD_MIN_SPEED))) {
        _look_ahead_yaw = degrees(atan2f(vel.y,vel.x));
    }
    return _look_ahead_yaw;
}

void Mode::AutoYaw::set_mode_to_default(bool rtl)
{
    set_mode(default_mode(rtl));
}

// default_mode - returns auto_yaw.mode() based on WP_YAW_BEHAVIOR parameter
// set rtl parameter to true if this is during an RTL
Mode::AutoYaw::Mode Mode::AutoYaw::default_mode(bool rtl) const
{
    switch (copter.g.wp_yaw_behavior) {

    case WP_YAW_BEHAVIOR_NONE:
        return Mode::HOLD;

    case WP_YAW_BEHAVIOR_LOOK_AT_NEXT_WP_EXCEPT_RTL:
        if (rtl) {
            return Mode::HOLD;
        } else {
            return Mode::LOOK_AT_NEXT_WP;
        }

    case WP_YAW_BEHAVIOR_LOOK_AHEAD:
        return Mode::LOOK_AHEAD;

    case WP_YAW_BEHAVIOR_LOOK_AT_NEXT_WP:
    default:
        return Mode::LOOK_AT_NEXT_WP;
    }
}

// set_mode - sets the yaw mode for auto
void Mode::AutoYaw::set_mode(Mode yaw_mode)
{
    // return immediately if no change
    if (_mode == yaw_mode) {
        return;
    }
    _last_mode = _mode;
    _mode = yaw_mode;

    // perform initialisation
    switch (_mode) {

    case Mode::HOLD:
        break;

    case Mode::LOOK_AT_NEXT_WP:
        // wpnav will initialise heading when wpnav's set_destination method is called
        break;

    case Mode::ROI:
        // look ahead until we know otherwise
        break;

    case Mode::FIXED:
        // keep heading pointing in the direction held in fixed_yaw
        // caller should set the fixed_yaw
        break;

    case Mode::LOOK_AHEAD:
        // Commanded Yaw to automatically look ahead.
        _look_ahead_yaw = copter.ahrs.yaw_sensor * 0.01;  // cdeg -> deg
        break;

    case Mode::RESETTOARMEDYAW:
        // initial_armed_bearing will be set during arming so no init required
        break;

    case Mode::ANGLE_RATE:
        break;

    case Mode::RATE:
        // initialise target yaw rate to zero
        _yaw_rate_cds = 0.0;
        break;

    case Mode::CIRCLE:
    case Mode::PILOT_RATE:
    case Mode::WEATHERVANE:
        // no initialisation required
        break;
    }
}

// set_fixed_yaw - sets the yaw look at heading for auto mode
void Mode::AutoYaw::set_fixed_yaw(float angle_deg, float turn_rate_ds, int8_t direction, bool relative_angle)
{
    _last_update_ms = millis();

    // calculate final angle as relative to vehicle heading or absolute
    if (relative_angle) {
        if (_mode == Mode::HOLD) {
            _yaw_angle_cd = copter.ahrs.yaw_sensor;
        }
        _fixed_yaw_offset_cd = angle_deg * 100.0 * (direction >= 0 ? 1.0 : -1.0);
    } else {
        // absolute angle
        _fixed_yaw_offset_cd = wrap_180_cd(angle_deg * 100.0 - _yaw_angle_cd);
        if (direction < 0 && is_positive(_fixed_yaw_offset_cd)) {
            _fixed_yaw_offset_cd -= 36000.0;
        } else if (direction > 0 && is_negative(_fixed_yaw_offset_cd)) {
            _fixed_yaw_offset_cd += 36000.0;
        }
    }

    // get turn speed
    if (!is_positive(turn_rate_ds)) {
        // default to default slew rate
        _fixed_yaw_slewrate_cds = copter.attitude_control->get_slew_yaw_max_degs() * 100.0;
    } else {
        _fixed_yaw_slewrate_cds = MIN(copter.attitude_control->get_slew_yaw_max_degs(), turn_rate_ds) * 100.0;
    }

    // set yaw mode
    set_mode(Mode::FIXED);
}

// set_fixed_yaw - sets the yaw look at heading for auto mode
void Mode::AutoYaw::set_yaw_angle_rate(float yaw_angle_d, float yaw_rate_ds)
{
    _last_update_ms = millis();

    _yaw_angle_cd = yaw_angle_d * 100.0;
    _yaw_rate_cds = yaw_rate_ds * 100.0;

    // set yaw mode
    set_mode(Mode::ANGLE_RATE);
}

// set_roi - sets the yaw to look at roi for auto mode
void Mode::AutoYaw::set_roi(const Location &roi_location)
{
    // if location is zero lat, lon and altitude turn off ROI
    if (roi_location.alt == 0 && roi_location.lat == 0 && roi_location.lng == 0) {
        // set auto yaw mode back to default assuming the active command is a waypoint command.  A more sophisticated method is required to ensure we return to the proper yaw control for the active command
        auto_yaw.set_mode_to_default(false);
#if HAL_MOUNT_ENABLED
        // switch off the camera tracking if enabled
        copter.camera_mount.clear_roi_target();
#endif  // HAL_MOUNT_ENABLED
    } else {
#if HAL_MOUNT_ENABLED
        // check if mount type requires us to rotate the quad
        if (!copter.camera_mount.has_pan_control()) {
            if (roi_location.get_vector_from_origin_NEU(roi)) {
                auto_yaw.set_mode(Mode::ROI);
            }
        }
        // send the command to the camera mount
        copter.camera_mount.set_roi_target(roi_location);

        // TO-DO: expand handling of the do_nav_roi to support all modes of the MAVLink.  Currently we only handle mode 4 (see below)
        //      0: do nothing
        //      1: point at next waypoint
        //      2: point at a waypoint taken from WP# parameter (2nd parameter?)
        //      3: point at a location given by alt, lon, lat parameters
        //      4: point at a target given a target id (can't be implemented)
#else
        // if we have no camera mount aim the quad at the location
        if (roi_location.get_vector_from_origin_NEU(roi)) {
            auto_yaw.set_mode(Mode::ROI);
        }
#endif  // HAL_MOUNT_ENABLED
    }
}

// set auto yaw rate in centi-degrees per second
void Mode::AutoYaw::set_rate(float turn_rate_cds)
{
    set_mode(Mode::RATE);
    _yaw_rate_cds = turn_rate_cds;
}

// return true if fixed yaw target has been reached
bool Mode::AutoYaw::reached_fixed_yaw_target()
{
    if (mode() != Mode::FIXED) {
        // should not happen, not in the right mode
        return true;
    }

    if (!is_zero(_fixed_yaw_offset_cd)) {
        // still slewing yaw target
        return false;
    }

    // Within 2 deg of target
    return (fabsf(wrap_180_cd(copter.ahrs.yaw_sensor-_yaw_angle_cd)) <= 200);
}

// yaw_cd - returns target heading depending upon auto_yaw.mode()
float Mode::AutoYaw::yaw_cd()
{
    switch (_mode) {

    case Mode::ROI:
        // point towards a location held in roi
        _yaw_angle_cd = roi_yaw();
        break;

    case Mode::FIXED: {
        // keep heading pointing in the direction held in fixed_yaw
        // with no pilot input allowed
        const uint32_t now_ms = millis();
        float dt = (now_ms - _last_update_ms) * 0.001;
        _last_update_ms = now_ms;
        float yaw_angle_step = constrain_float(_fixed_yaw_offset_cd, - dt * _fixed_yaw_slewrate_cds, dt * _fixed_yaw_slewrate_cds);
        _fixed_yaw_offset_cd -= yaw_angle_step;
        _yaw_angle_cd += yaw_angle_step;
        break;
    }

    case Mode::LOOK_AHEAD:
        // Commanded Yaw to automatically look ahead.
        _yaw_angle_cd = look_ahead_yaw() * 100.0;
        break;

    case Mode::RESETTOARMEDYAW:
        // changes yaw to be same as when quad was armed
        _yaw_angle_cd = copter.initial_armed_bearing;
        break;

    case Mode::CIRCLE:
#if MODE_CIRCLE_ENABLED
        if (copter.circle_nav->is_active()) {
            _yaw_angle_cd = copter.circle_nav->get_yaw();
        }
#endif
        break;

    case Mode::ANGLE_RATE:{
        const uint32_t now_ms = millis();
        float dt = (now_ms - _last_update_ms) * 0.001;
        _last_update_ms = now_ms;
        _yaw_angle_cd += _yaw_rate_cds * dt;
        break;
    }

    case Mode::RATE:
    case Mode::WEATHERVANE:
    case Mode::PILOT_RATE:
        _yaw_angle_cd = copter.attitude_control->get_att_target_euler_cd().z;
        break;

    case Mode::LOOK_AT_NEXT_WP:
    default:
        // point towards next waypoint.
        // we don't use wp_bearing because we don't want the copter to turn too much during flight
        _yaw_angle_cd = copter.pos_control->get_yaw_cd();
    break;
    }
    
    return _yaw_angle_cd;
}

// returns yaw rate normally set by SET_POSITION_TARGET mavlink
// messages (positive is clockwise, negative is counter clockwise)
float Mode::AutoYaw::rate_cds()
{
    switch (_mode) {

    case Mode::HOLD:
    case Mode::ROI:
    case Mode::FIXED:
    case Mode::LOOK_AHEAD:
    case Mode::RESETTOARMEDYAW:
    case Mode::CIRCLE:
        _yaw_rate_cds = 0.0f;
        break;

    case Mode::LOOK_AT_NEXT_WP:
        _yaw_rate_cds = copter.pos_control->get_yaw_rate_cds();
        break;

    case Mode::PILOT_RATE:
        _yaw_rate_cds = _pilot_yaw_rate_cds;
        break;

    case Mode::ANGLE_RATE:
    case Mode::RATE:
    case Mode::WEATHERVANE:
        break;
    }

    // return zero turn rate (this should never happen)
    return _yaw_rate_cds;
}

AC_AttitudeControl::HeadingCommand Mode::AutoYaw::get_heading()
{
    // process pilot's yaw input
    _pilot_yaw_rate_cds = 0.0;
    if (!copter.failsafe.radio && copter.flightmode->use_pilot_yaw()) {
        // get pilot's desired yaw rate
        _pilot_yaw_rate_cds = copter.flightmode->get_pilot_desired_yaw_rate(copter.channel_yaw->norm_input_dz());
        if (!is_zero(_pilot_yaw_rate_cds)) {
            auto_yaw.set_mode(AutoYaw::Mode::PILOT_RATE);
        }
    } else if (auto_yaw.mode() == AutoYaw::Mode::PILOT_RATE) {
        // RC failsafe, or disabled make sure not in pilot control
        auto_yaw.set_mode(AutoYaw::Mode::HOLD);
    }

#if WEATHERVANE_ENABLED == ENABLED
    update_weathervane(_pilot_yaw_rate_cds);
#endif

    AC_AttitudeControl::HeadingCommand heading;
    heading.yaw_angle_cd = auto_yaw.yaw_cd();
    heading.yaw_rate_cds = auto_yaw.rate_cds();

    switch (auto_yaw.mode()) {
        case Mode::HOLD:
        case Mode::RATE:
        case Mode::PILOT_RATE:
        case Mode::WEATHERVANE:
            heading.heading_mode = AC_AttitudeControl::HeadingMode::Rate_Only;
            break;
        case Mode::LOOK_AT_NEXT_WP:
        case Mode::ROI:
        case Mode::FIXED:
        case Mode::LOOK_AHEAD:
        case Mode::RESETTOARMEDYAW:
        case Mode::ANGLE_RATE:
        case Mode::CIRCLE:
            heading.heading_mode = AC_AttitudeControl::HeadingMode::Angle_And_Rate;
            break;
    }

    return heading;
}

// handle the interface to the weathervane library
// pilot_yaw can be an angle or a rate or rcin from yaw channel. It just needs to represent a pilot's request to yaw the vehicle to enable pilot overrides.
#if WEATHERVANE_ENABLED == ENABLED
void Mode::AutoYaw::update_weathervane(const int16_t pilot_yaw_cds)
{
    if (!copter.flightmode->allows_weathervaning()) {
        return;
    }

    float yaw_rate_cds;
    if (copter.g2.weathervane.get_yaw_out(yaw_rate_cds, pilot_yaw_cds, copter.flightmode->get_alt_above_ground_cm()*0.01,
                                                                       copter.pos_control->get_roll_cd()-copter.attitude_control->get_roll_trim_cd(),
                                                                       copter.pos_control->get_pitch_cd(),
                                                                       copter.flightmode->is_taking_off(),
                                                                       copter.flightmode->is_landing())) {
        set_mode(Mode::WEATHERVANE);
        _yaw_rate_cds = yaw_rate_cds;
        return;
    }

    // if the weathervane controller has previously been activated we need to ensure we return control back to what was previously set
    if (mode() == Mode::WEATHERVANE) {
        _yaw_rate_cds = 0.0;
        if (_last_mode == Mode::HOLD) {
            set_mode_to_default(false);
        } else {
            set_mode(_last_mode);
        }
    }
}
#endif // WEATHERVANE_ENABLED == ENABLED