#include "Systems/Weapon/AssaultWeaponBehaviour.h"

void AssaultWeaponBehaviour::Fire()
{
    m_TimeSinceLastFire = 0.0;
    m_Firing = true;
    fireRound();
}

void AssaultWeaponBehaviour::CeaseFire()
{
    m_Firing = false;
}

void AssaultWeaponBehaviour::Reload()
{
    if (m_Reloading) {
        return;
    }

    ComponentWrapper& cAssaultWeapon = m_Player["AssaultWeapon"];
    int magAmmo = cAssaultWeapon["MagazineAmmo"];
    int magSize = cAssaultWeapon["MagazineSize"];
    int ammo = cAssaultWeapon["Ammo"];

    // Don't reload if we're already fully loaded
    if (magAmmo == magSize) {
        return;
    }

    // Don't reload if we're completely out of ammo
    if (ammo == 0) {
        playEmptySound();
        m_TimeSinceLastFire = -0.0f; // HACK: To make empty sound play with interval
        return;
    }

    m_Reloading = true;
    m_ReloadTimer = cAssaultWeapon["ReloadTime"];
    playReloadAnimation();
    Events::PlaySoundOnEntity e;
    e.EmitterID = cAssaultWeapon.EntityID;
    e.FilePath = "Audio/weapon/reload.wav";
    m_EventBroker->Publish(e);
}

void AssaultWeaponBehaviour::Update(double dt)
{
    if (m_Reloading) {
        m_ReloadTimer -= dt;
        // Re-enable glow on reload impostor half-way through the animation
        if (IsClient) {
            if (m_ReloadTimer <= (double)m_Player["AssaultWeapon"]["ReloadTime"] / 2.0) {
                if (m_FirstPersonReloadImpostor.Valid()) {
                    m_FirstPersonReloadImpostor["Model"]["GlowMap"] = true;
                }
                if (m_ThirdPersonReloadImpostor.Valid()) {
                    m_ThirdPersonReloadImpostor["Model"]["GlowMap"] = true;
                }
            }
        }
        if (m_ReloadTimer <= 0) {
            finishReload();
        }
    }

    if (m_Firing && !m_Reloading) {
        m_TimeSinceLastFire += dt;
        ComponentWrapper& cAssaultWeapon = m_Player["AssaultWeapon"];
        if (m_TimeSinceLastFire >= 1.0 / ((double)cAssaultWeapon["RPM"] / 60.0)) {
            fireRound();
        }
    }

    if (!m_Firing && !m_Reloading) {
        if (IsClient) {
            playIdleAnimation();
        }
    }

    // Disable glow map on weapon if it's out of ammo
    // Make real first person weapon model visible again
    if (IsClient) {
        EntityWrapper firstPersonWeaponModel = m_Player.FirstChildByName("WeaponModel");
        if (firstPersonWeaponModel.Valid()) {
            firstPersonWeaponModel["Model"]["GlowMap"] = hasAmmo();
        }
    }
}

bool AssaultWeaponBehaviour::hasAmmo()
{
    ComponentWrapper& cAssaultWeapon = m_Player["AssaultWeapon"];
    int& magAmmo = cAssaultWeapon["MagazineAmmo"];
    return magAmmo > 0;
}

void AssaultWeaponBehaviour::fireRound()
{
    if (m_Reloading) {
        return;
    }

    ComponentWrapper& cAssaultWeapon = m_Player["AssaultWeapon"];

    int& magAmmo = cAssaultWeapon["MagazineAmmo"];
    int ammo = cAssaultWeapon["Ammo"];

    // Reload if our magazine is empty
    if (magAmmo <= 0) {
        Reload();
        return;
    } 

    // Fire
    magAmmo -= 1;
    m_TimeSinceLastFire = 0.0;

    // Effects
    if (IsClient) {
        spawnTracer();
    playFireSound();  
        viewPunch();
        playShootAnimation();
        bool hit = shoot(cAssaultWeapon["BaseDamage"]);
        if (hit) {
            showHitMarker();
        }
    }
}

void AssaultWeaponBehaviour::spawnTracer()
{
    if (!IsClient) {
        return;
    }

    EntityWrapper spawner;
    bool outOfBodyExperience = ResourceManager::Load<ConfigFile>("Config.ini")->Get<bool>("Debug.OutOfBodyExperience", false);
    if (m_Player == LocalPlayer && !outOfBodyExperience) {
        spawner = m_Player.FirstChildByName("WeaponMuzzle");
    } else {
        spawner = m_Player.FirstChildByName("ThirdPersonWeaponMuzzle");
    }

    if (!spawner.Valid()) {
        return;
    }

    float distance = traceRayDistance(Transform::AbsolutePosition(spawner), Transform::AbsoluteOrientation(spawner) * glm::vec3(0, 0, -1));
    EntityWrapper ray = SpawnerSystem::Spawn(spawner);
    ((glm::vec3&)ray["Transform"]["Scale"]).z = (distance / 100.f);
}

