/*Copyright (C) 2010-2020 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 "Player.hpp"

Player::Player() :
PhysicsObject(), m_health(100), m_strength(0), m_speed(0), m_jumpEnergy(0), m_walkPeriod(0),
m_soundEngine(NULL), m_walksound(WALKSOUND_NOT_STARTED), m_weapons(NULL), m_debugHits(false)
{
    
}

Player::Player(const std::string &name, float health, float strength, float speed, float jumpEnergy, float walkPeriod) :
PhysicsObject(), m_name(name), m_health(health), m_strength(strength), m_speed(speed), m_jumpEnergy(jumpEnergy), m_walkPeriod(walkPeriod),
m_soundEngine(NULL), m_walksound(WALKSOUND_NOT_STARTED), m_weapons(NULL), m_debugHits(false)
{
    
}

void Player::initWeapons(WeaponMedium &medium)
{
    m_weapons = &medium;
}

void Player::loadGraphics(TextureManager &textureManager)
{
    std::string graphicsPath = "gfx/characters/" + m_name + "/";
    m_moves.load(graphicsPath + "data.xml", graphicsPath, textureManager, (*this));
    m_moves.update(0); //Initial update (origin, ...)
    
    //Init physics box
    setWidth(m_moves.getWidth());
    setHeight(m_moves.getHeight());
    
    //Init shadow
    float shadowRatio = 0.1;
    
    m_shadowSize.x = m_moves.getWidth()*1.1;
    m_shadowSize.y = m_shadowSize.x * shadowRatio;
    
    m_shadow = Polygon::circle(m_shadowSize.x/2.f);
    m_shadow.setBorderSize(0);
    m_shadow.setYScale(shadowRatio);
}

void Player::draw()
{
    if (!m_moves.getXFlip()) m_moves.setXPosition(m_box.left);
    else m_moves.setXPosition(m_box.left + m_box.width);
    
    m_moves.setYPosition(m_box.top + m_box.height);
    
    float shadowDist = shadowProjection();
    if (shadowDist < 0.f) shadowDist = 0.f;
    
    m_shadow.setPosition( m_box.left - (m_shadowSize.x - m_box.width) / 2.f,
                          m_box.top + m_box.height + shadowDist - m_shadowSize.y / 2.f );
    m_shadow.setColor( Color( 0, 0, 0, 90 / (shadowDist/50 + 1.f) ) );
    
    m_shadow.draw();
    m_moves.draw();
}

void Player::update(float frameTime)
{
    setWidth(m_moves.getWidth(), m_moves.getXFlip());
    setHeight(m_moves.getHeight(), true);
    
    //Move
    if (m_moves.getState() == STATE_MOVING)
    {
        if (m_soundEngine != NULL && m_walkPeriod > 0 && m_walksound != WALKSOUND_ERROR)
        {
            if (isOnGround() && m_walksound == WALKSOUND_NOT_STARTED) //No sound started
            {
                unsigned int loopIx = 0;
                if ( m_soundEngine->loopSound("sound/characters/" + m_name + "/walk.ogg", m_walkPeriod, loopIx) )
                    m_walksound = static_cast<int>( loopIx );
                else
                {
                    //The walk sound failed to load, try again on next game ;)
                    m_walksound = WALKSOUND_ERROR;
                }
            }
        }
        
        move(frameTime);
    }
    
    //Walking sound should be stopped
    if ( (m_moves.getState() != STATE_MOVING || !isOnGround()) && m_walksound >= 0)
    {
        m_soundEngine->stopLoop( static_cast<unsigned int>(m_walksound) );
        m_walksound = WALKSOUND_NOT_STARTED;
    }
}

void Player::updateGraphics(float frameTime)
{
    m_moves.update(frameTime);
}

void Player::move(float frameTime)
{
    if (!isKo() && !isHit()) //Move
    {
        float elapsedTime = static_cast<float>(frameTime) / 1000;
        
        if (m_moves.getXFlip()) moveXPosition(-m_speed * elapsedTime);
        else moveXPosition(m_speed * elapsedTime);
    }
}

void Player::jump()
{
    if (m_physicsWorld != NULL && (!isKo() && !isHit() && m_moves.getState() != STATE_CROUCHED && isOnGround()))
        m_physicsWorld->pulseObject((*this), 0, -m_jumpEnergy);
}

void Player::crouch()
{
    if (!isKo() && !isHit() && isOnGround())
        m_moves.setState(STATE_CROUCHED);
}

void Player::punch()
{
    if (!isKo() && !isHit() && m_moves.mayAttack())
    {
        //Launch punch sound
        if (m_soundEngine != NULL)
            m_soundEngine->playSound("sound/characters/" + m_name + "/punchDefault.ogg");
        
        m_moves.punch();
    }
}

void Player::kick()
{
    if (!isKo() && !isHit() && m_moves.mayAttack())
    {
        //Launch kick sound
        if (m_soundEngine != NULL)
        {
            if (m_moves.getState() == STATE_CROUCHED)
                m_soundEngine->playSound("sound/characters/" + m_name + "/kickCrouched.ogg");
            else
                m_soundEngine->playSound("sound/characters/" + m_name + "/kickDefault.ogg");
        }
        
        m_moves.kick();
    }
}

void Player::throwShuriken()
{
    if (m_weapons != NULL && m_physicsWorld != NULL &&
        !isKo() && !isHit() && m_moves.mayAttack())
    {
        m_weapons->throwShuriken((*m_physicsWorld), (*this));
    }
}

void Player::collideWorld()
{
    //Nothing to do :)
}

void Player::collide(PhysicsObject &obj)
{
    std::cout << &obj << " hit by " << this << std::endl;
}

void Player::takeAHit(float strength, int side, const PhysicsObject* /*source*/)
{
    //Alter life
    int remainingLives = m_health - strength;
    setHealth(remainingLives);
    
    //Pulse
    float pulse = 40 * strength;
    if (side == 1) pulse *= -1;
    
    if (m_physicsWorld != NULL)
        m_physicsWorld->pulseObject((*this), pulse, 0);
    
    //Launch hit sound
    if (m_soundEngine != NULL)
        m_soundEngine->playSound("sound/characters/" + m_name + "/takeAHit.ogg");
     
    //Launch KO animation
    if (!isKo() && remainingLives <= 0)
        m_moves.setState(STATE_FALLING, STATE_KO, true);
    
    //Or launch hit animation
    if (!isKo() && !isHit())
    {
        if (m_moves.getState() == STATE_CROUCHED)
            m_moves.setState(STATE_HIT_CROUCHED, STATE_CROUCHED, true);
        else
            m_moves.setState(STATE_HIT_DEFAULT, STATE_DEFAULT, true);
    }
        
}

