#include <AP_HAL/AP_HAL.h>
#include <AP_AHRS/AP_AHRS.h>
#include "SoloGimbal.h"

#if HAL_SOLO_GIMBAL_ENABLED

#include <stdio.h>
#include <GCS_MAVLink/GCS.h>
#include <AP_Logger/AP_Logger.h>

extern const AP_HAL::HAL& hal;

bool SoloGimbal::present()
{
    if (_state != GIMBAL_STATE_NOT_PRESENT && AP_HAL::millis()-_last_report_msg_ms > 3000) {
        // gimbal went away
        _state = GIMBAL_STATE_NOT_PRESENT;
        return false;
    }

    return _state != GIMBAL_STATE_NOT_PRESENT;
}

bool SoloGimbal::aligned()
{
    return present() && _state == GIMBAL_STATE_PRESENT_RUNNING;
}

gimbal_mode_t SoloGimbal::get_mode()
{
    const auto &_ahrs = AP::ahrs();

    if ((_gimbalParams.initialized() && is_zero(_gimbalParams.get_K_rate())) || (_ahrs.get_rotation_body_to_ned().c.z < 0 && !(_lockedToBody || _calibrator.running()))) {
        return GIMBAL_MODE_IDLE;
    } else if (!_ekf.getStatus()) {
        return GIMBAL_MODE_POS_HOLD;
    } else if (_calibrator.running() || _lockedToBody) {
        return GIMBAL_MODE_POS_HOLD_FF;
    } else {
        return GIMBAL_MODE_STABILIZE;
    }
}

void SoloGimbal::receive_feedback(mavlink_channel_t chan, const mavlink_message_t &msg)
{
    mavlink_gimbal_report_t report_msg;
    mavlink_msg_gimbal_report_decode(&msg, &report_msg);
    uint32_t tnow_ms = AP_HAL::millis();
    _last_report_msg_ms = tnow_ms;

    _gimbalParams.set_channel(chan);

    if (report_msg.target_system != 1) {
        _state = GIMBAL_STATE_NOT_PRESENT;
    } else {
        GCS_MAVLINK::set_channel_private(chan);
    }

    switch(_state) {
        case GIMBAL_STATE_NOT_PRESENT:
            // gimbal was just connected or we just rebooted, transition to PRESENT_INITIALIZING
            _gimbalParams.reset();
            _gimbalParams.set_param(GMB_PARAM_GMB_SYSID, 1);
            _state = GIMBAL_STATE_PRESENT_INITIALIZING;
            break;

        case GIMBAL_STATE_PRESENT_INITIALIZING:
            _gimbalParams.update();
            if (_gimbalParams.initialized()) {
                // parameters done initializing, finalize initialization and transition to aligning
                extract_feedback(report_msg);
                _ang_vel_mag_filt = 20;
                _filtered_joint_angles = _measurement.joint_angles;
                _vehicle_to_gimbal_quat_filt.from_vector312(_filtered_joint_angles.x,_filtered_joint_angles.y,_filtered_joint_angles.z);
                _ekf.reset();
                _state = GIMBAL_STATE_PRESENT_ALIGNING;
            }
            break;

        case GIMBAL_STATE_PRESENT_ALIGNING:
            _gimbalParams.update();
            extract_feedback(report_msg);
            update_estimators();
            if (_ekf.getStatus()) {
                // EKF done aligning, transition to running
                _state = GIMBAL_STATE_PRESENT_RUNNING;
            }
            break;

        case GIMBAL_STATE_PRESENT_RUNNING:
            _gimbalParams.update();
            extract_feedback(report_msg);
            update_estimators();
            break;
    }

    send_controls(chan);
}