float AssaultWeaponBehaviour::traceRayDistance(glm::vec3 origin, glm::vec3 direction)
{
    float distance;
    glm::vec3 pos;
    auto entity = Collision::EntityFirstHitByRay(Ray(origin, direction), m_CollisionOctree, distance, pos);
    if (entity) {
        return distance;
    } else {
        return 100.f;
    }
}

void AssaultWeaponBehaviour::playFireSound()
{
    if (!IsClient) {
        return;
    }

    Events::PlaySoundOnEntity e;
    e.EmitterID = m_Player.ID;
    e.FilePath = "Audio/laser/laser1.wav";
    m_EventBroker->Publish(e);
}


void AssaultWeaponBehaviour::playEmptySound()
{
    if (!IsClient) {
        return;
    }

    Events::PlaySoundOnEntity e;
    e.EmitterID = m_Player.ID;
    e.FilePath = "Audio/weapon/zeroAmmo.wav";
    m_EventBroker->Publish(e);
}

void AssaultWeaponBehaviour::viewPunch()
{
    // Since we send absolute client orientations to server, running this server side would
    // cause aim desync.
    if (!IsClient) {
        return;
    }

    EntityWrapper playerCamera = m_Player.FirstChildByName("Camera");
    if (!playerCamera.Valid()) {
        return;
    }
    float viewPunch = m_Player["AssaultWeapon"]["ViewPunch"];
    ComponentWrapper cTransform = playerCamera["Transform"];
    glm::vec3& orientation = cTransform["Orientation"];
    orientation.x += viewPunch;
}

void AssaultWeaponBehaviour::finishReload()
{
    ComponentWrapper& cAssaultWeapon = m_Player["AssaultWeapon"];
    int& magAmmo = cAssaultWeapon["MagazineAmmo"];
    int magSize = cAssaultWeapon["MagazineSize"];
    int& ammo = cAssaultWeapon["Ammo"];

    // Throw away rounds in magazine to incentivise ammo sharing
    int toLoad = glm::min(magSize, ammo);
    magAmmo = toLoad;
    ammo -= toLoad;

    // Make real first person weapon model visible again
    EntityWrapper firstPersonWeaponModel = m_Player.FirstChildByName("WeaponModel");
    if (firstPersonWeaponModel.Valid()) {
        firstPersonWeaponModel["Model"]["Visible"] = true;
    }
    EntityWrapper thirdPersonWeaponModel = m_Player.FirstChildByName("ThirdPersonWeaponModel");
    if (thirdPersonWeaponModel.Valid()) {
        thirdPersonWeaponModel["Model"]["Visible"] = true;
    }

    m_Reloading = false;
}

void AssaultWeaponBehaviour::playShootAnimation()
{
    ComponentWrapper cAnimation = m_FirstPersonModel["Animation"];
    if (cAnimation["AnimationName1"] != "ShootRifle") {
        cAnimation["AnimationName1"] = "ShootRifle";
        cAnimation["Weight1"] = 1.0;
        cAnimation["Time1"] = 0.0;
        cAnimation["Speed1"] = 1.0;
        cAnimation["Loop1"] = true;
    }
}

void AssaultWeaponBehaviour::playIdleAnimation()
{
    if (!m_FirstPersonModel.Valid()) {
        return;
    }

    ComponentWrapper cAnimation = m_FirstPersonModel["Animation"];
    std::string& animationName1 = cAnimation["AnimationName1"];
    double& animationSpeed1 = cAnimation["Speed1"];

    std::string animationToPlay = "Idle";
    double speedToSet = 1.0;

    ComponentWrapper cPlayer = m_Player["Player"];
    glm::vec3 movementDirection = cPlayer["CurrentWishDirection"];
    if (glm::length2(movementDirection) > 0) {
        animationToPlay = "Run";
        ComponentWrapper cPhysics = m_Player["Physics"];
        speedToSet = glm::length((glm::vec3)cPhysics["Velocity"]) / (float)cPlayer["MovementSpeed"];
    }

    if (animationName1 != animationToPlay) {
        cAnimation["AnimationName1"] = animationToPlay;
        cAnimation["Weight1"] = 1.0;
        cAnimation["Time1"] = 0.0;
        cAnimation["Loop1"] = true;
    }

    if (animationSpeed1 != speedToSet) {
        cAnimation["Speed1"] = speedToSet;
    }
}

