use super::{ add_effect, targeting, EffectSpawner, EffectType, Entity, Targets, World };
use crate::{
    gamelog,
    gamesystem::{ hp_per_level, mana_per_level },
    Attributes,
    Confusion,
    Destructible,
    GrantsXP,
    Map,
    Player,
    Pools,
};
use crate::config::visuals::{ DEFAULT_PARTICLE_LIFETIME, LONG_PARTICLE_LIFETIME };
use crate::config::messages::LEVELUP_PLAYER;
use rltk::prelude::*;
use specs::prelude::*;

pub fn inflict_damage(ecs: &mut World, damage: &EffectSpawner, target: Entity) {
    let mut pools = ecs.write_storage::<Pools>();
    if let Some(target_pool) = pools.get_mut(target) {
        if !target_pool.god {
            if let EffectType::Damage { amount } = damage.effect_type {
                target_pool.hit_points.current -= amount;
                add_effect(None, EffectType::Bloodstain, Targets::Entity { target });
                add_effect(
                    None,
                    EffectType::Particle {
                        glyph: to_cp437('‼'),
                        fg: RGB::named(ORANGE),
                        bg: RGB::named(BLACK),
                        lifespan: DEFAULT_PARTICLE_LIFETIME,
                        delay: 0.0,
                    },
                    Targets::Entity { target }
                );
                if target_pool.hit_points.current < 1 {
                    super::DEAD_ENTITIES.lock().unwrap().push_back(target);
                    add_effect(damage.source, EffectType::EntityDeath, Targets::Entity { target });
                }
            }
        }
    } else if let Some(_destructible) = ecs.read_storage::<Destructible>().get(target) {
        add_effect(damage.source, EffectType::EntityDeath, Targets::Entity { target });
    }
}

pub fn heal_damage(ecs: &mut World, heal: &EffectSpawner, target: Entity) {
    let mut pools = ecs.write_storage::<Pools>();
    if let Some(pool) = pools.get_mut(target) {
        if let EffectType::Healing { amount, increment_max } = &heal.effect_type {
            let before = pool.hit_points.current;
            pool.hit_points.current = i32::min(pool.hit_points.max, pool.hit_points.current + amount);
            if pool.hit_points.current - before < *amount && *increment_max {
                // If the heal was not fully effective, and healing source was noncursed, increase max HP by 1.
                pool.hit_points.max += 1;
                pool.hit_points.current += 1;
            }
            add_effect(
                None,
                EffectType::Particle {
                    glyph: to_cp437('♥'),
                    fg: RGB::named(BLUE),
                    bg: RGB::named(BLACK),
                    lifespan: DEFAULT_PARTICLE_LIFETIME,
                    delay: 0.0,
                },
                Targets::Entity { target }
            );
        }
    }
}

pub fn add_confusion(ecs: &mut World, effect: &EffectSpawner, target: Entity) {
    if let EffectType::Confusion { turns } = &effect.effect_type {
        ecs.write_storage::<Confusion>()
            .insert(target, Confusion { turns: *turns })
            .expect("Unable to insert Confusion");
    }
}

pub fn bloodstain(ecs: &mut World, target: usize) {
    let mut map = ecs.fetch_mut::<Map>();
    // If the current tile isn't bloody, bloody it.
    if !map.bloodstains.contains(&target) {
        map.bloodstains.insert(target);
        return;
    }
    let mut spread: i32 = target as i32;
    let mut attempts: i32 = 0;
    // Otherwise, roll to move one tile in any direction.
    // If this tile isn't bloody, bloody it. If not, loop.
    loop {
        let mut rng = ecs.write_resource::<RandomNumberGenerator>();
        attempts += 1;
        spread = match rng.roll_dice(1, 8) {
            1 => spread + 1,
            2 => spread - 1,
            3 => spread + 1 + map.width,
            4 => spread - 1 + map.width,
            5 => spread + 1 - map.width,
            6 => spread - 1 - map.width,
            7 => spread + map.width,
            _ => spread - map.width,
        };
        // - If we're in bounds and the tile is unbloodied, bloody it and return.
        // - If we ever leave bounds, return.
        // - Roll a dice on each failed attempt, with an increasing change to return (soft-capping max spread)
        if spread > 0 && spread < map.height * map.width {
            if !map.bloodstains.contains(&(spread as usize)) {
                map.bloodstains.insert(spread as usize);
                return;
            }
            if rng.roll_dice(1, 10 - attempts) == 1 {
                return;
            }
        } else {
            return;
        }
    }
}

