/*
 *   ORXONOX - the hottest 3D action shooter ever to exist
 *                    > www.orxonox.net <
 *
 *
 *   License notice:
 *
 *   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.
 *
 *   Author:
 *      Gani Aliguzhinov
 *   Co-authors:
 *      ...
 *
 */

#include "ActionpointController.h"

#include "core/XMLPort.h"
#include <algorithm>
#include "worldentities/Actionpoint.h"
namespace orxonox
{

    RegisterClass(ActionpointController);

    ActionpointController::ActionpointController(Context* context) : FightingController(context)
    {
        this->ticks_ = 0;
        this->bPatrolling_ = false;
        this->bInLoop_ = false;
        this->bLoop_ = false;
        this->bEndLoop_ = false;
        loopActionpoints_.clear();
        parsedActionpoints_.clear();
        actionpoints_.clear();
        this->bTakenOver_ = false;
        this->action_ = Action::NONE;
        this->squaredaccuracy_ = 2500;
        this->bStartedDodging_ = false;
        this->bDefaultPatrol_ = true;
        this->bDefaultFightAll_ = true;
        RegisterObject(ActionpointController);

    }
    void ActionpointController::XMLPort( Element& xmlelement, XMLPort::Mode mode )
    {
        SUPER( ActionpointController, XMLPort, xmlelement, mode );

        XMLPortObject(ActionpointController, WorldEntity, "actionpoints", addActionpoint, getActionpoint,  xmlelement, mode);
        XMLPortParam(ActionpointController, "defaultFightAll", setDefaultFightAll, getDefaultFightAll, xmlelement, mode).defaultValues(true);
        XMLPortParam(ActionpointController, "defaultPatrol", setDefaultPatrol, getDefaultPatrol, xmlelement, mode).defaultValues(true);
    }

    ActionpointController::~ActionpointController()
    {
        loopActionpoints_.clear();
        parsedActionpoints_.clear();
        actionpoints_.clear();

    }
    void ActionpointController::tick(float dt)
    {
        if (!this->getControllableEntity() || !this->isActive())
            return;

        //count ticks, ticks_ is unsigned, so overflow is not a problem
        ++this->ticks_;
        if (this->ticks_ == 1)
        {
            //those vectors are in reversed order after being set by XML.
            std::reverse(parsedActionpoints_.begin(), parsedActionpoints_.end());
            std::reverse(actionpoints_.begin(), actionpoints_.end());
        }

        //fly
        if (this->bHasTargetPosition_)
        {
            this->moveToTargetPosition(dt);
        }//or just rotate
        else if (this->bLookAtTarget_)
        {
            this->lookAtTarget(dt);
        }
        
        //don't fire rocket each tick
        if (timeout_ <= 0)
        {    
            this->bFiredRocket_ = false;
        }
        else if (this->bFiredRocket_)
        {
            --this->timeout_;
        }

        //sometimes dodge, sometimes attack
        if (this->ticks_ % 80 <= 10)
        {
            this->bDodge_ = false;
        }
        else
        {
            this->bDodge_ = true;
        }

        //fire if you can
        if (this->bShooting_)
        {
            this->doFire();
        }
        SUPER(ActionpointController, tick, dt);
    }
     
    /**
    @brief
        action() manages the state machine.
    */

