// FGAIWingman - FGAIBllistic-derived class creates an AI Wingman
//
// Written by Vivian Meazza, started February 2008.
// - vivian.meazza at lineone.net 
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <simgear/sg_inlines.h>

#include <Main/fg_props.hxx>

#include "AIWingman.hxx"

FGAIWingman::FGAIWingman() : FGAIBallistic(object_type::otWingman),
_formate_to_ac(true),
_break(false),
_join(false),
_break_angle(-90),
_coeff_hdg(5.0),
_coeff_pch(5.0),
_coeff_bnk(5.0),
_coeff_spd(2.0)

{
    invisible = false;
    _parent="";
    tgt_heading = 250;

}

void FGAIWingman::readFromScenario(SGPropertyNode* scFileNode) {
    if (!scFileNode)
        return;

    FGAIBase::readFromScenario(scFileNode);

    setAzimuth(scFileNode->getDoubleValue("azimuth", 0.0));
    setElevation(scFileNode->getDoubleValue("elevation", 0.0));
    setLife(scFileNode->getDoubleValue("life", -1));
    setNoRoll(scFileNode->getBoolValue("no-roll", false));
    setName(scFileNode->getStringValue("name", "Wingman"));
    setParentName(scFileNode->getStringValue("parent", ""));
    setSubID(scFileNode->getIntValue("SubID", 0));
    setXoffset(scFileNode->getDoubleValue("x-offset", 0.0));
    setYoffset(scFileNode->getDoubleValue("y-offset", 0.0));
    setZoffset(scFileNode->getDoubleValue("z-offset", 0.0));
    setPitchoffset(scFileNode->getDoubleValue("pitch-offset", 0.0));
    setRolloffset(scFileNode->getDoubleValue("roll-offset", 0.0));
    setYawoffset(scFileNode->getDoubleValue("yaw-offset", 0.0));
    setGroundOffset(scFileNode->getDoubleValue("ground-offset", 0.0));
    setFormate(scFileNode->getBoolValue("formate", true));
    setMaxSpeed(scFileNode->getDoubleValue("max-speed-kts", 300.0));
    setCoeffHdg(scFileNode->getDoubleValue("coefficients/heading", 5.0));
    setCoeffPch(scFileNode->getDoubleValue("coefficients/pitch", 5.0));
    setCoeffBnk(scFileNode->getDoubleValue("coefficients/bank", 4.0));
    setCoeffSpd(scFileNode->getDoubleValue("coefficients/speed", 2.0));


}