void SoloGimbal::send_controls(mavlink_channel_t chan)
{
    if (_state == GIMBAL_STATE_PRESENT_RUNNING) {
        // get the gimbal quaternion estimate
        Quaternion quatEst;
        _ekf.getQuat(quatEst);

        // run rate controller
        _ang_vel_dem_rads.zero();
        switch(get_mode()) {
            case GIMBAL_MODE_POS_HOLD_FF: {
                _ang_vel_dem_rads += get_ang_vel_dem_body_lock();
                _ang_vel_dem_rads += get_ang_vel_dem_gyro_bias();
                float _ang_vel_dem_radsLen = _ang_vel_dem_rads.length();
                if (_ang_vel_dem_radsLen > radians(400)) {
                    _ang_vel_dem_rads *= radians(400)/_ang_vel_dem_radsLen;
                }
                if (HAVE_PAYLOAD_SPACE(chan, GIMBAL_CONTROL)) {
                    mavlink_msg_gimbal_control_send(chan, mavlink_system.sysid, _compid,
                                                    _ang_vel_dem_rads.x, _ang_vel_dem_rads.y, _ang_vel_dem_rads.z);
                }
                break;
            }
            case GIMBAL_MODE_STABILIZE: {
                _ang_vel_dem_rads += get_ang_vel_dem_yaw(quatEst);
                _ang_vel_dem_rads += get_ang_vel_dem_roll_tilt(quatEst);
                _ang_vel_dem_rads += get_ang_vel_dem_feedforward(quatEst);
                _ang_vel_dem_rads += get_ang_vel_dem_gyro_bias();
                float ang_vel_dem_norm = _ang_vel_dem_rads.length();
                if (ang_vel_dem_norm > radians(400)) {
                    _ang_vel_dem_rads *= radians(400)/ang_vel_dem_norm;
                }
                if (HAVE_PAYLOAD_SPACE(chan, GIMBAL_CONTROL)) {
                    mavlink_msg_gimbal_control_send(chan, mavlink_system.sysid, _compid,
                                                    _ang_vel_dem_rads.x, _ang_vel_dem_rads.y, _ang_vel_dem_rads.z);
                }
                break;
            }
            default:
            case GIMBAL_MODE_IDLE:
            case GIMBAL_MODE_POS_HOLD:
                break;
        }
    }

    // set GMB_POS_HOLD
    if (get_mode() == GIMBAL_MODE_POS_HOLD) {
        _gimbalParams.set_param(GMB_PARAM_GMB_POS_HOLD, 1);
    } else {
        _gimbalParams.set_param(GMB_PARAM_GMB_POS_HOLD, 0);
    }

    // set GMB_MAX_TORQUE
    float max_torque;
    _gimbalParams.get_param(GMB_PARAM_GMB_MAX_TORQUE, max_torque, 0);
    if (!is_equal(max_torque,_max_torque) && !is_zero(max_torque)) {
        _max_torque = max_torque;
    }

    if (!hal.util->get_soft_armed() || joints_near_limits()) {
        _gimbalParams.set_param(GMB_PARAM_GMB_MAX_TORQUE, _max_torque);
    } else {
        _gimbalParams.set_param(GMB_PARAM_GMB_MAX_TORQUE, 0);
    }
}

