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

AI::AI() : Player(), m_targetEnemy(0), m_behaviour(BEHAVIOUR_OFFENSIVE), m_attackState_start(0.f),
m_attackPause(false), m_attackPause_start(0.f), m_defensive_turnaway(false), m_defensive_turnaway_start(0.f)
{
    
}

AI::AI(const std::string &name, float health, float strength, float speed, float jumpEnergy, float walkPeriod) :
Player(name, health, strength, speed, jumpEnergy, walkPeriod), m_targetEnemy(0),
m_behaviour(BEHAVIOUR_OFFENSIVE), m_attackState_start(0.f), m_attackPause(false),
m_attackPause_start(0.f), m_defensive_turnaway(false), m_defensive_turnaway_start(0.f)
{
    
}

void AI::act()
{
    //Handling KO enemies (not to focus on a poor ko player ;))
    updateEnemies();
    
    if (m_moves.getAction() == ACTION_DEFAULT && !isKo() && !isHit() && m_enemies.size() == 0) //No more enemies so stop !
    {
        if (m_moves.getState() != STATE_DEFAULT)
            m_moves.setState(STATE_DEFAULT);
    }
    else if (m_moves.getAction() == ACTION_DEFAULT && !isKo() && !isHit() && m_enemies.size() > 0) //Main AI Behaviour block
    {
        //Handling target enemy
        if (m_targetEnemy >= m_enemies.size()) //Bad index
            m_targetEnemy = rand() % m_enemies.size();
        else
        {
            //A chance to randomly change target
            if (rand() % 100 == 0)
                m_targetEnemy = rand() % m_enemies.size();
        }
        
        //Get information on the target enemy's state
        const Player &enemy = (*m_enemies[m_targetEnemy]);
        
        /*unsigned int e_state = 0, e_action = 0;
        enemy.getState(e_state, e_action);*/
        
        //Define the behaviour of the AI
        if (enemy.getHealth() <= 25)
            m_behaviour = BEHAVIOUR_OFFENSIVE;
        else if (getHealth() <= 30)
            m_behaviour = BEHAVIOUR_DEFENSIVE;
        
        //Implement the different behaviours of the AI
        if (m_behaviour == BEHAVIOUR_DEFENSIVE)
        {
            Box enemyBox = enemy.getBox();
            
            //If the enemy is too close
            if (enemyBox.left - 300 <= getBox().left + getBox().width && enemyBox.left + enemyBox.width + 300 >= getBox().left)
            {
                Box worldBox = getPhysicsWorld().getWorldBox();
                
                //Move away if not too close to the world limits
                float world_right = worldBox.left + worldBox.width;
                float ai_right = getBox().left + getBox().width;
                
                if (world_right - ai_right >= 200 && getBox().left - worldBox.left >= 200)
                {
                    if (m_moves.getState() != STATE_MOVING)
                        m_moves.setState(STATE_MOVING);
                    
                    m_moves.setXFlip(enemyBox.left >= getBox().left);
                    
                    if (rand() % 30 == 0) jump();
                }
                else //Or fight back
                {
                    //Run away if possible
                    bool enemy_left = enemyBox.left <= getBox().left; //Is the enemy to the left ?
                    
                    if ( (world_right - ai_right < 200 && !enemy_left) || (getBox().left - worldBox.left < 200 && enemy_left) )
                    {
                        if (m_moves.getState() != STATE_MOVING)
                            m_moves.setState(STATE_MOVING);
                        
                        m_moves.setXFlip(!enemy_left); //Turn away
                    }
                    else
                    {
                        if (m_moves.getState() != STATE_DEFAULT)
                            m_moves.setState(STATE_DEFAULT);
                        
                        m_moves.setXFlip(enemyBox.left < getBox().left);
                        
                        unsigned int action = rand() % 40;
                        if (action == 0 || action == 1) punch();
                        else if (action == 2 || action == 3) kick();
                        else if (action == 4) jump();
                    }
                }
            }
            else if (enemyBox.left - 450 > getBox().left + getBox().width || enemyBox.left + enemyBox.width + 450 < getBox().left)
            {
                //If the distance between the AI and its enemy is fair enough
                randomDefensive();
            }
        }
        else if (m_behaviour == BEHAVIOUR_OFFENSIVE)
        {
            Box enemyBox = enemy.getBox();
            
            //Check if the enemy is on ground above or the opposite, then crouch
            bool aboveEnemy = isOnGround() && getBox().top + getBox().height < enemyBox.top + enemyBox.height;
            bool enemyAbove = enemy.isOnGround() && getBox().top + getBox().height > enemyBox.top + enemyBox.height;
            
            if (aboveEnemy || enemyAbove)
            {
                if (m_moves.getState() != STATE_MOVING)
                    m_moves.setState(STATE_MOVING);
                
                if (rand() % 100 == 0) jump();
            }
            else
            {
                //X Flip to face the enemy
                if (enemyBox.left - 100 > getBox().left || enemyBox.left + 100 < getBox().left)
                    m_moves.setXFlip( enemyBox.left < getBox().left );
                
                //If the enemy is too far away, run to him
                if (enemyBox.left - 30 > getBox().left + getBox().width || enemyBox.left + enemyBox.width + 30 < getBox().left)
                {
                    //Attack pauses handling ! (sometimes make a break)
                    if (m_attackPause && Timer::getTicks() >= m_attackPause_start + 300.f)
                        m_attackPause = false;
                    
                    if (!m_attackPause && rand() % 25 == 20)
                    {
                        if (m_moves.getState() != STATE_DEFAULT)
                            m_moves.setState(STATE_DEFAULT);
                        
                        unsigned int crouch_or_jump = rand() % 16;
                        if ((crouch_or_jump == 1 || crouch_or_jump == 2) && m_moves.getState() != STATE_CROUCHED)
                            crouch();
                        else if (crouch_or_jump == 4)
                            jump();
                        
                        m_attackPause = true;
                        m_attackPause_start = Timer::getTicks();
                    }
                    
                    if (!m_attackPause) //Run to the enemy if no pause
                    {
                        if (m_moves.getState() != STATE_MOVING)
                            m_moves.setState(STATE_MOVING);
                    }
                }
                else //Fight the enemy
                {
                    unsigned int action = rand() % 200;
                    if (action <= 8) punch();
                    else if (action <= 16) kick();
                    else if (action == 100) jump();
                    
                    if (m_attackState_start + 300.f <= Timer::getTicks())
                    {
                        unsigned int actionState = rand() % 30;
                        
                        if (actionState <= 20 && m_moves.getState() != STATE_DEFAULT) m_moves.setState(STATE_DEFAULT);
                        else if (actionState <= 25 && m_moves.getState() != STATE_CROUCHED) crouch();
                        else if (m_moves.getState() != STATE_MOVING) m_moves.setState(STATE_MOVING);
                        
                        m_attackState_start = Timer::getTicks();
                    }
                }
            }
        }
    }
}