void FGAIWingman::bind() {
    FGAIBallistic::bind();

    props->untie("controls/slave-to-ac");

    tie("id", SGRawValueMethods<FGAIBase,int>(*this,
        &FGAIBase::getID));
    tie("subID", SGRawValueMethods<FGAIBase,int>(*this,
        &FGAIBase::_getSubID));
    tie("position/altitude-ft",
        SGRawValueMethods<FGAIBase,double>(*this,
        &FGAIBase::_getElevationFt,
        &FGAIBase::_setAltitude));
    tie("position/latitude-deg",
        SGRawValueMethods<FGAIBase,double>(*this,
        &FGAIBase::_getLatitude,
        &FGAIBase::_setLatitude));
    tie("position/longitude-deg",
        SGRawValueMethods<FGAIBase,double>(*this,
        &FGAIBase::_getLongitude,
        &FGAIBase::_setLongitude));

    tie("controls/break", SGRawValuePointer<bool>(&_break));
    tie("controls/join", SGRawValuePointer<bool>(&_join));

    tie("controls/formate-to-ac",
        SGRawValueMethods<FGAIWingman,bool>
        (*this, &FGAIWingman::getFormate, &FGAIWingman::setFormate));
    tie("controls/tgt-heading-deg",
        SGRawValueMethods<FGAIWingman,double>
        (*this, &FGAIWingman::getTgtHdg, &FGAIWingman::setTgtHdg));
    tie("controls/tgt-speed-kt",
        SGRawValueMethods<FGAIWingman,double>
        (*this, &FGAIWingman::getTgtSpd, &FGAIWingman::setTgtSpd));
    tie("controls/break-deg-rel",
        SGRawValueMethods<FGAIWingman,double>
        (*this, &FGAIWingman::getBrkAng, &FGAIWingman::setBrkAng));
    tie("controls/coefficients/heading",
        SGRawValuePointer<double>(&_coeff_hdg));
    tie("controls/coefficients/pitch",
        SGRawValuePointer<double>(&_coeff_pch));
    tie("controls/coefficients/bank",
        SGRawValuePointer<double>(&_coeff_bnk));
    tie("controls/coefficients/speed",
        SGRawValuePointer<double>(&_coeff_spd));

    tie("orientation/pitch-deg",   SGRawValuePointer<double>(&pitch));
    tie("orientation/roll-deg",    SGRawValuePointer<double>(&roll));
    tie("orientation/true-heading-deg", SGRawValuePointer<double>(&hdg));

    tie("submodels/serviceable", SGRawValuePointer<bool>(&serviceable));

    tie("load/rel-brg-to-user-deg",
        SGRawValueMethods<FGAIBallistic,double>
        (*this, &FGAIBallistic::getRelBrgHitchToUser));
    tie("load/elev-to-user-deg",
        SGRawValueMethods<FGAIBallistic,double>
        (*this, &FGAIBallistic::getElevHitchToUser));

    tie("velocities/vertical-speed-fps",
        SGRawValueMethods<FGAIBase,double>(*this, &FGAIBase::_getVS_fps, &FGAIBase::_setVS_fps));
    tie("velocities/true-airspeed-kt",
        SGRawValuePointer<double>(&speed));
    tie("velocities/speed-east-fps",
        SGRawValuePointer<double>(&_speed_east_fps));
    tie("velocities/speed-north-fps",
        SGRawValuePointer<double>(&_speed_north_fps));

    tie("position/x-offset",
        SGRawValueMethods<FGAIBase,double>(*this, &FGAIBase::_getXOffset, &FGAIBase::setXoffset));
    tie("position/y-offset",
        SGRawValueMethods<FGAIBase,double>(*this, &FGAIBase::_getYOffset, &FGAIBase::setYoffset));
    tie("position/z-offset",
        SGRawValueMethods<FGAIBase,double>(*this, &FGAIBase::_getZOffset, &FGAIBase::setZoffset));
    tie("position/tgt-x-offset",
        SGRawValueMethods<FGAIBallistic,double>(*this, &FGAIBallistic::getTgtXOffset, &FGAIBallistic::setTgtXOffset));
    tie("position/tgt-y-offset",
        SGRawValueMethods<FGAIBallistic,double>(*this, &FGAIBallistic::getTgtYOffset, &FGAIBallistic::setTgtYOffset));
    tie("position/tgt-z-offset",
        SGRawValueMethods<FGAIBallistic,double>(*this, &FGAIBallistic::getTgtZOffset, &FGAIBallistic::setTgtZOffset));
}

bool FGAIWingman::init(ModelSearchOrder searchOrder)
{
    if (!FGAIBallistic::init(searchOrder))
        return false;
    reinit();
    return true;
}

void FGAIWingman::reinit() {
    invisible = false;

    _tgt_x_offset = _x_offset;
    _tgt_y_offset = _y_offset;
    _tgt_z_offset = _z_offset;

    hdg = _azimuth;
    pitch = _elevation;
    roll = _rotation;
    _ht_agl_ft = 1e10;

    if(_parent != ""){
        setParentNode();
    }

    setParentNodes(_selected_ac);

    props->setStringValue("submodels/path", _path.c_str());
    user_WoW_node      = fgGetNode("gear/gear[1]/wow", true);

    FGAIBallistic::reinit();
}

void FGAIWingman::update(double dt) {

//    FGAIBallistic::update(dt);

    if (_formate_to_ac){
        formateToAC(dt);
        Transform();
        setBrkHdg(_break_angle);
    }else if (_break) {
        FGAIBase::update(dt);
        tgt_altitude_ft = altitude_ft;
        tgt_speed = speed;
        tgt_roll = roll;
        tgt_pitch = pitch;
        Break(dt);
        Transform();
    } else {
        Join(dt);
        Transform();
    }

}