void SoloGimbal::extract_feedback(const mavlink_gimbal_report_t& report_msg)
{
    _measurement.delta_time = report_msg.delta_time;
    _measurement.delta_angles.x = report_msg.delta_angle_x;
    _measurement.delta_angles.y = report_msg.delta_angle_y;
    _measurement.delta_angles.z = report_msg.delta_angle_z;
    _measurement.delta_velocity.x = report_msg.delta_velocity_x,
    _measurement.delta_velocity.y = report_msg.delta_velocity_y;
    _measurement.delta_velocity.z = report_msg.delta_velocity_z;
    _measurement.joint_angles.x = report_msg.joint_roll;
    _measurement.joint_angles.y = report_msg.joint_el;
    _measurement.joint_angles.z = report_msg.joint_az;

    if (_calibrator.get_status() == ACCEL_CAL_COLLECTING_SAMPLE) {
        _calibrator.new_sample(_measurement.delta_velocity,_measurement.delta_time);
    }

    _measurement.delta_angles -= _gimbalParams.get_gyro_bias() * _measurement.delta_time;
    _measurement.joint_angles -= _gimbalParams.get_joint_bias();
    _measurement.delta_velocity -= _gimbalParams.get_accel_bias() * _measurement.delta_time;
    Vector3f accel_gain = _gimbalParams.get_accel_gain();
    _measurement.delta_velocity.x *= (is_zero(accel_gain.x) ? 1.0f : accel_gain.x);
    _measurement.delta_velocity.y *= (is_zero(accel_gain.y) ? 1.0f : accel_gain.y);
    _measurement.delta_velocity.z *= (is_zero(accel_gain.z) ? 1.0f : accel_gain.z);

    // update _ang_vel_mag_filt, used for accel sample readiness
    Vector3f ang_vel = _measurement.delta_angles / _measurement.delta_time;
    Vector3f ekf_gyro_bias;
    _ekf.getGyroBias(ekf_gyro_bias);
    ang_vel -= ekf_gyro_bias;
    float alpha = constrain_float(_measurement.delta_time/(_measurement.delta_time+0.5f),0.0f,1.0f);
    _ang_vel_mag_filt += (ang_vel.length()-_ang_vel_mag_filt)*alpha;
    _ang_vel_mag_filt = MIN(_ang_vel_mag_filt,20.0f);

    // get complementary filter inputs
    _vehicle_to_gimbal_quat.from_vector312(_measurement.joint_angles.x,_measurement.joint_angles.y,_measurement.joint_angles.z);

    // update log deltas
    _log_dt += _measurement.delta_time;
    _log_del_ang += _measurement.delta_angles;
    _log_del_vel += _measurement.delta_velocity;
}

void SoloGimbal::update_estimators()
{
    if (_state == GIMBAL_STATE_NOT_PRESENT || _state == GIMBAL_STATE_PRESENT_INITIALIZING) {
        return;
    }

    // Run the gimbal attitude and gyro bias estimator
    _ekf.RunEKF(_measurement.delta_time, _measurement.delta_angles, _measurement.delta_velocity, _measurement.joint_angles);
    update_joint_angle_est();
}

void SoloGimbal::readVehicleDeltaAngle(uint8_t ins_index, Vector3f &dAng) {
    const AP_InertialSensor &ins = AP::ins();

    if (ins_index < ins.get_gyro_count()) {
        float dAng_dt;
        if (!ins.get_delta_angle(ins_index,dAng, dAng_dt)) {
            dAng = ins.get_gyro(ins_index) / ins.get_loop_rate_hz();
        }
    }
}

void SoloGimbal::update_fast() {
    const AP_InertialSensor &ins = AP::ins();

#if INS_MAX_INSTANCES > 1
    if (ins.use_gyro(0) && ins.use_gyro(1)) {
        // dual gyro mode - average first two gyros
        Vector3f dAng;
        readVehicleDeltaAngle(0, dAng);
        _vehicle_delta_angles += dAng*0.5f;
        readVehicleDeltaAngle(1, dAng);
        _vehicle_delta_angles += dAng*0.5f;
    } else
#endif
    {
        // single gyro mode - one of the first two gyros are unhealthy or don't exist
        // just read primary gyro
        Vector3f dAng;
        readVehicleDeltaAngle(ins.get_primary_gyro(), dAng);
        _vehicle_delta_angles += dAng;
    }
}

void SoloGimbal::update_joint_angle_est()
{
    static const float tc = 1.0f;
    float dt = _measurement.delta_time;
    float alpha = constrain_float(dt/(dt+tc),0.0f,1.0f);

    Matrix3f Tvg; // vehicle frame to gimbal frame
    _vehicle_to_gimbal_quat.inverse().rotation_matrix(Tvg);

    Vector3f delta_angle_bias;
    _ekf.getGyroBias(delta_angle_bias);
    delta_angle_bias *= dt;

    Vector3f joint_del_ang;
    gimbal_ang_vel_to_joint_rates((_measurement.delta_angles-delta_angle_bias) - Tvg*_vehicle_delta_angles, joint_del_ang);

    _filtered_joint_angles += joint_del_ang;
    _filtered_joint_angles += (_measurement.joint_angles-_filtered_joint_angles)*alpha;

    _vehicle_to_gimbal_quat_filt.from_vector312(_filtered_joint_angles.x,_filtered_joint_angles.y,_filtered_joint_angles.z);

    _vehicle_delta_angles.zero();
}

