/*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 "PlayerMoves.hpp"

PlayerMoves::PlayerMoves() : Drawable(), m_state(STATE_DEFAULT), m_action(ACTION_DEFAULT), m_targetState(-1),
m_lastAttackEndTime(-RECOVERY_TIME - 1.f)
{
    
}

void PlayerMoves::draw()
{
    //Update matrix
    Drawable::pushMatrix();
    Drawable::updateMatrix();
    
    if (m_action != ACTION_DEFAULT) m_statesList[m_state].attacks[m_action].draw();
    else m_statesList[m_state].animation.draw();
    
    Drawable::popMatrix();
}

void PlayerMoves::update(float frameTime)
{
    if (m_action != ACTION_DEFAULT) //Attack launched
    {
        m_statesList[m_state].attacks[m_action].update(frameTime);
        
        if (m_statesList[m_state].attacks[m_action].ready()) //finished
        {
            m_action = ACTION_DEFAULT;
            
            m_statesList[m_state].animation.goToTime(0);
            updateOrigin();
            
            m_lastAttackEndTime = Timer::getTicks();
            return;
        }
    }
    else
    {
        Animation &animation = m_statesList[m_state].animation;
        if (animation.getCurrentTime() == animation.getDuration())
        {
            animation.goToTime(0);
            
            if (m_targetState != -1)
            {
                setState(m_targetState);
                m_targetState = -1;
                
                return;
            }
        }
        
        animation.goForward(frameTime / 1000);
    }
    
    updateOrigin();
}

//Action
bool PlayerMoves::mayAttack() {
    
    return m_action == ACTION_DEFAULT && (Timer::getTicks() - m_lastAttackEndTime) >= RECOVERY_TIME;
}

void PlayerMoves::punch()
{
    if (mayAttack() && m_statesList[m_state].attacks.count(ACTION_PUNCH) == 1)
    {
        m_action = ACTION_PUNCH;
        updateOrigin();
    }
}

void PlayerMoves::kick()
{
    if (mayAttack() && m_statesList[m_state].attacks.count(ACTION_KICK) == 1)
    {
        m_action = ACTION_KICK;
        updateOrigin();
    }
}

//State
void PlayerMoves::setState(unsigned int state, bool force)
{
    if (state <= STATE_MAX)
    {
        //Forcing to change state
        //(basically in case of KO : current attacks must not interfere)
        if (force && m_action != ACTION_DEFAULT)
        {
            //Reinit current attack and properly go back to default action
            m_statesList[m_state].attacks[m_action].reinit();
            m_action = ACTION_DEFAULT;
        }
        
        if (m_action == ACTION_DEFAULT)
        {
            m_state = state;
            
            m_statesList[m_state].animation.goToTime(0);
            updateOrigin();
        }
    }
}

void PlayerMoves::setState(unsigned int state, unsigned int targetState, bool force)
{
    if (state <= STATE_MAX && targetState <= STATE_MAX && (m_action == ACTION_DEFAULT || force))
    {
        setState(state, force);
        m_targetState = targetState;
    }
}

unsigned int PlayerMoves::getAction() const
{
    return m_action;
}

unsigned int PlayerMoves::getState() const
{
    return m_state;
}

float PlayerMoves::getWidth()
{
    Box bodyBox;
    getAnimation().getBodyBox(bodyBox);
    
    return bodyBox.width;
}

float PlayerMoves::getHeight()
{
    Box bodyBox;
    getAnimation().getBodyBox(bodyBox);
    
    return bodyBox.height;
}

const Animation &PlayerMoves::getAnimation()
{
    if (m_action != ACTION_DEFAULT)
        return m_statesList[m_state].attacks[m_action].getAnimation();
    
    return m_statesList[m_state].animation;
}

void PlayerMoves::updateOrigin()
{
    Box bodyBox;
    getAnimation().getBodyBox(bodyBox);
    
    setXOrigin(bodyBox.left);
    setYOrigin(bodyBox.top + bodyBox.height);
}

void PlayerMoves::load(const std::string &xmlRelPath, const std::string &animationsPath, TextureManager &textureManager, Player &player)
{
    //TinyXML initialization
    std::string xmlPath = BuildValues::data(xmlRelPath); //Locate absolute path to xml data
    
    TiXmlDocument xmlFile;
    if (!xmlFile.LoadFile(xmlPath.c_str()))
        return;
    
    TiXmlHandle hdl(&xmlFile);
    hdl = hdl.FirstChildElement().FirstChildElement();
    
    TiXmlElement *xmlElement = hdl.FirstChildElement().ToElement();
    unsigned int nodeIx = 0;
    while (xmlElement)
    {
        State state;
        
        //Main animation
        TiXmlElement *xmlAnimation = xmlElement->FirstChildElement("animation");
        
        std::string animationPath = animationsPath + xmlAnimation->Attribute("path") + ".png";
        Texture *texture = textureManager.getTexture(animationPath);
        
        loadAnimation((*texture), xmlAnimation, state.animation);
        
        //Attacks
        TiXmlHandle attacksHdl = hdl.Child(nodeIx).FirstChildElement("attacks");
        TiXmlElement *xmlAttacks = attacksHdl.FirstChildElement().ToElement();
        
        unsigned int attackIx = 0;
        while (xmlAttacks)
        {
            Attack &attack = state.attacks[attackIx + 1];
            attack.setPlayer(player);
            
            //Animation
            TiXmlElement *xmlAttacks_animation = xmlAttacks->FirstChildElement("animation");
            
            Animation animation;
            
            std::string animationPath = animationsPath + xmlAttacks_animation->Attribute("path") + ".png";
            Texture *texture = textureManager.getTexture(animationPath);
            
            loadAnimation((*texture), xmlAttacks_animation, animation);
            attack.setAnimation(animation);
            
            //Hits
            TiXmlHandle hitsHdl = attacksHdl.Child(attackIx).FirstChildElement("hits");
            TiXmlElement *xmlAttacks_hits = hitsHdl.FirstChildElement().ToElement();
            
            while (xmlAttacks_hits)
            {
                //Box
                Box hitBox;
                float x = 0, y = 0, width = 0, height = 0;
                
                xmlAttacks_hits->QueryFloatAttribute("x", &x);
                xmlAttacks_hits->QueryFloatAttribute("y", &y);
                xmlAttacks_hits->QueryFloatAttribute("width", &width);
                xmlAttacks_hits->QueryFloatAttribute("height", &height);
                
                if (width == 0 || height == 0)
                    hitBox = Box(x - 5, y - 5, 10, 10);
                else
                    hitBox = Box(x, y, width, height);
                
                //Time & Strenght factor
                float hitTime = 0, hitFactor = 0;
                
                xmlAttacks_hits->QueryFloatAttribute("time", &hitTime);
                xmlAttacks_hits->QueryFloatAttribute("strenght", &hitFactor);
                
                attack.addHitBox( HitBox(hitBox, hitTime, hitFactor) );
                
                xmlAttacks_hits = xmlAttacks_hits->NextSiblingElement();
            }
            
            attackIx++;
            xmlAttacks = xmlAttacks->NextSiblingElement();
        }
        
        //Add state
        m_statesList[nodeIx] = state;
        
        nodeIx++;
        xmlElement = xmlElement->NextSiblingElement();
    }
}

void PlayerMoves::loadAnimation(Texture &texture, const TiXmlElement *xmlData, Animation &dst) const
{
    if (xmlData != NULL)
    {
        //Main params
        float duration = 0;
        xmlData->QueryFloatAttribute("duration", &duration);
        
        dst.setTexture(texture);
        dst.setDuration(duration);
        
        //Frames
        if (!xmlData->NoChildren())
        {
            const TiXmlElement *xmlFrames = xmlData->FirstChildElement();
            
            while (xmlFrames)
            {
                int srcX = 0, srcY = 0, srcWidth = 0, srcHeight = 0, time = 1;
                float bodyX = 0, bodyY = 0, bodyWidth = 0, bodyHeight = 0;
                
                xmlFrames->QueryIntAttribute("x", &srcX);
                xmlFrames->QueryIntAttribute("y", &srcY);
                
                xmlFrames->QueryIntAttribute("width", &srcWidth);
                xmlFrames->QueryIntAttribute("height", &srcHeight);
                
                xmlFrames->QueryFloatAttribute("bodyX", &bodyX);
                xmlFrames->QueryFloatAttribute("bodyY", &bodyY);
                
                xmlFrames->QueryFloatAttribute("bodyWidth", &bodyWidth);
                xmlFrames->QueryFloatAttribute("bodyHeight", &bodyHeight);
                
                xmlFrames->QueryIntAttribute("time", &time);
                
                Frame frame( Box(srcX, srcY, srcWidth, srcHeight), Box(bodyX, bodyY, bodyWidth, bodyHeight) );
                
                for (int i = 0; i < time; i++)
                    dst.addFrame(frame);
                
                xmlFrames = xmlFrames->NextSiblingElement();
            }
        }
    }
}