double FGAIWingman::calcDistanceM(SGGeod pos1, SGGeod pos2) const {
    //calculate the distance load to hitch
    SGVec3d cartPos1 = SGVec3d::fromGeod(pos1);
    SGVec3d cartPos2 = SGVec3d::fromGeod(pos2);

    SGVec3d diff = cartPos1 - cartPos2;
    double distance = norm(diff);
    return distance;
}

double FGAIWingman::calcAngle(double range, SGGeod pos1, SGGeod pos2){

    double angle = 0;
    double distance = calcDistanceM(pos1, pos2);
    double daltM = pos1.getElevationM() - pos2.getElevationM();

    if (fabs(distance) < SGLimits<float>::min()) {
        angle = 0;
    } else {
        double sAngle = daltM/range;
        sAngle = SGMiscd::min(1, SGMiscd::max(-1, sAngle));
        angle = SGMiscd::rad2deg(asin(sAngle));
    }

    return angle;
}

void FGAIWingman::formateToAC(double dt){

    double p_hdg, p_pch, p_rll, p_agl, p_ht, p_wow = 0;

    setTgtOffsets(dt, 25);

    if (_pnode != 0) {
        setParentPos();
        p_hdg = _p_hdg_node->getDoubleValue();
        p_pch = _p_pch_node->getDoubleValue();
        p_rll = _p_rll_node->getDoubleValue();
        p_ht  = _p_alt_node->getDoubleValue();
        setOffsetPos(_parentpos, p_hdg, p_pch, p_rll);
        setSpeed(_p_spd_node->getDoubleValue());
    }else {
        p_hdg = manager->get_user_heading();
        p_pch = manager->get_user_pitch();
        p_rll = manager->get_user_roll();
        p_ht  = globals->get_aircraft_position().getElevationFt();
        setOffsetPos(globals->get_aircraft_position(), p_hdg,p_pch, p_rll);
        setSpeed(manager->get_user_speed());
    }

    // elapsed time has a random initialisation so that each
    // wingman moves differently
    _elapsed_time += dt;

    // we derive a sine based factor to give us smoothly
    // varying error between -1 and 1
    double factor  = sin(SGMiscd::deg2rad(_elapsed_time * 10));
    double r_angle = 5 * factor;
    double p_angle = 2.5 * factor;
    double h_angle = 5 * factor;
    double h_feet  = 3 * factor;

    p_agl = manager->get_user_agl();
    p_wow = user_WoW_node->getDoubleValue();

    if(p_agl <= 10 || p_wow == 1) {
        _height = p_ht;
    } else if (p_agl <= 150 ) {
        setHt(p_ht, dt, 1.0);
    } else if (p_agl <= 250) {
        setHt(_offsetpos.getElevationFt() + h_feet, dt, 0.75);
    } else{
        setHt(_offsetpos.getElevationFt() + h_feet, dt, 0.5);
    }

    pos.setElevationFt(_height);
    pos.setLatitudeDeg(_offsetpos.getLatitudeDeg());
    pos.setLongitudeDeg(_offsetpos.getLongitudeDeg());

    // these calculations are unreliable at slow speeds
    // and we don't want random movement on the ground
    if(speed >= 10 && p_wow != 1) {
        setHdg(p_hdg + h_angle, dt, 0.9);
        setPch(p_pch + p_angle + _pitch_offset, dt, 0.9);

        if (roll <= 115 && roll >= -115)
            setBnk(p_rll + r_angle + _roll_offset, dt, 0.5);
        else
            roll = p_rll + r_angle + _roll_offset;

    } else {
        setHdg(p_hdg, dt, 0.9);
        setPch(p_pch + _pitch_offset, dt, 0.9);
        setBnk(p_rll + _roll_offset, dt, 0.9);
    }

        setOffsetVelocity(dt, pos);
}// end formateToAC

void FGAIWingman::Break(double dt) {

    Run(dt);

    //calculate the turn direction: 1 = right, -1 = left
    double rel_brg = calcRelBearingDeg(tgt_heading, hdg);
    int turn = SGMiscd::sign(rel_brg);

    // set heading and pitch
    setHdg(tgt_heading, dt, _coeff_hdg);
    setPch(0, dt, _coeff_pch);

    if (fabs(tgt_heading - hdg) >= 10)
        setBnk(45 * turn , dt, _coeff_bnk);
    else
        setBnk(0, dt, _coeff_bnk);

}  // end Break