Vector3f SoloGimbal::get_ang_vel_dem_yaw(const Quaternion &quatEst)
{
    static const float tc = 0.1f;
    static const float yawErrorLimit = radians(5.7f);
    float dt = _measurement.delta_time;
    float alpha = dt/(dt+tc);

    const auto &_ahrs = AP::ahrs();
    Matrix3f Tve = _ahrs.get_rotation_body_to_ned();
    Matrix3f Teg;
    quatEst.inverse().rotation_matrix(Teg);


    //_vehicle_yaw_rate_ef_filt = _ahrs.get_yaw_rate_earth();

    // filter the vehicle yaw rate to remove noise
    _vehicle_yaw_rate_ef_filt += (_ahrs.get_yaw_rate_earth() - _vehicle_yaw_rate_ef_filt) * alpha;

    float yaw_rate_ff = 0;

    // calculate an earth-frame yaw rate feed-forward that prevents gimbal from exceeding the maximum yaw error
    if (_vehicle_yaw_rate_ef_filt > _gimbalParams.get_K_rate()*yawErrorLimit) {
        yaw_rate_ff = _vehicle_yaw_rate_ef_filt-_gimbalParams.get_K_rate()*yawErrorLimit;
    } else if (_vehicle_yaw_rate_ef_filt < -_gimbalParams.get_K_rate()*yawErrorLimit) {
        yaw_rate_ff = _vehicle_yaw_rate_ef_filt+_gimbalParams.get_K_rate()*yawErrorLimit;
    }

    // filter the feed-forward to remove noise
    //_yaw_rate_ff_ef_filt += (yaw_rate_ff - _yaw_rate_ff_ef_filt) * alpha;

    Vector3f gimbalRateDemVecYaw;
    gimbalRateDemVecYaw.z = yaw_rate_ff - _gimbalParams.get_K_rate() * _filtered_joint_angles.z / constrain_float(Tve.c.z,0.5f,1.0f);
    gimbalRateDemVecYaw.z /= constrain_float(Tve.c.z,0.5f,1.0f);

    // rotate the rate demand into gimbal frame
    gimbalRateDemVecYaw = Teg * gimbalRateDemVecYaw;

    return gimbalRateDemVecYaw;
}

Vector3f SoloGimbal::get_ang_vel_dem_roll_tilt(const Quaternion &quatEst)
{
    // Calculate the gimbal 321 Euler angle estimates relative to earth frame
    Vector3f eulerEst = quatEst.to_vector312();

    // Calculate a demanded quaternion using the demanded roll and pitch and estimated yaw (yaw is slaved to the vehicle)
    Quaternion quatDem;
    quatDem.from_vector312( _att_target_euler_rad.x,
                            _att_target_euler_rad.y,
                            eulerEst.z);

    //divide the demanded quaternion by the estimated to get the error
    Quaternion quatErr = quatDem / quatEst;

    // Convert to a delta rotation
    quatErr.normalize();
    Vector3f deltaAngErr;
    quatErr.to_axis_angle(deltaAngErr);

    // multiply the angle error vector by a gain to calculate a demanded gimbal rate required to control tilt
    Vector3f gimbalRateDemVecTilt = deltaAngErr * _gimbalParams.get_K_rate();
    return gimbalRateDemVecTilt;
}

Vector3f SoloGimbal::get_ang_vel_dem_feedforward(const Quaternion &quatEst) const
{
    // quaternion demanded at the previous time step
    static float lastDemY;

    // calculate the delta rotation from the last to the current demand where the demand does not incorporate the copters yaw rotation
    float deltaY = _att_target_euler_rad.y - lastDemY;
    lastDemY = _att_target_euler_rad.y;

    // quaternion demanded at the previous time step
    static float lastDemX;

    // calculate the delta rotation from the last to the current demand where the demand does not incorporate the copters yaw rotation
    float deltaX = _att_target_euler_rad.x - lastDemX;
    lastDemX = _att_target_euler_rad.x;

    Vector3f gimbalRateDemVecForward;
    gimbalRateDemVecForward.x = deltaX / _measurement.delta_time;
    gimbalRateDemVecForward.y = deltaY / _measurement.delta_time;
    return gimbalRateDemVecForward;
}