/// Takes a level, and returns the total XP required to reach the next level.
fn get_next_level_requirement(level: i32) -> i32 {
    if level == 0 {
        return 5;
    } else if level < 10 {
        return 20 * (2_i32).pow((level as u32) - 1);
    } else if level < 20 {
        return 10000 * (2_i32).pow((level as u32) - 10);
    } else if level < 30 {
        return 10000000 * (level - 19);
    }
    return -1;
}

/// Handles EntityDeath effects.
pub fn entity_death(ecs: &mut World, effect: &EffectSpawner, target: Entity) {
    let mut xp_gain = 0;
    let mut pools = ecs.write_storage::<Pools>();
    let attributes = ecs.read_storage::<Attributes>();
    // If the target has a position, remove it from the SpatialMap.
    if let Some(pos) = targeting::entity_position(ecs, target) {
        crate::spatial::remove_entity(target, pos as usize);
    }
    // If the target was killed by a source, cont.
    if let Some(source) = effect.source {
        // Calc XP value of target.
        if let Some(xp_value) = ecs.read_storage::<GrantsXP>().get(target) {
            xp_gain += xp_value.amount;
        }
        // If there was XP, run through XP-gain and level-up.
        if xp_gain != 0 {
            if let None = pools.get(source) {
                return;
            }
            let mut source_pools = pools.get_mut(source).unwrap();
            let source_attributes = attributes.get(source).unwrap();
            source_pools.xp += xp_gain;
            let next_level_requirement = get_next_level_requirement(source_pools.level);
            if next_level_requirement != -1 && source_pools.xp >= next_level_requirement {
                source_pools.level += 1;
                // If it was the PLAYER that levelled up:
                if ecs.read_storage::<Player>().get(source).is_some() {
                    gamelog::record_event("player_level", 1);
                    gamelog::Logger::new().append(LEVELUP_PLAYER).append_n(source_pools.level).append("!").log();
                    let player_pos = ecs.fetch::<Point>();
                    let map = ecs.fetch_mut::<Map>();
                    for i in 0..5 {
                        if player_pos.y - i > 1 {
                            add_effect(
                                None,
                                EffectType::Particle {
                                    glyph: to_cp437('░'),
                                    fg: RGB::named(GOLD),
                                    bg: RGB::named(BLACK),
                                    lifespan: LONG_PARTICLE_LIFETIME,
                                    delay: (i as f32) * 100.0,
                                },
                                Targets::Tile { target: map.xy_idx(player_pos.x, player_pos.y - i) }
                            );
                            if i > 2 {
                                add_effect(
                                    None,
                                    EffectType::Particle {
                                        glyph: to_cp437('░'),
                                        fg: RGB::named(GOLD),
                                        bg: RGB::named(BLACK),
                                        lifespan: LONG_PARTICLE_LIFETIME,
                                        delay: (i as f32) * 100.0,
                                    },
                                    Targets::Tile { target: map.xy_idx(player_pos.x + (i - 2), player_pos.y - i) }
                                );
                                add_effect(
                                    None,
                                    EffectType::Particle {
                                        glyph: to_cp437('░'),
                                        fg: RGB::named(GOLD),
                                        bg: RGB::named(BLACK),
                                        lifespan: LONG_PARTICLE_LIFETIME,
                                        delay: (i as f32) * 100.0,
                                    },
                                    Targets::Tile { target: map.xy_idx(player_pos.x - (i - 2), player_pos.y - i) }
                                );
                            }
                        }
                    }
                } else {
                    console::log("DEBUGINFO: Something other than the player levelled up.");
                    // TODO: Growing up, NPC-specific level-up cases.
                }
                let mut rng = ecs.write_resource::<RandomNumberGenerator>();
                let hp_gained = hp_per_level(
                    &mut rng,
                    source_attributes.constitution.base + source_attributes.constitution.modifiers
                );
                let mana_gained = mana_per_level(
                    &mut rng,
                    source_attributes.intelligence.base + source_attributes.intelligence.modifiers
                );
                source_pools.hit_points.max += hp_gained;
                source_pools.hit_points.current += hp_gained;
                // Roll for MANA gain this level
                source_pools.mana.max += mana_gained;
                source_pools.mana.current += mana_gained;
            }
        }
    }
}