void FGAIWingman::Join(double dt) {

    double range, bearing, az2;
    double parent_hdg, parent_spd = 0;
    double p_hdg, p_pch, p_rll = 0;

    setTgtOffsets(dt, 25);

    if (_pnode != 0) {
        setParentPos();
        p_hdg = _p_hdg_node->getDoubleValue();
        p_pch = _p_pch_node->getDoubleValue();
        p_rll = _p_rll_node->getDoubleValue();
        setOffsetPos(_parentpos, p_hdg, p_pch, p_rll);
        parent_hdg = _p_hdg_node->getDoubleValue();
        parent_spd = _p_spd_node->getDoubleValue();
    }else {
        p_hdg = manager->get_user_heading();
        p_pch = manager->get_user_pitch();
        p_rll = manager->get_user_roll();
        setOffsetPos(globals->get_aircraft_position(), p_hdg, p_pch, p_rll);
        parent_hdg = manager->get_user_heading();
        parent_spd = manager->get_user_speed();
    }

    setSpeed(parent_spd);

    double distance = calcDistanceM(pos, _offsetpos);
    double daltM = _offsetpos.getElevationM() - pos.getElevationM();
    double limit = 10;
    double hdg_l_lim = parent_hdg - limit;
    SG_NORMALIZE_RANGE(hdg_l_lim, 0.0, 360.0);
    double hdg_r_lim = parent_hdg + limit;
    SG_NORMALIZE_RANGE(hdg_r_lim, 0.0, 360.0);

    if (distance <= 2 && fabs(daltM) <= 2 &&
        (hdg >= hdg_l_lim || hdg <= hdg_r_lim)){
            _height = _offsetpos.getElevationFt();
            _formate_to_ac = true;
            _join = false;

            SG_LOG(SG_AI, SG_ALERT, _name << " joined " << " RANGE " << distance
            << " SPEED " << speed );

            return;
    }

    geo_inverse_wgs_84(pos, _offsetpos, &bearing, &az2, &range);

    double rel_brg   = calcRelBearingDeg(bearing, hdg);
    double recip_brg = calcRecipBearingDeg(bearing);
    double angle = calcAngle(distance,_offsetpos, pos);
    //double approx_angle = atan2(daltM, range);
    double frm_spd = 50; // formation speed
    double join_rnge = 1000.0;
//    double recip_parent_hdg = calcRecipBearingDeg(parent_hdg);
    int turn = SGMiscd::sign(rel_brg);// turn direction: 1 = right, -1 = left

    if (range <= join_rnge && (hdg >= hdg_l_lim || hdg <= hdg_r_lim)){

        //these are the rules governing joining

        if ((rel_brg <= -175 || rel_brg >= 175) && range <=10 ){
            // station is behind us - back up a bit
            setSpeed(parent_spd - ((frm_spd/join_rnge) * range));
            setHdg(recip_brg, dt, _coeff_hdg);
            setPch(angle, dt, _coeff_pch);
            //cout << _name << " backing up HEADING " << hdg
            //    << " RANGE " << range;
        } else if (rel_brg >= -5 && rel_brg <= 5) {
            // station is in front of us - slow down
            setSpeed(parent_spd + ((frm_spd/100) * range));
            //SGMiscd::clip
            setHdg(bearing, dt, 1.5);
            setPch(angle, dt, _coeff_pch);
            //cout << _name << " slowing HEADING " << hdg
            //    << " RANGE " << range <<endl;
        } else if ( range <=10 ){
            // station is to one side - equal speed and turn towards
            setSpd(parent_spd , dt, 2.0);
            setSpeed(_speed);
            setHdg(parent_hdg + (5 * turn), dt, _coeff_hdg);
            //cout << _name << " equal speed HEADING " << hdg
            //    << " RANGE " << range<< endl;
        } else {
            // we missed it - equal speed and turn to recip
            setSpd(parent_spd , dt, 2.0);
            setSpeed(_speed);
            setHdg(recip_brg, dt, _coeff_hdg);
            //cout << _name << " WHOOPS!! missed join HEADING " << hdg
            //    << " RANGE " << range<< endl;
        }

    } else if (range <= join_rnge) {
        // we missed it - equal speed and turn to recip
        setSpd(parent_spd , dt, 2.0);
        setSpeed(_speed);
        setHdg(recip_brg , dt, _coeff_hdg);
        //cout << _name << " WHOOPS!! missed approach HEADING " << hdg
        //    << " " << recip_brg
        //    /*<< " " << recip_parent_hdg*/
        //    << " RANGE " << range<< endl;
    } else if (range > join_rnge && range <= 2000 ){
        //approach phase
        //cout << _name << " approach HEADING " << hdg
        //        << " RANGE " << range<< endl;
        setSpd(parent_spd + frm_spd, dt, 2.0);
        setSpeed(_speed);
        setHdg(bearing, dt, _coeff_hdg);
        setPch(angle, dt, _coeff_pch);
    } else {
        //hurry up
        //cout << _name << " hurry up HEADING " << hdg
        //        << " RANGE " << range<< endl;
        setSpd(_max_speed -10, dt, 2.0);
        setSpeed(_speed);
        setHdg(bearing, dt, _coeff_hdg);
        setPch(angle, dt, _coeff_pch);
    }

    Run(dt);

    // set roll

    if (fabs(bearing - hdg) >= 10)
        setBnk(45 * turn , dt, _coeff_bnk);
    else
        setBnk(0, dt, _coeff_bnk);

}  // end Join