Vector3f SoloGimbal::get_ang_vel_dem_gyro_bias()
{
    Vector3f gyroBias;
    _ekf.getGyroBias(gyroBias);
    return gyroBias + _gimbalParams.get_gyro_bias();
}

Vector3f SoloGimbal::get_ang_vel_dem_body_lock()
{
    // Define rotation from vehicle to gimbal using a 312 rotation sequence
    Matrix3f Tvg;
    _vehicle_to_gimbal_quat_filt.inverse().rotation_matrix(Tvg);

    // multiply the joint angles by a gain to calculate a rate vector required to keep the joints centred
    Vector3f gimbalRateDemVecBodyLock;
    gimbalRateDemVecBodyLock = _filtered_joint_angles * -_gimbalParams.get_K_rate();

    joint_rates_to_gimbal_ang_vel(gimbalRateDemVecBodyLock, gimbalRateDemVecBodyLock);

    // Add a feedforward term from vehicle gyros
    const auto &_ahrs = AP::ahrs();
    gimbalRateDemVecBodyLock += Tvg * _ahrs.get_gyro();

    return gimbalRateDemVecBodyLock;
}

void SoloGimbal::update_target(const Vector3f &newTarget)
{
    // Low-pass filter
    _att_target_euler_rad.y = _att_target_euler_rad.y + 0.02f*(newTarget.y - _att_target_euler_rad.y);
    // Update tilt
    _att_target_euler_rad.y = constrain_float(_att_target_euler_rad.y,radians(-90),radians(0));

    // For roll angle
    // Low-pass filter
    _att_target_euler_rad.x = _att_target_euler_rad.x + 0.02f*(newTarget.x - _att_target_euler_rad.x);
    // Update roll
    _att_target_euler_rad.x = constrain_float(_att_target_euler_rad.x,radians(-30),radians(30));
}

void SoloGimbal::write_logs()
{
    AP_Logger &logger = AP::logger();

    const uint64_t tstamp = AP_HAL::micros64();

    // @LoggerMessage: GMB1
    // @Vehicles: Copter
    // @Description: Solo Gimbal measurements
    // @Field: TimeUS: Time since system startup
    // @Field: dt: sum of time across measurements in this packet
    // @Field: dax: delta-angle sum, x-axis
    // @Field: day: delta-angle sum, y-axis
    // @Field: daz: delta-angle sum, z-axis
    // @Field: dvx: delta-velocity sum, x-axis
    // @Field: dvy: delta-velocity sum, y-axis
    // @Field: dvz: delta-velocity sum, z-axis
    // @Field: jx: joint angle, x
    // @Field: jy: joint angle, y
    // @Field: jz: joint angle, z
    logger.Write(
        "GMB1",
        "TimeUS,dt,dax,day,daz,dvx,dvy,dvz,jx,jy,jz",
        "ssrrrEEELLL",
        "FC000000000",
        "Qffffffffff",
        tstamp,
        _log_dt,
        _log_del_ang.x,
        _log_del_ang.y,
        _log_del_ang.z,
        _log_del_vel.x,
        _log_del_vel.y,
        _log_del_vel.z,
        _measurement.joint_angles.x,
        _measurement.joint_angles.y,
        _measurement.joint_angles.z
        );

    Quaternion quatEst;
    _ekf.getQuat(quatEst);
    Vector3f eulerEst;
    quatEst.to_euler(eulerEst.x, eulerEst.y, eulerEst.z);

    // @LoggerMessage: GMB2
    // @Vehicles: Copter
    // @Description: Solo Gimbal estimation and demands
    // @Field: TimeUS: Time since system startup
    // @Field: es: Solo Gimbal EKF status bits
    // @Field: ex: Solo Gimbal EKF estimate of gimbal angle, x-axis
    // @Field: ey: Solo Gimbal EKF estimate of gimbal angle, y-axis
    // @Field: ez: Solo Gimbal EKF estimate of gimbal angle, y-axis
    // @Field: rx: Angular velocity demand around x-axis
    // @Field: ry: Angular velocity demand around y-axis
    // @Field: rz: Angular velocity demand around z-axis
    // @Field: tx: Angular position target around x-axis
    // @Field: ty: Angular position target around y-axis
    // @Field: tz: Angular position target around z-axis
    logger.Write(
        "GMB2",
        "TimeUS,es,ex,ey,ez,rx,ry,rz,tx,ty,tz",
        "s-rrrEEELLL",
        "F-000000000",
        "QBfffffffff",
        tstamp,
        (uint8_t) _ekf.getStatus(),
        eulerEst.x,
        eulerEst.y,
        eulerEst.z,
        _ang_vel_dem_rads.x,
        _ang_vel_dem_rads.y,
        _ang_vel_dem_rads.z,
        _att_target_euler_rad.x,
        _att_target_euler_rad.y,
        _att_target_euler_rad.z
        );

    _log_dt = 0;
    _log_del_ang.zero();
    _log_del_vel.zero();
}