    void ActionpointController::action()
    {
        if (!this->getControllableEntity() || !this->isActive())
            return;

        //deltaHp is used to know if this got attacked
        this->deltaHp = orxonox_cast<Pawn*> (this->getControllableEntity())->getHealth() - this->previousHp;
        this->previousHp = orxonox_cast<Pawn*> (this->getControllableEntity())->getHealth();

        //look out for enemies
        if (this->bDefaultPatrol_ || (this->action_ != Action::FLY && this->action_ != Action::NONE))
        {
            this->startAttackingEnemiesThatAreClose();
        }

        //No action -> pop one from stack
        if (this->action_ == Action::NONE || this->bTakenOver_)
        {
            //if default behaviour is fighting all, push it onto the stack
            if (this->parsedActionpoints_.empty() && this->loopActionpoints_.empty() && this->bDefaultFightAll_)
            {
                Point p = { Action::FIGHTALL, "", Vector3::ZERO, false };
                this->parsedActionpoints_.push_back (p);
            }
            this->executeActionpoint();
            this->bTakenOver_ = false;
        }

        //Action fightall -> fight till nobody alive
        if (this->action_ == Action::FIGHTALL)
        {

            if (!this->hasTarget())
            {
                ControllableEntity* newTarget = this->closestTarget();    
                if (newTarget)
                {
                    this->setAction (Action::FIGHTALL, newTarget);
                }
                else
                {
                    this->nextActionpoint();
                    this->executeActionpoint();
    
                }
            }
        }
        //Action fight -> fight as long as enemies in range
        else if (this->action_ == Action::FIGHT)
        {
            if (!this->hasTarget() )
            {
                //----find a target----
                ControllableEntity* newTarget = this->closestTarget();
                if (newTarget && 
                        CommonController::distance (this->getControllableEntity(), newTarget) < this->attackRange_)
                {
                    this->setAction (Action::FIGHT, newTarget);
                }
                else
                {
                    this->nextActionpoint();
                    this->executeActionpoint();
                }
            }
            else if (this->hasTarget())
            {
                //----fly in formation if far enough----
                Vector3 diffVector = this->positionOfTarget_ - this->getControllableEntity()->getWorldPosition();         
                    
                if (diffVector.length() > this->attackRange_)
                {
                    ControllableEntity* newTarget = this->closestTarget();
                    if (newTarget && 
                        CommonController::distance (this->getControllableEntity(), newTarget) < this->attackRange_)
                    {
                        this->setAction (Action::FIGHT, newTarget);
                    }
                    else
                    {
                        this->nextActionpoint();
                        this->executeActionpoint();
                    }
                }
            }
        }
        else if (this->action_ == Action::FLY)
        {
            if (this->squaredDistanceToTarget() <= this->squaredaccuracy_)
            {
                this->nextActionpoint();   
                this->executeActionpoint();
            }
        }
        else if (this->action_ == Action::PROTECT)
        {
            if (!this->getProtect())
            {
                this->nextActionpoint();
                this->executeActionpoint(); 
            }
            this->stayNearProtect();
        }
        else if (this->action_ == Action::ATTACK)
        {   
            if (!this->hasTarget())
            {
                this->nextActionpoint();
                this->executeActionpoint();
            }
        }
    }
    /**
    @brief
        if action is protect, this follows protect_ and fights enemies that are close
    */
    void ActionpointController::setProtect (ControllableEntity* protect)
    {
        this->protect_ = protect;
    }
    ControllableEntity* ActionpointController::getProtect ()
    {
        return this->protect_;
    }
    //XML method
    void ActionpointController::addActionpoint(WorldEntity* actionpoint)
    {
        std::string actionName;
        Vector3 position;
        std::string targetName;
        bool inLoop = false;
        Point p;
        if (actionpoint->getIdentifier()->getName() == "Actionpoint")
        {
            Actionpoint* ap = orxonox_cast<Actionpoint*> (actionpoint);
            actionName = ap->getActionXML();
            targetName = ap->getName();
            position = ap->getWorldPosition();

            if (this->bEndLoop_)
            {
                this->bInLoop_ = false;
            }
            if (!this->bInLoop_ && ap->getLoopStart())
            {
                this->bInLoop_ = true;
            }
            if (this->bInLoop_ && ap->getLoopEnd())
            {
                this->bEndLoop_ = true;
            }
            inLoop = this->bInLoop_;

            Action value;
            
            if ( actionName == "FIGHT" )
            { value = Action::FIGHT; }
            else if ( actionName == "FLY" )
            { value = Action::FLY; }
            else if ( actionName == "PROTECT" )
            { value = Action::PROTECT; }
            else if ( actionName == "NONE" )
            { value = Action::NONE; }
            else if ( actionName == "FIGHTALL" )
            { value = Action::FIGHTALL; }
            else if ( actionName == "ATTACK" )
            { value = Action::ATTACK; }
            else
                ThrowException( ParseError, std::string( "Attempting to set an unknown Action: '" )+ actionName + "'." );
            p.action = value; p.name = targetName; p.position = position; p.inLoop = inLoop;
        }
        else
        {
            inLoop = true;
            p.action = Action::FLY; p.name = ""; p.position = actionpoint->getWorldPosition(); p.inLoop = inLoop;
        }
            parsedActionpoints_.push_back(p);
            this->actionpoints_.push_back(actionpoint);
    }
    //XML method
    WorldEntity* ActionpointController::getActionpoint(unsigned int index) const
    {
        if (index < this->actionpoints_.size())
            return this->actionpoints_[index];
        else
            return nullptr;
    }
    //XML method
    Action ActionpointController::getAction ()
    {
        return this->action_;
    }
    //XML method
    std::string ActionpointController::getActionName()
    {
        switch ( this->action_ )
        {
            case Action::FIGHT:
            { return "FIGHT"; }
            case Action::FLY:
            { return "FLY"; }
            case Action::PROTECT:
            { return "PROTECT"; }
            case Action::NONE:
            { return "NONE"; }
            case Action::FIGHTALL:
            { return "FIGHTALL"; }
            case Action::ATTACK:
            { return "ATTACK"; }
            default:
                return "NONE";
                break;
        }
    }
    //XML method
    void ActionpointController::setAction (Action action)
    {
        this->action_ = action;
    }
    //set action and target/protect
    void ActionpointController::setAction (Action action, ControllableEntity* target)
    {
        if (!this->getControllableEntity())
            return;
        this->action_ = action;
        if (action == Action::FIGHT || action == Action::FIGHTALL || action == Action::ATTACK)
        {   
            if (target)
                this->setTarget (target);
        }
        else if (action == Action::PROTECT)
        {
            if (target)
                this->setProtect (target);
        }
    }
    //set action and target position
    void ActionpointController::setAction (Action action, const Vector3& target)
    {
        if (!this->getControllableEntity())
            return;
        this->action_ = action;
        if (action == Action::FLY)
        {
            this->setTargetPosition (target);
        }
    }
    //set action and target position and orientation
    void ActionpointController::setAction (Action action, const Vector3& target,  const Quaternion& orient )
    {
        if (!this->getControllableEntity())
            return;
        this->action_ = action;
        if (action == Action::FLY)
        {
            this->setTargetPosition (target);
            this->setTargetOrientation (orient);
        }  
    }
    