void FGAIWingman::Run(double dt) {

    // don't let speed become negative
    SG_CLAMP_RANGE(speed, 100.0, _max_speed);

    double speed_fps = speed * SG_KT_TO_FPS;

    // calculate vertical and horizontal speed components
    if (speed == 0.0) {
        hs = vs_fps = 0.0;
    } else {
        vs_fps = sin( pitch * SG_DEGREES_TO_RADIANS ) * speed_fps;
        hs = cos( pitch * SG_DEGREES_TO_RADIANS ) * speed_fps;
    }

    //cout << "vs hs " << vs << " " << hs << endl;

    //resolve horizontal speed into north and east components:
    double speed_north_fps = cos(hdg / SG_RADIANS_TO_DEGREES) * hs;
    double speed_east_fps = sin(hdg / SG_RADIANS_TO_DEGREES) * hs;

    // convert horizontal speed (fps) to degrees per second
    double speed_north_deg_sec = speed_north_fps / ft_per_deg_lat;
    double speed_east_deg_sec  = speed_east_fps / ft_per_deg_lon;

    //get wind components
    _wind_from_north = manager->get_wind_from_north();
    _wind_from_east = manager->get_wind_from_east();

    // convert wind speed (fps) to degrees lat/lon per second
    double wind_speed_from_north_deg_sec = _wind_from_north / ft_per_deg_lat;
    double wind_speed_from_east_deg_sec  = _wind_from_east / ft_per_deg_lon;

    //recombine the horizontal velocity components
    hs = sqrt(((speed_north_fps) * (speed_north_fps))
        + ((speed_east_fps)* (speed_east_fps )));

    if (hs <= 0.00001)
        hs = 0;

    if (vs_fps <= 0.00001 && vs_fps >= -0.00001)
        vs_fps = 0;

    //cout << "lat " << pos.getLatitudeDeg()<< endl;
    // set new position
    pos.setLatitudeDeg( pos.getLatitudeDeg()
            + (speed_north_deg_sec - wind_speed_from_north_deg_sec) * dt );
    pos.setLongitudeDeg( pos.getLongitudeDeg()
            + (speed_east_deg_sec - wind_speed_from_east_deg_sec ) * dt );
    pos.setElevationFt(pos.getElevationFt() + vs_fps * dt);

    //cout << _name << " run hs " << hs << " vs " << vs << endl;

    // recalculate total speed
    if ( vs_fps == 0 && hs == 0)
        speed = 0;
    else
        speed = sqrt( vs_fps * vs_fps + hs * hs) / SG_KT_TO_FPS;

    // recalculate elevation and azimuth (velocity vectors)
    pitch = atan2( vs_fps, hs ) * SG_RADIANS_TO_DEGREES;
    hdg   = atan2((speed_east_fps),(speed_north_fps))* SG_RADIANS_TO_DEGREES;

    // rationalise heading
    SG_NORMALIZE_RANGE(hdg, 0.0, 360.0);

}// end Run

// end AIWingman