bool SoloGimbal::joints_near_limits() const
{
    return fabsf(_measurement.joint_angles.x) > radians(40) || _measurement.joint_angles.y > radians(45) || _measurement.joint_angles.y < -radians(135);
}

AccelCalibrator* SoloGimbal::_acal_get_calibrator(uint8_t instance)
{
    if(instance==0 && (present() || _calibrator.get_status() == ACCEL_CAL_SUCCESS)) {
        return &_calibrator;
    } else {
        return nullptr;
    }
}

bool SoloGimbal::_acal_get_ready_to_sample()
{
    return _ang_vel_mag_filt < radians(10);
}

bool SoloGimbal::_acal_get_saving()
{
    return _gimbalParams.flashing();
}

void SoloGimbal::_acal_save_calibrations()
{
    if (_calibrator.get_status() != ACCEL_CAL_SUCCESS) {
        return;
    }
    Vector3f bias;
    Vector3f gain;
    _calibrator.get_calibration(bias,gain);
    _gimbalParams.set_accel_bias(bias);
    _gimbalParams.set_accel_gain(gain);
    _gimbalParams.flash();
}

void SoloGimbal::gimbal_ang_vel_to_joint_rates(const Vector3f& ang_vel, Vector3f& joint_rates) const
{
    float sin_theta = sinf(_measurement.joint_angles.y);
    float cos_theta = cosf(_measurement.joint_angles.y);

    float sin_phi = sinf(_measurement.joint_angles.x);
    float cos_phi = cosf(_measurement.joint_angles.x);
    float sec_phi = 1.0f/cos_phi;
    float tan_phi = sin_phi/cos_phi;

    joint_rates.x = ang_vel.x*cos_theta+ang_vel.z*sin_theta;
    joint_rates.y = ang_vel.x*sin_theta*tan_phi-ang_vel.z*cos_theta*tan_phi+ang_vel.y;
    joint_rates.z = sec_phi*(ang_vel.z*cos_theta-ang_vel.x*sin_theta);
}

void SoloGimbal::joint_rates_to_gimbal_ang_vel(const Vector3f& joint_rates, Vector3f& ang_vel) const
{
    float sin_theta = sinf(_measurement.joint_angles.y);
    float cos_theta = cosf(_measurement.joint_angles.y);

    float sin_phi = sinf(_measurement.joint_angles.x);
    float cos_phi = cosf(_measurement.joint_angles.x);

    ang_vel.x = cos_theta*joint_rates.x-sin_theta*cos_phi*joint_rates.z;
    ang_vel.y = joint_rates.y + sin_phi*joint_rates.z;
    ang_vel.z = sin_theta*joint_rates.x+cos_theta*cos_phi*joint_rates.z;
}

#endif // HAL_SOLO_GIMBAL_ENABLED