    //------------------------------------------------------------------------------
    //------------------------------Actionpoint methods-----------------------------
    //------------------------------------------------------------------------------

    //POST: this starts doing what was asked by the last element of parsedActionpoints_,
    //if last element was failed to be parsed, next element will be executed.
    void ActionpointController::executeActionpoint()
    {
        if (!this->getControllableEntity())
            return;

        Point p;
        if (this->bLoop_ && !loopActionpoints_.empty())
        {
            p = loopActionpoints_.back();
        }
        else if (this->bLoop_)
        {
            this->bLoop_ = false;
            return;
        }
        else if (!this->bLoop_ && !parsedActionpoints_.empty())
        {
            p = parsedActionpoints_.back();
        }
        else
        {
            this->setTarget(nullptr);
            this->setTargetPosition(this->getControllableEntity()->getWorldPosition());
            this->action_ = Action::NONE;
            return;
        }
        if (!this->bLoop_ && this->parsedActionpoints_.back().inLoop)
        {
            //MOVES all points that are in loop to a loop vector
            this->fillLoop();
            this->bLoop_ = true;
            executeActionpoint();
            return;
        }
        this->setAction (p.action);

        switch (this->action_)
        {
            case Action::FIGHT:
            {
                std::string targetName = p.name;
                if (targetName == "")
                    break;
                for (Pawn* pawn : ObjectList<Pawn>())
                {
                    if (CommonController::getName(pawn) == targetName)
                    {
                        this->setTarget (static_cast<ControllableEntity*>(pawn));
                    }
                }
                break;
            }
            case Action::FLY:
            {
                this->setTargetPosition( p.position );
                if (this->squaredDistanceToTarget() <= this->squaredaccuracy_)
                {
                    this->nextActionpoint();
                    this->executeActionpoint();
                }
                break;
            }
            case Action::PROTECT:
            {

                std::string protectName = p.name;
                if (protectName == "reservedKeyword:human")
                {
                    for (Pawn* pawn : ObjectList<Pawn>())
                    {
                        if (orxonox_cast<ControllableEntity*>(pawn) && (pawn->getController()) && (pawn->getController()->getIdentifier()->getName() == "NewHumanController"))
                        {
                            this->setProtect (static_cast<ControllableEntity*>(pawn));
                        }
                    }
                }
                else
                {
                    for (Pawn* pawn : ObjectList<Pawn>())
                    {
                        if (CommonController::getName(pawn) == protectName)
                        {
                            this->setProtect (static_cast<ControllableEntity*>(pawn));
                        }
                    }                            
                }
                if (!this->getProtect())
                {
                    this->nextActionpoint();
                    this->executeActionpoint();
                }
                break;
            }
            case Action::NONE:
            {
                break;
            }
            case Action::FIGHTALL:
            {
                break;
            }
            case Action::ATTACK:
            {
                std::string targetName = p.name;

                for (Pawn* pawn : ObjectList<Pawn>())
                {
                    if (CommonController::getName(pawn) == targetName)
                    {
                        this->setTarget (static_cast<ControllableEntity*>(pawn));
                    }
                }
                if (!this->hasTarget())
                {
                    this->nextActionpoint();
                    this->executeActionpoint();
                }
                break;
            }
            default:
                break;
        }   
    }

