/*Copyright (C) 2010 Deveaux Duncan

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 "PhysicsEngine.hpp"

PhysicsEngine::PhysicsEngine()
{
    
}

PhysicsEngine::PhysicsEngine(const sf::FloatRect &physicsWorld, int ground, int gravity) :
m_physicsWorld(physicsWorld), m_ground(ground), m_gravity(gravity)
{
    
}

void PhysicsEngine::addObject(PhysicsObject *object)
{
    m_physicsObjectsList.push_back(object);
}

void PhysicsEngine::removeObject(PhysicsObject *object)
{
    //Search for the object
    std::vector<PhysicsObject*>::iterator it;
    it = std::find(m_physicsObjectsList.begin(), m_physicsObjectsList.end(), object);
    
    //If find, destroy it
    if (it != m_physicsObjectsList.end())
        m_physicsObjectsList.erase(it);
}

void PhysicsEngine::update()
{
    for (int i = 0; i < m_physicsObjectsList.size(); i++)
    {
        //Gravity
        if (!isOnGround(m_physicsObjectsList[i]))
        {
            float newY = m_physicsObjectsList[i]->getYPosition() + m_gravity;
            if (newY > m_ground) //The object landed
                newY = m_ground;
            
            moveObjectY(m_physicsObjectsList[i], newY);
        }
    }
}

void PhysicsEngine::moveObjectX(PhysicsObject *objectToMove, float xPosition)
{
    //Save old position
    float oldX = objectToMove->getXPosition();
    
    //Move the object
    objectToMove->setXPosition(xPosition);
    
    //If the object hit another one, set back its position
    if (objectHitTest(objectToMove) != NULL || !worldHitTest(objectToMove))
        objectToMove->setXPosition(oldX);
}

void PhysicsEngine::moveObjectY(PhysicsObject *objectToMove, float yPosition)
{
    //Save old position
    float oldY = objectToMove->getYPosition();
    
    //Move the object
    objectToMove->setYPosition(yPosition);
    
    //If the object hit another one, set back its position
    if (objectHitTest(objectToMove) != NULL || !worldHitTest(objectToMove))
        objectToMove->setYPosition(oldY);
}

PhysicsObject *PhysicsEngine::objectHitTest(PhysicsObject *objectToTest)
{
    //Position using the object's top-left corner
    float xPosition = objectToTest->getXPosition() - objectToTest->getOrigin().x;
    float yPosition = objectToTest->getYPosition() - objectToTest->getOrigin().y;
    float width = objectToTest->getWidth();
    float height = objectToTest->getHeight();
    
    for (int i = 0; i < m_physicsObjectsList.size(); i++)
    {
        if (objectToTest != m_physicsObjectsList[i])
        {
            float testedXPosition = m_physicsObjectsList[i]->getXPosition() - m_physicsObjectsList[i]->getOrigin().x;
            float testedYPosition = m_physicsObjectsList[i]->getYPosition() - m_physicsObjectsList[i]->getOrigin().y;
            float testedWidth = m_physicsObjectsList[i]->getWidth();
            float testedHeight = m_physicsObjectsList[i]->getHeight();
            
            //Hit test condition
            if ((xPosition + width >= testedXPosition && xPosition <= testedXPosition + testedWidth) && (yPosition + height >= testedYPosition && yPosition <= testedYPosition + testedHeight))
                return m_physicsObjectsList[i];
        }
    }
    
    return NULL;
}

bool PhysicsEngine::worldHitTest(PhysicsObject *objectToTest)
{
    //Object's top-left corner
    float x1 = objectToTest->getXPosition() - objectToTest->getOrigin().x;
    float y1 = objectToTest->getYPosition() - objectToTest->getOrigin().y;
    
    //Object's bottom-right corner
    float x2 = x1 + objectToTest->getWidth();
    float y2 = y1 + objectToTest->getHeight();
    
    //Hit test condition
    if (m_physicsWorld.Contains(x1, y1) && m_physicsWorld.Contains(x2, y2))
        return true;
    
    return false;
}

bool PhysicsEngine::isOnGround(PhysicsObject *objectToTest)
{
    float yPosition = objectToTest->getYPosition();
    if (yPosition < m_ground) // The object doesn't touch the ground
    {
        objectToTest->setYPosition(yPosition + m_gravity);
        PhysicsObject *hitObject = objectHitTest(objectToTest);
        
        objectToTest->setYPosition(yPosition);
        if (hitObject == NULL) //The object doesn't touch another one
            return false;
    }
    
    return true;
}

//Get and set methods
void PhysicsEngine::setPhysicsWorld(const sf::FloatRect &physicsWorld)
{
    m_physicsWorld = physicsWorld;
}

const sf::FloatRect &PhysicsEngine::getPhysicsWorld() const
{
    return m_physicsWorld;
}

void PhysicsEngine::setGroundPosition(int ground)
{
    m_ground = ground;
}

int PhysicsEngine::getGoundPosition() const
{
    return m_ground;
}

void PhysicsEngine::setGravityForce(int gravity)
{
    m_gravity = gravity;
}

int PhysicsEngine::getGravityForce() const
{
    return m_gravity;
}