#include <App/Humanoid/Humanoid.hpp>
#include <App/V8/World/World.hpp>
#include <App/InputManager.hpp>

namespace RNR
{
    Humanoid::Humanoid()
    {
        setName("Humanoid");

        m_maxHealth = 100.f;
        m_health = 100.f;
    }

    Humanoid::~Humanoid()
    {
        //
    }

    bool Humanoid::canSit()
    {
        return true;
    }

    void Humanoid::buildJoints()
    {
        //
    }

    void Humanoid::checkForJointDeath()
    {
        //
    }

    void Humanoid::computeForce(float force, bool throttling)
    {
        //
    }

    void Humanoid::createHealthBar()
    {
        if(!getHead())
        {
            printf("Humanoid::createHealthBar: no head\n");
            return;
        }
        if(getNode())
            world->getOgreSceneManager()->destroySceneNode(getNode());

        setNode(world->getOgreSceneManager()->getRootSceneNode()->createChildSceneNode());
        Ogre::BillboardSet* healthBarSet = world->getOgreSceneManager()->createBillboardSet("HumanoidHealth" + getParent()->getName());
        healthBarSet->setBillboardType(Ogre::BillboardType::BBT_PERPENDICULAR_COMMON);
        healthBarSet->setDebugDisplayEnabled(true);
        //Ogre::Billboard* healthBarBillboard = healthBarSet->createBillboard(Ogre::Vector3(0, 2, 0), Ogre::ColourValue(1, 0, 0, 1));
        Ogre::Billboard* healthBarBillboardFilled = healthBarSet->createBillboard(Ogre::Vector3(0, 2, 0), Ogre::ColourValue(0, 1, 0, 1));
        float healthBarScale = 0.5f;
        //healthBarBillboard->setDimensions(5 * healthBarScale, 1 * healthBarScale);
        healthBarBillboardFilled->setDimensions((m_health / m_maxHealth) * 5.f * healthBarScale, 1 * healthBarScale);
        healthBarSet->setCastShadows(true);

        Ogre::BillboardSet* nameBarSet = world->getOgreSceneManager()->createBillboardSet("HumanoidName" + getParent()->getName());        
        nameBarSet->setDebugDisplayEnabled(true);
        Ogre::FontPtr comic = Ogre::FontManager::getSingletonPtr()->getByName("ComicSans");
        if(!comic)
            printf("Humanoid::createHealthBar: comic == NULL\n");
        comic->putText(nameBarSet, getParent()->getName(), 1);
        int num_billboards = nameBarSet->getNumBillboards();
        for(int i = 0; i < num_billboards; i++)
        {
            Ogre::Billboard* chara = nameBarSet->getBillboard(i);
            Ogre::Vector3 chara_pos = chara->getPosition();
            chara_pos.y += 2 + healthBarScale;
            chara->setPosition(chara_pos);
        }
        getNode()->attachObject(healthBarSet);
        getNode()->attachObject(nameBarSet);
        getNode()->setPosition(getHead()->getPosition());
        getNode()->setOrientation(getHead()->getRotation());

        printf("Humanoid::createHealthBar: WIP\n");
    }

    void Humanoid::deserializeProperty(char* prop_name, pugi::xml_node prop)
    {
        if(prop_name == std::string("Health"))
        {
            setHealth(prop.text().as_float());
        }
        else if(prop_name == std::string("MaxHealth"))
        {
            setMaxHealth(prop.text().as_float());
        }
    }

    void Humanoid::inputFrame(float dx, float dy)
    {
        IInputManager* inputManager = world->getInputManager();
        PartInstance* torso = getTorso();
        Camera* camera = world->getWorkspace()->getCurrentCamera();
        if(!torso)
        {
            printf("Humanoid::inputFrame: no torso\n");
            return;
        }
        if(inputManager)
        {
            Ogre::Matrix3 camera_rotation;
            camera_rotation.FromEulerAnglesYXZ(camera->getYaw(), Ogre::Radian(0), Ogre::Radian(0)); // we only want yaw because otherwise the movement target will go through the ground/in the air
            Ogre::Quaternion direction = torso->getRotation();
            Ogre::Quaternion new_direction = Ogre::Quaternion::nlerp(0.5f, direction, camera_rotation);
            float forward = 0;

            if(inputManager->isKeyDown('W'))
                forward = 16;

            Ogre::Vector3 move = Ogre::Vector3(0, 0, -forward);
            move = direction * move;
            move *= world->getLastDelta();

            bool move_valid = true;
    
            // TODO: collision checking

            if(!move_valid)
                return;

            for(auto& child : *getParent()->getChildren())
            {
                PartInstance* bp = dynamic_cast<PartInstance*>(child);
                if(bp)
                {
                    Ogre::Vector3 bp_p = bp->getPosition();
                    bp_p += move;
                    bp->getCFrame().setPosition(bp_p);
                    Ogre::Matrix3 new_rotation_matrix;
                    new_direction.ToRotationMatrix(new_rotation_matrix);
                    bp->getCFrame().setRotation(new_rotation_matrix);
                    bp->updateMatrix();
                    world->getWorkspace()->setDirty();
                }
            }

            camera->getCFrame().setPosition(camera->getCFrame().getPosition() + move);
        }
    }

    void Humanoid::addProperties(std::vector<ReflectionProperty>& properties)
    {
        ReflectionProperty _properties[]  = {
            { this, std::string("Health"), std::string(""), 
              ACCESS_NONE, OPERATION_READWRITE, PROPERTY_FLOAT,         
              REFLECTION_GETTER(Humanoid* instance = (Humanoid*)object; return &instance->m_health; ), 
              REFLECTION_SETTER(Humanoid* instance = (Humanoid*)object; instance->setHealth(*(float*)value); ) },
            { this, std::string("MaxHealth"), std::string(""), 
              ACCESS_NONE, OPERATION_READWRITE, PROPERTY_FLOAT,         
              REFLECTION_GETTER(Humanoid* instance = (Humanoid*)object; return &instance->m_maxHealth; ), 
              REFLECTION_SETTER(Humanoid* instance = (Humanoid*)object; instance->setMaxHealth(*(float*)value); ) },
        };

        properties.insert(properties.end(), _properties, _properties+(sizeof(_properties)/sizeof(ReflectionProperty)));
    }

    PartInstance* Humanoid::getTorso()
    {
        return (PartInstance*)getParent()->findFirstChild("Torso");
    }

    PartInstance* Humanoid::getHead()
    {
        return (PartInstance*)getParent()->findFirstChild("Head");
    }
}