/*
 *   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:
 *      Sandro Sgier
 *   Co-authors:
 *      ...
 *
 */


/**
    @file ShrinkPickup.cc
    @brief Implementation of the HealthPickup class.
*/


#include "ShrinkPickup.h"

#include <sstream>
#include "core/CoreIncludes.h"
#include "core/XMLPort.h"

#include "worldentities/pawns/Pawn.h"

#include "worldentities/CameraPosition.h"

namespace orxonox
{
    RegisterClass(ShrinkPickup);

    /**
    @brief
        Constructor: Initializes the Pickup.
    */
    ShrinkPickup::ShrinkPickup(Context* context) : Pickup(context)
    {
        RegisterObject(ShrinkPickup);

        this->initialize();
    }

    ShrinkPickup::~ShrinkPickup()
    {

    }

    void ShrinkPickup::initialize(void)
    {
        this->addTarget(ClassIdentifier<Pawn>::getIdentifier());

        this->shrinkFactor_ = 5.0f;
        this->shrinkDuration_ = 5.0f;
        this->duration_ = 5.0f;

        this->isActive_ = false;
        this->isShrinking_ = false;
        this->isTerminating_ = false;

        this->timeRemainig_ = 0.0f;
        this->currentFactor_ = 1.0f;
    }

   /**
    @brief
        Method for creating a ShrinkPickup object through XML.
    */
    void ShrinkPickup::XMLPort(Element& xmlelement, orxonox::XMLPort::Mode mode)
    {
        SUPER(ShrinkPickup, XMLPort, xmlelement, mode);

        XMLPortParam(ShrinkPickup, "shrinkFactor", setShrinkFactor, getShrinkFactor, xmlelement, mode);
        XMLPortParam(ShrinkPickup, "duration", setDuration, getDuration, xmlelement, mode);
        XMLPortParam(ShrinkPickup, "shrinkDuration", setShrinkDuration, getShrinkDuration, xmlelement, mode);
    }

    /**
    @brief Sets the shrinking factor.
    @param factor The factor, needs to greater than 1.
    */
    void ShrinkPickup::setShrinkFactor(float factor)
    {
        if(factor <= 1.0f)
        {
            orxout(internal_warning, context::pickups) << "Invalid shrinking factor in ShrinkPickup. Ignoring.." << endl;
            return;
        }
        this->shrinkFactor_ = factor;
    }

    /**
    @brief Set the duration for which the ship remains shrunken.
    @param duration The duration, needs to be non-negative.
    */
    void ShrinkPickup::setDuration(float duration)
    {
        if(duration < 0.0f)
        {
            orxout(internal_warning, context::pickups) << "Invalid duration in ShrinkPickup. Ignoring.." << endl;
            return;
        }
        this->duration_ = duration;
    }

    /**
    @brief Set the shrink duration.
    @param speed The shrink duration, needs to be positive.
    */
    void ShrinkPickup::setShrinkDuration(float speed)
    {
        if(speed <= 0.0f)
        {
            orxout(internal_warning, context::pickups) << "Invalid shrink duration in ShrinkPickup. Ignoring.." << endl;
            return;
        }
        this->shrinkDuration_ = speed;
    }

    /**
    @brief
        Prepares for shrinking.
    */
    void ShrinkPickup::changedUsed(void)
    {
        SUPER(ShrinkPickup, changedUsed);

        if(this->isUsed())
        {
            Pawn* pawn = this->carrierToPawnHelper();
            if(pawn == nullptr) // If the PickupCarrier is no Pawn, then this pickup is useless and therefore is destroyed.
            {
                this->Pickupable::destroy();
                return;
            }

            this->currentFactor_ = 1.0f;
            this->timeRemainig_ = this->shrinkDuration_;

            this->isActive_ = true; // Start shrinking now.
            this->isShrinking_ = true;
            this->durationTimer_.setTimer(this->duration_, false, createExecutor(createFunctor(&ShrinkPickup::terminate, this))); //Set timer for termination.
        }
        if(!this->isUsed() && this->isActive_)
             this->isTerminating_ = true;
    }

    void ShrinkPickup::changedPickedUp(void)
    {
        SUPER(ShrinkPickup, changedPickedUp);

        if(!this->isPickedUp() && this->isActive_)
        {
            if(this->isShrinking_ || this->isTerminating_)
            {
                //TODO: Deploy particle effect.
                Pawn* pawn = this->carrierToPawnHelper();
                if(pawn == nullptr) // If the PickupCarrier is no Pawn, then this pickup is useless and therefore is destroyed.
                    return;

                float factor = 1.0f/this->currentFactor_;

                pawn->setScale3D(pawn->getScale3D()*factor);
                pawn->setMass(pawn->getMass()*factor);

                // Iterate over all camera positions and inversely move the camera to create a shrinking sensation.
                const std::list<StrongPtr<CameraPosition>>& cameraPositions = pawn->getCameraPositions();
                int size = cameraPositions.size();
                for(int index = 0; index < size; index++)
                {
                    CameraPosition* cameraPos = pawn->getCameraPosition(index);
                    if(cameraPos == nullptr)
                        continue;
                    cameraPos->setPosition(cameraPos->getPosition()/factor);
                }
                this->currentFactor_ = 1.0f;
                this->timeRemainig_ = this->shrinkDuration_;
                this->isActive_ = false;
                this->isShrinking_ = false;
                this->isTerminating_ = false;
            }
            else
            {
                //TODO: Deploy particle effect.
                Pawn* pawn = this->carrierToPawnHelper();
                if(pawn == nullptr) // If the PickupCarrier is no Pawn, then this pickup is useless and therefore is destroyed.
                    return;

                pawn->setScale3D(pawn->getScale3D()*this->shrinkFactor_);
                pawn->setMass(pawn->getMass()*this->shrinkFactor_);

                // Iterate over all camera positions and inversely move the camera to create a shrinking sensation.
                const std::list<StrongPtr<CameraPosition>>& cameraPositions = pawn->getCameraPositions();
                int size = cameraPositions.size();
                for(int index = 0; index < size; index++)
                {
                    CameraPosition* cameraPos = pawn->getCameraPosition(index);
                    if(cameraPos == nullptr)
                        continue;
                    cameraPos->setPosition(cameraPos->getPosition()/this->shrinkFactor_);
                }
                this->currentFactor_ = 1.0f;
                this->timeRemainig_ = this->shrinkDuration_;
                this->isActive_ = false;
            }
        }
    }