bool Player::isKo() const
{
    return (m_moves.getState() == STATE_FALLING || m_moves.getState() == STATE_KO);
}

bool Player::isHit() const
{
    return (m_moves.getState() == STATE_HIT_DEFAULT || m_moves.getState() == STATE_HIT_CROUCHED);
}

//Attacks callback
void Player::hit(const HitBox &hitBox)
{
    //Define the hit box
    Box relativeBox = hitBox.box;
    relativeBox.left = m_box.left + relativeBox.left;
    relativeBox.top = m_box.top + relativeBox.top;
    
    if (m_moves.getXFlip())
        relativeBox.left = m_box.left - ((relativeBox.left + relativeBox.width) - (m_box.left + m_box.width));
    
    //Draw a red square around it (if needed)
    if (m_debugHits)
    {
        Polygon debug = Polygon::rectangle(relativeBox.left,
                                            relativeBox.top,
                                            relativeBox.width,
                                            relativeBox.height,
                                            Color(0, 0, 0, 0),
                                            Color(225, 0, 0));
        debug.setBorderSize(4);
        debug.draw();
    }
    
    //Hit objects
    std::vector<PhysicsObject*> hitObjects = hitTest(relativeBox);
    
    for (unsigned int i = 0; i < hitObjects.size(); i++) //Pum ! ;)
    {
        if (m_box.left < hitObjects[i]->getBox().left)
            hitObjects[i]->takeAHit(m_strength * hitBox.factor, 0, this);
        else
            hitObjects[i]->takeAHit(m_strength * hitBox.factor, 1, this);
    }
}

//Engines methods
void Player::setSoundEngine(SoundEngine &soundEngine)
{
    m_soundEngine = &soundEngine;
}

const SoundEngine &Player::getSoundEngine() const
{
    return *m_soundEngine;
}

//Get and set methods
const std::string &Player::getName() const
{
    return m_name;
}

void Player::setHealth(float health)
{
    if (health < 0)
        m_health = 0;
    
    else if (health > 100)
        m_health = 100;
    
    else
        m_health = health;
}

float Player::getHealth() const
{
    return m_health;
}

void Player::setStrength(float strength)
{
    if (strength >= 0)
        m_strength = strength;
}

float Player::getStrength() const
{
    return m_strength;
}

void Player::setSpeed(float speed)
{
    if (speed >= 0)
        m_speed = speed;
}

float Player::getSpeed() const
{
    return m_speed;
}

void Player::setJumpEnergy(float jumpEnergy)
{
    if (jumpEnergy >= 0)
        m_jumpEnergy = jumpEnergy;
}

float Player::getJumpEnergy() const
{
    return m_jumpEnergy;
}

void Player::setDebugHits(bool debugHits)
{
    m_debugHits = debugHits;
}

bool Player::getDebugHits() const
{
    return m_debugHits;
}

bool Player::getXFlip() const
{
    return m_moves.getXFlip();
}

void Player::getState(unsigned int &state, unsigned int &action) const
{
    state = m_moves.getState();
    action = m_moves.getAction();
}

bool Player::getWeapons(WeaponSlots &dest) const
{
    if (m_weapons != NULL)
        return m_weapons->getSlots((*this), dest);
    
    return false;
}