void AI::randomDefensive()
{
    //Don't move for too long
    if (m_moves.getState() == STATE_MOVING)
    {
        unsigned int actionStopMove = rand() % 10;
        if (actionStopMove <= 2)
            m_moves.setState(STATE_DEFAULT);
    }
    
    if (rand() % 3 != 0) //Sometimes just do nothing
    {
        if (m_enemies.size() == 1)
        {
            //Turn away sometimes
            if (m_defensive_turnaway && Timer::getTicks() >= m_defensive_turnaway_start + 300.f)
                m_defensive_turnaway = false;
            
            if (!m_defensive_turnaway && rand() % 100 == 20)
            {
                m_moves.setXFlip( m_enemies[0]->getBox().left >= getBox().left );
                
                m_defensive_turnaway = true;
                m_defensive_turnaway_start = Timer::getTicks();
            }
            
            if (!m_defensive_turnaway)
                m_moves.setXFlip( m_enemies[0]->getBox().left < getBox().left );
        }
        else
        {
            //Flip randomly
            unsigned int actionFlip = rand() % 160;
            if (actionFlip == 49)
                m_moves.setXFlip( !m_moves.getXFlip() );
        }
        
        //Act randomly
        unsigned int action = rand() % 250;
        if (action <= 10 && m_moves.getState() != STATE_MOVING) m_moves.setState(STATE_MOVING);
        else if (action == 70) jump();
        else if (action == 80 || action == 81) punch();
        else if (action == 90 || action == 91) kick();
    }
}

void AI::updateEnemies()
{
    //Remove all KO enemies from m_enemies
    std::vector<const Player*>::iterator it = m_enemies.begin();
    while (it != m_enemies.end())
    {
        if ((*it)->isKo())
            it = m_enemies.erase(it);
        else
            it++;
    }
}

void AI::takeAHit(float strength, int side, const PhysicsObject *source)
{
    Player::takeAHit(strength, side, source);
    
    //The AI knows who hit it
    if (source != NULL)
    {
        //See if it's a know enemy and if so, focus it (likely)
        for (unsigned int i = 0; i < m_enemies.size(); i++)
        {
            //Found the enemy
            if (m_enemies[i] == source)
            {
                if (rand() % 100 <= 80) //80% probability to focus it
                    m_targetEnemy = i;
            }
        }
    }
}

void AI::addEnemy(const Player &enemy)
{
    m_enemies.push_back(&enemy);
    m_targetEnemy = rand() % m_enemies.size();
}

void AI::clearEnemies()
{
    m_enemies.clear();
}

const std::vector<const Player*> &AI::getEnemies() const
{
    return m_enemies;
}

//Static methods
AI AI::loadFromXML(const std::string &name, const std::string &xmlRelPath)
{
    //TinyXML initialization
    std::string xmlPath = BuildValues::data(xmlRelPath); //Locate absolute path to xml data
    
    TiXmlDocument xmlFile;
    if (!xmlFile.LoadFile(xmlPath.c_str()))
        return AI();
    
    TiXmlHandle hdl(&xmlFile);
    TiXmlElement *xmlElement = hdl.FirstChildElement().FirstChildElement().Element();
    
    int i = 0;
    while (xmlElement)
    {
        std::string itemName = xmlElement->Attribute("name");
        if (itemName == name)
        {
            //Get informations
            float strenght = 10, speed = 700, jumpEnergy = 750, walkPeriod = .20;
            
            xmlElement->QueryFloatAttribute("strenght", &strenght);
            xmlElement->QueryFloatAttribute("speed", &speed);
            xmlElement->QueryFloatAttribute("jumpEnergy", &jumpEnergy);
            xmlElement->QueryFloatAttribute("walkPeriod", &walkPeriod);
            
            //Create player
            return AI(name, 100, strenght, speed, jumpEnergy, walkPeriod);
        }
        
        xmlElement = xmlElement->NextSiblingElement();
        i++;
    }
    
    return AI();
}