    /**
    @brief
        Updates the scales of the ship.
    @param dt
        Time since last call.
    */
    void ShrinkPickup::tick(float dt)
    {
        if(this->isActive_)
        {
            if(this->isShrinking_)    // If the ship has not reached the target scale, continue shrinking
            {
                Pawn* pawn = this->carrierToPawnHelper();
                if(pawn == nullptr) // If the PickupCarrier is no Pawn, then this pickup is useless and therefore is destroyed.
                {
                    this->Pickupable::destroy();
                    return;
                }

                this->timeRemainig_ -= dt;

                // Calculate the scaling factor by which the initial size would have to be scaled to have the current scale.
                float currentFactor = std::max(1 - (1-std::max(this->timeRemainig_, 0.0f)/this->shrinkDuration_)*(1-1/this->shrinkFactor_), 1/this->shrinkFactor_);
                // Calculate the factor by which the previous size has to be scaled to be the current scale.
                float factor = currentFactor/this->currentFactor_;
                this->currentFactor_ = currentFactor;

                // Stop shrinking if the desired size is reached.
                if(this->timeRemainig_ <= 0.0f)
                {
                    this->timeRemainig_ = this->shrinkDuration_; // Reset the time remaining for when we start to grow the ship again.
                    this->currentFactor_ = 1/this->shrinkFactor_;
                    this->isShrinking_ = false;
                }

                pawn->setScale3D(pawn->getScale3D()*factor);
                pawn->setMass(pawn->getMass()*factor);

                // Iterate over all camera positions and inversely move the camera to create a shrinking sensation.
                const std::list<StrongPtr<CameraPosition>>& cameraPositions = pawn->getCameraPositions();
                int size = cameraPositions.size();
                for(int index = 0; index < size; index++)
                {
                    CameraPosition* cameraPos = pawn->getCameraPosition(index);
                    if(cameraPos == nullptr)
                        continue;
                    cameraPos->setPosition(cameraPos->getPosition()/factor);
                }

            }
            else if(this->isTerminating_)    // Grow until the ship reaches its default scale.
            {
                Pawn* pawn = this->carrierToPawnHelper();
                if (pawn == nullptr) // If the PickupCarrier is no Pawn, then this pickup is useless and therefore is destroyed.
                {
                    this->Pickupable::destroy();
                    return;
                }

                this->timeRemainig_ -= dt;

                // Calculate the scaling factor by which the initial size would have to be scaled to have the current scale.
                float currentFactor = std::min(1/this->shrinkFactor_ + (1-std::max(this->timeRemainig_, 0.0f)/this->shrinkDuration_)*(1-1/this->shrinkFactor_), 1.0f);
                // Calculate the factor by which the previous size has to be scaled to be the current scale.
                float factor = currentFactor/this->currentFactor_;
                this->currentFactor_ = currentFactor;

                bool destroy = false;

                // Stop shrinking if the desired size is reached.
                if(this->timeRemainig_ <= 0.0f)
                {
                    this->timeRemainig_ = shrinkDuration_; // Reset the time remaining for when we start to grow the ship again.
                    this->currentFactor_ = 1.0f;
                    this->isTerminating_ = false;
                    this->isActive_ = false;
                    destroy = true;
                }

                pawn->setScale3D(pawn->getScale3D()*factor);
                pawn->setMass(pawn->getMass()*factor);

                // Iterate over all camera positions and inversely move the camera to create a shrinking sensation.
                const std::list<StrongPtr<CameraPosition>>& cameraPositions = pawn->getCameraPositions();
                int size = cameraPositions.size();
                for(int index = 0; index < size; index++)
                {
                    CameraPosition* cameraPos = pawn->getCameraPosition(index);
                    if(cameraPos == nullptr)
                        continue;
                    cameraPos->setPosition(cameraPos->getPosition()/factor);
                }

                if(destroy)
                    this->Pickupable::destroy();
            }
        }
    }

    /**
    @brief
        Initializes the termination.
    */
    void ShrinkPickup::terminate(void)
    {
        this->setUsed(false);
    }

    Pawn* ShrinkPickup::carrierToPawnHelper(void)
    {
        PickupCarrier* carrier = this->getCarrier();
        Pawn* pawn = orxonox_cast<Pawn*>(carrier);

        return pawn;
    }
}