    //calculate where in world coordinates this ship has to be, so that it keeps distance to protect_, and fly there
    void ActionpointController::stayNearProtect()
    {
        if (!this->getControllableEntity())
            return;

        Vector3 targetRelativePosition(0, 300, 300);
        if (!this->getProtect())
        {
            this->nextActionpoint();
            return;
        }  
        Vector3 targetAbsolutePosition = ((this->getProtect()->getWorldPosition()) + 
            (this->getProtect()->getWorldOrientation()* (targetRelativePosition)));
        this->setTargetPosition(targetAbsolutePosition);
        if (!this->getProtect())
        {
            this->nextActionpoint();
            return;
        }  
        this->setTargetOrientation(this->getProtect()->getWorldOrientation());
    }
    //remove current point from the stack
    void ActionpointController::nextActionpoint()
    {
        if (this->bLoop_)
        {
            if (this->bPatrolling_)
            {
                this->loopActionpoints_.pop_back();
                this->bPatrolling_ = false;
            }
            else if (!this->loopActionpoints_.empty())
            {
                this->moveBackToTop();
            }
        }
        else
        {
            if (!this->parsedActionpoints_.empty())
            {
                this->parsedActionpoints_.pop_back();
            }            
        }
        this->setAction(Action::NONE);
        this->bHasTargetPosition_ = false;
    }
    //if looping, instead of erasing point, move it to the top (back is what gets executed, so it's kinda reversed stack)
    void ActionpointController::moveBackToTop()
    {
        if (!this->getControllableEntity())
            return;

        Point temp = loopActionpoints_.back();
        loopActionpoints_.pop_back();
        std::reverse (loopActionpoints_.begin(), loopActionpoints_.end());
        loopActionpoints_.push_back(temp);
        std::reverse (loopActionpoints_.begin(), loopActionpoints_.end());
    }
    //POST: moves all consecutive points that are in loop to the loop stack
    void ActionpointController::fillLoop()
    {
        loopActionpoints_.clear();
        fillLoopReversed();
        std::reverse (loopActionpoints_.begin(), loopActionpoints_.end());
    }
    void ActionpointController::fillLoopReversed()
    {
        if (parsedActionpoints_.back().inLoop)
        {
            loopActionpoints_.push_back(parsedActionpoints_.back());
            parsedActionpoints_.pop_back();
        }
        if (parsedActionpoints_.back().inLoop)
        {
            fillLoopReversed();
        }
    }
    //copy other ship's stacks so that if it dies, this can finish that ship's actions
    void ActionpointController::takeActionpoints (const std::vector<Point>& vector, const std::vector<Point>& loop, bool b)
    {
        if (!this->getControllableEntity())
            return;
        this->parsedActionpoints_ = vector;
        this->loopActionpoints_ = loop;
        this->bLoop_ = b;
        this->bTakenOver_ = true;
    }
    //attack closest target
    void ActionpointController::setClosestTarget()
    {
        this->setTarget (static_cast<ControllableEntity*>( closestTarget() ) ); 
    }
    //find closest target
    Pawn* ActionpointController::closestTarget()
    {
        if (!this->getControllableEntity())
            return nullptr;

        Pawn* closestTarget = nullptr;
        float minDistance =  std::numeric_limits<float>::infinity();
        Gametype* gt = this->getGametype();
        for (Pawn* pawn : ObjectList<Pawn>())
        {
            if ( CommonController::sameTeam (this->getControllableEntity(), static_cast<ControllableEntity*>(pawn), gt) )
                continue;

            float distance = CommonController::distance (pawn, this->getControllableEntity());
            if (distance < minDistance)
            {
                closestTarget = pawn;
                minDistance = distance;
            }
        }
        if (closestTarget)
        {
           return closestTarget;
        } 
        return nullptr;
    }
    //push action FIGHT to the stack and set target to the closest enemy
    void ActionpointController::startAttackingEnemiesThatAreClose()
    {
        if (!this->getControllableEntity())
            return;
        
        if (!this->target_ || (this->target_ && CommonController::distance (this->getControllableEntity(), this->target_) > this->attackRange_))
        {
            Pawn* newTarget = this->closestTarget();
            if ( newTarget && 
                CommonController::distance (this->getControllableEntity(), static_cast<ControllableEntity*>(newTarget))
                    <= this->attackRange_ )
            {
                this->setTarget(newTarget);
                if (this->bLoop_ && !this->bPatrolling_)
                {
                    Point p = { Action::FIGHT, "", Vector3::ZERO, true };
                    this->loopActionpoints_.push_back(p);
                }
                else if (!this->bPatrolling_)
                {
                    Point p = { Action::FIGHT, "", Vector3::ZERO, false };
                    this->parsedActionpoints_.push_back(p);
                }
                this->bPatrolling_ = true;
                this->executeActionpoint();
            }
        }
    }
}   
