/*Copyright (C) 2010-2023 Duncan Deveaux

This file is part of Hikou no mizu.

Hikou no mizu 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 3 of the License, or
(at your option) any later version.

Hikou no mizu 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 Hikou no mizu.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "PhysicsRoutines.hpp"
#include "PhysicsObject.hpp"

#include <algorithm>

namespace PhysicsRoutines
{
    void soustractVectors(std::vector<PhysicsObject*> &dst,
                          const std::vector<PhysicsObject*> &src)
    {
        std::vector<PhysicsObject*>::iterator it = dst.begin();

        while (it != dst.end())
        {
            if (std::find(src.begin(), src.end(), (*it)) != src.end())
                it = dst.erase(it);
            else
                ++it;
        }
    }

    void sortObjectsByX(std::vector<PhysicsObject*> &objects, int checkedSide)
    {
        std::sort(objects.begin(), objects.end(), SortByXPos(checkedSide));
    }

    void sortObjectsByY(std::vector<PhysicsObject*> &objects, int checkedSide)
    {
        std::sort(objects.begin(), objects.end(), SortByYPos(checkedSide));
    }

    bool containsFixedObject(const std::vector<PhysicsObject*> &objects)
    {
        return (std::find_if(objects.begin(), objects.end(), FindFixed()) !=
                objects.end());
    }

    ParabolaStep computeParabolaStep(float velocity,
                                     float acceleration, float timeDelta)
    {
        ParabolaStep update;
        update.velocityDelta = acceleration * timeDelta;
        update.positionDelta = acceleration * timeDelta * timeDelta / 2.f +
                               velocity * timeDelta;
        return update;
    }

    ////////////////
    ///SortByXPos///
    ////////////////
    SortByXPos::SortByXPos(int checkedSide) : side(checkedSide)
    {

    }

    bool SortByXPos::operator() (const PhysicsObject *lhs,
                                 const PhysicsObject *rhs) const
    {
        const Box &lhsBox = lhs->getBox();
        const Box &rhsBox = rhs->getBox();

        if (side == 0)
            return lhsBox.left < rhsBox.left;
        else
            return lhsBox.left + lhsBox.width < rhsBox.left + rhsBox.width;
    }


    ////////////////
    ///SortByYPos///
    ////////////////
    SortByYPos::SortByYPos(int checkedSide) : side(checkedSide)
    {

    }

    bool SortByYPos::operator() (const PhysicsObject *lhs,
                                 const PhysicsObject *rhs) const
    {
        const Box &lhsBox = lhs->getBox();
        const Box &rhsBox = rhs->getBox();

        if (side == 0)
            return lhsBox.top < rhsBox.top;
        else
            return lhsBox.top + lhsBox.height < rhsBox.top + rhsBox.height;
    }

    ///////////////
    ///FindFixed///
    ///////////////
    bool FindFixed::operator() (const PhysicsObject *obj) const
    {
        return obj->isFixed();
    }


    ///////////
    ///Box1D///
    ///////////
    BoxView1D::BoxView1D(Axis viewAxis) : axis(viewAxis) {}

    float BoxView1D::position(const Box &box) const
    {
        return (axis == Axis::Horizontal) ? box.left : box.top;
    }

    float BoxView1D::size(const Box &box) const
    {
        return (axis == Axis::Horizontal) ? box.width : box.height;
    }

    float BoxView1D::bound(const Box &box) const
    {
        return (axis == Axis::Horizontal) ? box.left + box.width
                                          : box.top + box.height;
    }

    Box BoxView1D::shrink(const Box &box, float amount) const
    {
        return (axis == Axis::Horizontal) ?
            Box(box.left+amount, box.top, box.width-amount*2.f, box.height) :
            Box(box.left, box.top+amount, box.width, box.height-amount*2.f);
    }

    void BoxView1D::setPosition(Box &box, float position) const
    {
        if (axis == Axis::Horizontal)
            box.left = position;
        else if (axis == Axis::Vertical)
            box.top = position;
    }

    void BoxView1D::setSize(Box &box, float size) const
    {
        if (axis == Axis::Horizontal)
            box.width = size;
        else if (axis == Axis::Vertical)
            box.height = size;
    }
}