void AssaultWeaponBehaviour::playReloadAnimation()
{
    // Play animation
    // First person
    if (IsClient)
    {
        ComponentWrapper cAnimation = m_FirstPersonModel["Animation"];
        cAnimation["AnimationName1"] = "ReloadSwitch";
        cAnimation["Weight1"] = 1.0;
        cAnimation["Time1"] = 0.0;
        cAnimation["Speed1"] = 0.5;
        cAnimation["Loop1"] = true;
    }
    // TODO: Third person
    //{
    //    ComponentWrapper cAnimation = m_ThirdPersonModel["Animation"];
    //    cAnimation["AnimationName1"] = "ReloadSwitch";
    //    cAnimation["Weight1"] = 1.0;
    //    cAnimation["Time1"] = 0.0;
    //    cAnimation["Speed1"] = 0.5;
    //    cAnimation["Loop1"] = true;
    //}

    // Hide weapon model and spawn the exploding version
    {
        EntityWrapper firstPersonWeaponModel = m_Player.FirstChildByName("WeaponModel");
        EntityWrapper reloadSpawner = m_Player.FirstChildByName("FirstPersonReloadSpawner");
        if (IsClient) {
            m_FirstPersonReloadImpostor = SpawnerSystem::Spawn(reloadSpawner, reloadSpawner);
            firstPersonWeaponModel["Model"].Copy(m_FirstPersonReloadImpostor["Model"]);
        }
        firstPersonWeaponModel["Model"]["Visible"] = false;
    }
    {
        EntityWrapper thirdPersonWeaponModel = m_Player.FirstChildByName("ThirdPersonWeaponModel");
        EntityWrapper reloadSpawner = m_Player.FirstChildByName("ThirdPersonReloadSpawner");
        if (IsClient) {
            m_ThirdPersonReloadImpostor = SpawnerSystem::Spawn(reloadSpawner, reloadSpawner);
            thirdPersonWeaponModel["Model"].Copy(m_ThirdPersonReloadImpostor["Model"]);
        }
        thirdPersonWeaponModel["Model"]["Visible"] = false;
    }
}

bool AssaultWeaponBehaviour::shoot(double damage)
{
    // Only do shooting clientside
    if (!IsClient) {
        return false;
    }

    // Only handle shooting for the local player
    if (m_Player != LocalPlayer) {
        return false;
    }

    // Make sure the player isn't shooting from the grave
    if (!m_Player.Valid()) {
        return false;
    }

    // Screen center, based on current resolution!
    Rectangle screenResolution = m_Renderer->GetViewportSize();
    glm::vec2 centerScreen = glm::vec2(screenResolution.Width / 2, screenResolution.Height / 2);

    // Pick middle of screen
    PickData pickData = m_Renderer->Pick(centerScreen);
    if (pickData.Entity == EntityID_Invalid) {
        return false;
    }

    EntityWrapper victim(m_World, pickData.Entity);
    if (!victim.Valid()) {
        return false;
    }

    // Don't let us shoot ourselves in the foot somehow
    if (victim == LocalPlayer) {
        return false;
    }

    // Only care about players being hit
    if (!victim.HasComponent("Player")) {
        victim = victim.FirstParentWithComponent("Player");
    }
    if (!victim.Valid()) {
        return false;
    }


    // If friendly fire - reduce damage to 0 (needed to make Boosts, Ammosharing work)
    if ((ComponentInfo::EnumType)victim["Team"]["Team"] == (ComponentInfo::EnumType)m_Player["Team"]["Team"]) {
        damage = 0;
    }

    // Deal damage! 
    Events::PlayerDamage ePlayerDamage;
    ePlayerDamage.Inflictor = m_Player;
    ePlayerDamage.Victim = victim;
    ePlayerDamage.Damage = damage;
    m_EventBroker->Publish(ePlayerDamage);

    return true;
}

void AssaultWeaponBehaviour::showHitMarker()
{
    // Show hit marker
    EntityWrapper hitMarkerSpawner = m_Player.FirstChildByName("HitMarkerSpawner");
    if (hitMarkerSpawner.Valid()) {
        SpawnerSystem::Spawn(hitMarkerSpawner, hitMarkerSpawner);
        Events::PlaySoundOnEntity e;
        e.EmitterID = m_Player.ID; 
        e.FilePath = "Audio/weapon/hitclick.wav";
        m_EventBroker->Publish(e);
    }
}
