use rand::{thread_rng, Rng};
use uuid::Uuid;

use util::{IntPct};
use cryp::{Cryp, CrypEffect, EffectMeta, Stat};
use vbox::{Var};

pub fn resolve(skill: Skill, source: &mut Cryp, target: &mut Cryp, mut resolutions: Vec<Resolution>) -> Resolutions {
    if let Some(disable) = source.disabled(skill) {
        resolutions.push(Resolution::new(source, target).event(Event::Disable { disable, skill }));
        return resolutions;
    }

    if target.is_ko() {
        resolutions.push(Resolution::new(source, target).event(Event::TargetKo { skill }));
        return resolutions;
    }

    if target.affected(Effect::Reflect) {
        // guard against overflow
        if source.affected(Effect::Reflect) {
            return resolutions;
        }
        resolutions.push(Resolution::new(source, target).event(Event::Reflection { skill }));
        return resolve(skill, target, source, resolutions);
    }

    if source.affected(Effect::Haste) {
        match skill {
            Skill::Attack |
            Skill::Slay |
            Skill::Chaos |
            Skill::Strike => {
                let amount = source.speed().pct(Skill::HasteStrike.multiplier());
                target.deal_red_damage(Skill::HasteStrike, amount)
                .into_iter()
                .for_each(|e| resolutions.push(Resolution::new(source, target).event(e)));
            },
            _ => (),
        }
    }

    if source.affected(Effect::Impurity) {
        match skill {
            Skill::Blast |
            Skill::Chaos |
            Skill::Siphon => {
                let amount = source.green_damage().pct(Skill::ImpureBlast.multiplier());
                target.deal_blue_damage(Skill::ImpureBlast, amount)
                .into_iter()
                .for_each(|e| resolutions.push(Resolution::new(source, target).event(e)));
            },
            _ => (),
        }
    }

    // match self.category() == Category::Red {
    //     true => {
    //         if let Some(evasion) = target.evade(*self) {
    //             resolutions.push(evasion);
    //             return Event;
    //         }
    //     },
    //     false => (),
    // }

    resolutions = match skill {
        Skill::Amplify => amplify(source, target, resolutions, skill), // increase magic damage
        Skill::Attack => attack(source, target, resolutions, skill),
        Skill::Banish => banish(source, target, resolutions, skill), // TODO prevent all actions
        Skill::Blast => blast(source, target, resolutions, skill),
        Skill::Block => block(source, target, resolutions, skill),
        Skill::Chaos => chaos(source, target, resolutions, skill),
        Skill::Clutch => clutch(source, target, resolutions, skill),
        Skill::Corrupt => corrupt(source, target, resolutions, skill),
        Skill::CorruptionTick => corruption_tick(source, target, resolutions, skill),
        Skill::Curse => curse(source, target, resolutions, skill),
        Skill::Decay => decay(source, target, resolutions, skill), // dot
        Skill::DecayTick => decay_tick(source, target, resolutions, skill), // dot
        Skill::Haste => haste(source, target, resolutions, skill), // speed slow
        Skill::HasteStrike => panic!("haste strike should not be caste"),
        Skill::Heal => heal(source, target, resolutions, skill),
        Skill::Hex => hex(source, target, resolutions, skill),
        Skill::Hostility => hostility(source, target, resolutions, skill),
        Skill::Impurity => impurity(source, target, resolutions, skill),
        Skill::ImpureBlast => panic!("impure blast should not be caste"),
        Skill::Invert => invert(source, target, resolutions, skill),
        Skill::Injure => injure(source, target, resolutions, skill),
        Skill::Parry => parry(source, target, resolutions, skill),
        Skill::Purge => purge(source, target, resolutions, skill), // dispel all buffs
        Skill::Purify => purify(source, target, resolutions, skill), // dispel all debuffs
        Skill::Recharge => recharge(source, target, resolutions, skill), // target is immune to magic damage and fx
        Skill::Reflect => reflect(source, target, resolutions, skill),
        Skill::Riposte => panic!("riposte should not be caste"),
        Skill::Ruin => ruin(source, target, resolutions, skill),
        Skill::Scatter => scatter(source, target, resolutions, skill), // target is immune to magic damage and fx
        Skill::Silence => silence(source, target, resolutions, skill), // target cannot cast spells
        Skill::Siphon => siphon(source, target, resolutions, skill),
        Skill::SiphonTick => siphon_tick(source, target, resolutions, skill), // hot
        Skill::Slay => slay(source, target, resolutions, skill), // hybrid dmg self heal
        Skill::Sleep => sleep(source, target, resolutions, skill), // speed slow
        Skill::Slow => slow(source, target, resolutions, skill), // speed slow
        Skill::Snare => snare(source, target, resolutions, skill),
        Skill::Strangle => strangle(source, target, resolutions, skill),
        Skill::StrangleTick => strangle_tick(source, target, resolutions, skill),

        Skill::Strike    => strike(source, target, resolutions, skill),
        Skill::StrikeII  => strike(source, target, resolutions, skill),
        Skill::StrikeIII => strike(source, target, resolutions, skill),

        Skill::Stun => stun(source, target, resolutions, skill),
        Skill::Taunt => taunt(source, target, resolutions, skill),
        Skill::Throw => throw(source, target, resolutions, skill), // no damage stun, adds vulnerable
        Skill::Triage => triage(source, target, resolutions, skill), // hot
        Skill::TriageTick => triage_tick(source, target, resolutions, skill), // hot

        // -----------------
        // Test
        // -----------------
        Skill::TestAttack => attack(source, target, resolutions, skill),
        Skill::TestHeal => heal(source, target, resolutions, skill),
        Skill::TestTouch => touch(source, target, resolutions, skill),
        Skill::TestStun => stun(source, target, resolutions, Skill::Stun),
        Skill::TestBlock => block(source, target, resolutions, Skill::Block),
        Skill::TestParry => parry(source, target, resolutions, Skill::Parry),
        Skill::TestSiphon => siphon(source, target, resolutions, Skill::Siphon),
    };

    // if any event dealt damage to target cryp
    // hit them with corruption

    // on damage events
    // todo not sure if this fucks up with multiple calls to resolve
    // have to think
    for r in resolutions.clone() {
        match r.event {
            Event::Damage { amount, skill, mitigation: _, colour: _ } => {
                if target.affected(Effect::Corrupt) {
                    resolutions = corruption(target, source, resolutions, Skill::Corrupt);
                }

                if target.affected(Effect::Hostility) {
                    resolutions = hatred(source, target, resolutions, skill, amount, Skill::Hostility);
                }
            },
            Event::Immunity { skill: _, immunity } => match immunity.contains(&Effect::Parry) {
                true => resolutions = riposte(target, source, resolutions, Skill::Riposte),
                false => (),
            },
            _ => (),
        }
    };

    // i don't think we need to check the source being ko
    if target.is_ko() {
        resolutions.push(Resolution::new(source, target).event(Event::Ko { skill }));
        target.effects.clear();
    }

    return resolutions;
}



#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)]
pub struct Cast {
    pub id: Uuid,
    pub source_player_id: Uuid,
    pub source_cryp_id: Uuid,
    pub target_cryp_id: Uuid,
    pub skill: Skill,
    pub speed: u64,
    pub resolutions: Resolutions,
}

impl Cast {
    pub fn new(source_cryp_id: Uuid, source_player_id: Uuid, target_cryp_id: Uuid, skill: Skill) -> Cast {
        return Cast {
            id: Uuid::new_v4(),
            source_cryp_id,
            source_player_id,
            target_cryp_id,
            skill,
            speed: 0,
            resolutions: vec![],
        };
    }

    pub fn new_tick(source: &mut Cryp, target: &mut Cryp, skill: Skill) -> Cast {
        Cast::new(source.id, source.account, target.id, skill)
    }

    pub fn used_cooldown(&self) -> bool {
        return self.skill.base_cd().is_some();
    }
}

pub type Disable = Vec<Effect>;
pub type Immunity = Vec<Effect>;

#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)]
pub struct LogCryp {
    pub id: Uuid,
    pub name: String,
}

#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)]
pub struct Resolution {
    pub source: LogCryp,
    pub target: LogCryp,
    pub event: Event,
}

impl Resolution {
    fn new(source: &Cryp, target: &Cryp) -> Resolution {
        Resolution {
            source: LogCryp { id: source.id, name: source.name.clone() },
            target: LogCryp { id: target.id, name: target.name.clone() },
            event: Event::Incomplete,
        }
    }

    fn event(mut self, e: Event) -> Resolution {
        self.event = e;
        self
    }
}


#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)]
pub enum Event {
    Disable     { skill: Skill, disable: Disable },
    Immunity    { skill: Skill, immunity: Immunity },
    Damage      { skill: Skill, amount: u64, mitigation: u64, colour: Category },
    Healing     { skill: Skill, amount: u64, overhealing: u64 },
    Recharge    { skill: Skill, red: u64, blue: u64 },
    Inversion   { skill: Skill },
    Reflection  { skill: Skill },
    Effect      { skill: Skill, effect: Effect, duration: u8 },
    Skill       { skill: Skill },
    Removal     { effect: Effect },
    TargetKo    { skill: Skill },
    // skill not necessary but makes it neater as all events are arrays in js
    Ko          { skill: Skill },
    Incomplete,
    // not used
    Evasion     { skill: Skill, evasion_rating: u64 },
}

type Resolutions = Vec<Resolution>;
pub type Cooldown = Option<u8>;

#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub enum Effect {
    // physical
    Stun,
    Parry,
    Block,
    Bleed,
    Leech,
    Airborne,
    Untouchable,
    Deadly,
    Vulnerable,
    Fury,
    Blind,
    Snare,
    Clutch,
    Injured,

    Reflect,

    Taunt,
    Impurity,
    Invert,
    Strangle,
    Strangling,

    // magic
    Hex,
    Ruin,
    Curse,
    Banish,
    Slow,
    Haste,
    Enslave,
    Mesmerise,
    Amplify,
    Silence,
    Wither, // Reduce green dmg (healing) taken

    // corrupt is the buff that applies
    // corruption the dmg debuff
    Corrupt,
    Corruption,

    // hostility is the buff
    // hatred is the increased damage
    Hostility,
    Hatred,

    // magic immunity
    Scatter,

    // effects over time
    Triage,
    Decay,
    Regen,
    Siphon,

    SpeedSiphon,
    SpeedIncrease,

    Ko,
}

impl Effect {
    pub fn immune(&self, skill: Skill) -> bool {
        match self {
            Effect::Parry => match skill.category() {
                Category::Blue => false,
                Category::Red => true,
                _ => false,
            },
            Effect::Scatter => match skill.category() {
                Category::Blue => true,
                Category::Red => false,
                _ => false,
            },
            Effect::Banish => true,
            Effect::Injured => match skill.category() {
                Category::Green => true,
                Category::GreenTick => true,
                _ => false,
            },
            _ => false,
        }
    }

    pub fn disables_skill(&self, skill: Skill) -> bool {
        match self {
            Effect::Stun => true,
            Effect::Hex => true,
            Effect::Ruin => true,
            Effect::Banish => true,
            Effect::Strangle => true,
            Effect::Strangling => skill != Skill::StrangleTick,
            Effect::Silence => match skill.category() {
                Category::Blue => true,
                Category::Red => false,
                _ => false,
            },
            Effect::Snare => match skill.category() {
                Category::Blue => false,
                Category::Red => true,
                _ => false,
            },

            Effect::Ko => match skill.category() {
                Category::BlueTick => false,
                Category::GreenTick => false,
                _ => true,
            },
            _ => false,
        }
    }

    pub fn modifications(&self) -> Vec<Stat> {
        match self {
            Effect::Vulnerable => vec![Stat::RedDamageTaken],
            Effect::Block => vec![Stat::RedDamageTaken],

            Effect::Hatred => vec![Stat::RedDamage, Stat::BlueDamage],

            Effect::Amplify => vec![Stat::RedDamage, Stat::BlueDamage],
            Effect::Curse => vec![Stat::BlueDamageTaken],

            Effect::Impurity => vec![Stat::GreenDamage],
            Effect::Wither => vec![Stat::GreenDamageTaken],

            Effect::Haste => vec![Stat::Speed],
            Effect::Slow => vec![Stat::Speed],

            _ => vec![],
        }
    }

    pub fn apply(&self, value: u64, meta: Option<EffectMeta>) -> u64 {
        match self {
            Effect::Vulnerable  => value << 1,
            Effect::Block       => value >> 1,

            Effect::Amplify     => value << 1,
            Effect::Curse       => value << 1,

            Effect::Haste       => value << 1,
            Effect::Slow        => value >> 1,

            Effect::Impurity    => value << 1,
            Effect::Wither      => value >> 1,

            Effect::Hatred      => value + match meta {
                Some(EffectMeta::AddedDamage(d)) => d,
                _ => panic!("hatred meta not damage"),
            },

            _ => {
                println!("{:?} does not have a mod effect", self);
                return value;
            },
        }
    }

    pub fn category(&self) -> Category {
        match self {
            // physical
            Effect::Stun => Category::Debuff,
            Effect::Block => Category::Buff,
            Effect::Parry => Category::Buff,
            Effect::Bleed => Category::Debuff,
            Effect::Leech => Category::Debuff,
            Effect::Airborne => Category::Debuff,
            Effect::Untouchable => Category::Buff,
            Effect::Deadly => Category::Buff,
            Effect::Vulnerable => Category::Debuff,
            Effect::Fury => Category::Buff,
            Effect::Blind => Category::Debuff,
            Effect::Snare => Category::Debuff,
            Effect::Clutch => Category::Buff,
            Effect::Taunt => Category::Buff,
            Effect::Injured => Category::Debuff,
            Effect::Strangle => Category::Debuff,
            Effect::Strangling => Category::Buff,

            // magic
            Effect::Hex => Category::Debuff,
            Effect::Ruin => Category::Debuff,
            Effect::Curse => Category::Debuff,
            Effect::Banish => Category::Debuff, // todo randomise
            Effect::Slow => Category::Debuff,
            Effect::Haste => Category::Buff,
            Effect::Hatred => Category::Buff,
            Effect::Reflect => Category::Buff,
            Effect::Enslave => Category::Debuff,
            Effect::Mesmerise => Category::Debuff,
            Effect::Amplify => Category::Buff,
            Effect::Silence => Category::Debuff,
            Effect::Wither => Category::Debuff,

            Effect::Corrupt => Category::Buff,
            Effect::Corruption => Category::Debuff,

            Effect::Hostility => Category::Buff,

            // magic 
            Effect::Impurity => Category::Buff,
            Effect::Scatter => Category::Buff,
            Effect::Invert => Category::Buff,

            // effects over time
            Effect::Triage => Category::Buff,
            Effect::Decay => Category::Debuff,
            Effect::Regen => Category::Buff,
            Effect::Siphon => Category::Debuff,

            Effect::SpeedSiphon => Category::Debuff,
            Effect::SpeedIncrease => Category::Buff,

            Effect::Ko => Category::Ko,
        }
    }
}

#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub enum Category {
    Red,
    Blue,
    Green,

    RedDamage,
    BlueDamage,
    GreenDamage,

    RedTick,
    BlueTick,
    GreenTick,

    Buff,
    Debuff,

    Ko,
}

#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub enum Skill {
    Attack,

    // -----------------
    // Nature
    // -----------------
    Block, // reduce damage
    Parry, // avoid all damage
    Riposte,
    Snare,
    Injure,

    Recharge,
    Reflect,
    Ruin,
    Slay,
    Sleep,
    Clutch,
    Taunt,
    Impurity,
    ImpureBlast,
    Invert,

    Strangle,
    StrangleTick,

    Strike,
    StrikeII,
    StrikeIII,
    Stun,
    // Evade, // actively evade

    // -----------------
    // Nonviolence
    // -----------------
    Heal,
    Triage, // hot
    TriageTick,
    Throw, // no damage stun, adds vulnerable

    // Sleep,
    // Nightmare,

    // -------------------
    // Destruction
    // -------------------
    Blast,
    Amplify,
    Decay, // dot
    DecayTick, // dot
    Siphon,
    SiphonTick,
    Curse,

    Hostility,

    Corrupt,
    CorruptionTick,


    // -----------------
    // Purity
    // -----------------
    Scatter,
    Silence,
    Purify,
    Purge,

    // -----------------
    // Chaos
    // -----------------
    Banish,
    Chaos,
    Hex,
    Haste,
    HasteStrike,
    Slow,

    // used by tests, no cd, 100% multiplier
    TestAttack,
    TestHeal,
    TestTouch, // No damage
    TestStun,
    TestBlock,
    TestParry,
    TestSiphon,
}

impl Skill {
    pub fn multiplier(&self) -> u64 {
        match self {
            // Attack Base
            Skill::Attack => 80, // Base
            Skill::Blast => 110, // BB
            Skill::Chaos => 40, // BR
            Skill::Heal => 130, //GG
            Skill::SiphonTick => 40, // GB
            Skill::Slay => 70, // RG
            
            Skill::Strike => 90, //RR
            Skill::StrikeII => 110,
            Skill::StrikeIII => 130,

            // Block Base
            Skill::CorruptionTick => 80,
            Skill::Purify => 45, //Green dmg (heal)
            Skill::Recharge => 85, //restore red and blue life (heal)
            Skill::Reflect => 45, //restore blue life (heal)
            Skill::Riposte => 70,

            // Stun Base
            Skill::Sleep => 240, //Green dmg (heal)
            Skill::StrangleTick => 65,

            // Debuff Base
            Skill::Silence => 55, // Deals more per blue skill on target
            Skill::Snare => 40, // Deals more per red skill on target
            Skill::DecayTick => 25,

            // Buff base
            Skill::ImpureBlast => 25,
            Skill::HasteStrike => 30,
            Skill::TriageTick => 75,

            _ => 100,
        }
    }

    pub fn duration(&self) -> u8 {
        match self {
            Skill::Block => 1,
            Skill::Parry => 1,
            Skill::Clutch => 1,

            Skill::Reflect => 1,

            Skill::Injure => 2,

            Skill::Strangle => 2,

            Skill::Stun => 2,
            Skill::Sleep => 3,

            Skill::Throw => 2,
            Skill::Snare => 3,

            Skill::Taunt => 1,
            Skill::Impurity => 3,
            Skill::Invert => 1,

            Skill::Hex => 2,
            Skill::Ruin => 1,
            Skill::Curse => 2,
            Skill::Banish => 1,

            Skill::Slow => 2,
            Skill::Haste => 2,

            Skill::Amplify => 2,
            Skill::Silence => 3,

            Skill::Hostility => 2, // Primary Buff
            Skill::Corrupt => 2, // Primary Buff

            Skill::Scatter => 2,

            Skill::Triage => 3,
            Skill::Decay => 3,
            Skill::Siphon => 2,

            _ => {
                println!("{:?} does not have a duration", self);
                return 1;
            },
        }
    }

    pub fn secondary_duration(&self) -> u8 {
        match self {
            Skill::Hostility => 5, // Increased dmg buff
            Skill::Corrupt => 3, // Damage over time
            Skill::Throw => 3, // Inc dmg taken debuff

            _ => {
                println!("{:?} does not have a secondary duration", self);
                return 1;
            },
        }

    }


    pub fn base_cd(&self) -> Cooldown {
        match self {
            Skill::Attack => None,
            Skill::Strike => None,
            Skill::StrikeII => None,
            Skill::StrikeIII => None,
            Skill::Block => None, // reduce damage
            Skill::Parry => None, // avoid all damage
            Skill::Riposte => None, // used on parry
            Skill::Snare => Some(2),
            Skill::Stun => Some(1),
            Skill::Heal => None,
            Skill::Triage => None, // hot
            Skill::TriageTick => None,
            Skill::Throw => Some(1), // no damage stun, adds vulnerable
            Skill::Blast => None,
            Skill::Chaos => None,
            Skill::Amplify => Some(1),
            Skill::Impurity => None,
            Skill::ImpureBlast => None,
            Skill::Invert => Some(2),
            Skill::Decay => Some(1), // dot
            Skill::DecayTick => None,
            Skill::Siphon => None,
            Skill::SiphonTick => None,
            Skill::Curse => Some(1),
            Skill::Scatter => Some(2),
            Skill::Silence => Some(2),
            Skill::Purify => None,
            Skill::Purge => None,
            Skill::Banish => Some(1),
            Skill::Hex => Some(1),
            Skill::Haste => None,
            Skill::HasteStrike => None, // Used in haste

            Skill::Slow => None,
            Skill::Reflect => Some(2),
            Skill::Recharge => Some(2),
            Skill::Ruin => Some(3),
            Skill::Slay => None,
            Skill::Sleep => Some(3),

            Skill::Strangle => Some(2),
            Skill::StrangleTick => None,
            Skill::Clutch => Some(2),
            Skill::Taunt => Some(1),
            Skill::Injure => Some(2),

            Skill::Corrupt => Some(1),
            Skill::CorruptionTick => None,

            Skill::Hostility => Some(1),

            // -----------------
            // Test
            // -----------------
            Skill::TestAttack => None,
            Skill::TestHeal => None,
            Skill::TestTouch => None,
            Skill::TestStun => None,
            Skill::TestBlock => None,
            Skill::TestSiphon => None,
            Skill::TestParry => None,
        }
    }

    pub fn category(&self) -> Category {
        match self {
            Skill::Attack => Category::Red,
            Skill::Strike => Category::Red,
            Skill::StrikeII => Category::Red,
            Skill::StrikeIII => Category::Red,
            Skill::Injure => Category::Red,
            Skill::Strangle => Category::Red,
            Skill::StrangleTick => Category::Red,
            Skill::Block => Category::Red, // reduce damage
            Skill::Parry => Category::Red, // avoid all damage
            Skill::Riposte => Category::Red, // avoid all damage
            Skill::Snare => Category::Red,
            Skill::Clutch => Category::Red,
            Skill::Stun => Category::Red,
            Skill::Slay => Category::Red,
            Skill::Taunt => Category::Red,
            Skill::HasteStrike => Category::Red,

            Skill::Heal => Category::Green,
            Skill::Triage => Category::Green, // hot
            Skill::TriageTick => Category::GreenTick, // hot
            Skill::Throw => Category::Green,
            Skill::Purify => Category::Green,
            Skill::Recharge => Category::Green,
            Skill::Reflect => Category::Green,
            Skill::Haste => Category::Green,
            Skill::Impurity => Category::Green,
            Skill::Invert => Category::Green,
            Skill::Sleep => Category::Green,
            
            Skill::ImpureBlast => Category::Blue,
            Skill::Scatter => Category::Blue,
            Skill::Blast => Category::Blue,
            Skill::Chaos => Category::Blue,
            Skill::Amplify => Category::Blue,
            Skill::Decay => Category::Blue, // dot
            Skill::DecayTick => Category::BlueTick, // hot
            Skill::Siphon => Category::Blue,
            Skill::SiphonTick => Category::BlueTick, // hot
            Skill::Curse => Category::Blue,
            Skill::Silence => Category::Blue,
            Skill::Purge => Category::Blue,
            Skill::Banish => Category::Blue,
            Skill::Hex => Category::Blue,
            Skill::Slow => Category::Blue,
            Skill::Ruin => Category::Blue,
            Skill::Hostility => Category::Blue,
            Skill::Corrupt => Category::Blue,
            Skill::CorruptionTick => Category::Blue,

            // -----------------
            // Test
            // -----------------

            Skill::TestAttack => Category::Red,
            Skill::TestHeal => Category::Green,
            Skill::TestTouch => Category::Red,
            Skill::TestStun => Category::Red,
            Skill::TestParry => Category::Red,
            Skill::TestBlock => Category::Red,
            Skill::TestSiphon => Category::Blue,
        }
    }

    pub fn ko_castable(&self) -> bool {
        match self {
            Skill::TriageTick => true,
            Skill::DecayTick => true,
            Skill::SiphonTick => true,
            Skill::CorruptionTick => true,
            _ => false,
        }
    }

    pub fn speed(&self) -> u8 {
        match self {
            // -----------------
            // Test
            // -----------------
            Skill::TestTouch => 10,
            Skill::TestStun => 5,
            Skill::TestBlock => 10,
            Skill::TestParry => 10,
            Skill::TestSiphon => 10,

            Skill::Strike => u8::max_value(),
            Skill::StrikeII => Skill::Strike.speed(),
            Skill::StrikeIII => Skill::Strike.speed(),

            Skill::SiphonTick => Var::from(Skill::Siphon).speed(),
            Skill::DecayTick => Var::from(Skill::Decay).speed(),
            Skill::TriageTick => Var::from(Skill::Triage).speed(),
            Skill::StrangleTick => Var::from(Skill::Strangle).speed(),
            Skill::CorruptionTick => Var::from(Skill::Corrupt).speed(),

            _ => Var::from(*self).speed(),
        }
    }

    pub fn aoe(&self) -> bool {
        match self {
            Skill::Ruin => true,
            _ => false,
        }
    }

    pub fn self_targeting(&self) -> bool {
        match self {
            Skill::Block => true,
            Skill::Parry => true,
            Skill::Clutch => true,
            // Skill::Taunt => true,
            Skill::Corrupt => true,

            Skill::TestBlock => true,
            Skill::TestParry => true,
            _ => false,
        }
    }

    pub fn defensive(&self) -> bool {
        let mut rng = thread_rng();

        match self {
            Skill::Heal |
            Skill::Triage |
            Skill::Purify |
            Skill::Parry |
            Skill::Clutch |
            Skill::Scatter |
            Skill::Recharge |
            Skill::Reflect |
            Skill::Haste |
            Skill::Invert |
            Skill::Amplify |
            Skill::Hostility |
            Skill::Corrupt |
            Skill::Block => true,

            Skill::Banish => rng.gen_bool(0.5),

            _ => false,
        }
    }
}

fn touch(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    target.deal_red_damage(skill, 0)
        .into_iter()
        .for_each(|e| results.push(Resolution::new(source, target).event(e)));

    return results;
}

fn attack(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let amount = source.red_damage().pct(skill.multiplier());
    target.deal_red_damage(skill, amount)
        .into_iter()
        .for_each(|e| results.push(Resolution::new(source, target).event(e)));

    return results;
}

fn strike(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let amount = source.red_damage().pct(skill.multiplier());
    target.deal_red_damage(skill, amount)
        .into_iter()
        .for_each(|e| results.push(Resolution::new(source, target).event(e)));

    return results;
}

fn injure(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let amount = source.red_damage().pct(skill.multiplier());
    target.deal_red_damage(skill, amount)
        .into_iter()
        .for_each(|e| results.push(Resolution::new(source, target).event(e)));

    let effect = CrypEffect::new(Effect::Injured, 2);
    results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
    return results;
}

fn stun(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let effect = CrypEffect::new(Effect::Stun, skill.duration());
    results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
    return results;
}

fn sleep(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let effect = CrypEffect::new(Effect::Stun, skill.duration());
    results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
    let amount = source.green_damage().pct(skill.multiplier());
    target.deal_green_damage(skill, amount)
        .into_iter()
        .for_each(|e| results.push(Resolution::new(source, target).event(e)));

    return results;
}

fn clutch(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let effect = CrypEffect::new(Effect::Clutch, skill.duration());
    results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
    return results;
}

fn taunt(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let effect = CrypEffect::new(Effect::Taunt, skill.duration());
    results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
    return results;
}

fn throw(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let stun = CrypEffect::new(Effect::Stun, skill.duration());
    let vulnerable = CrypEffect::new(Effect::Vulnerable, skill.secondary_duration());

    results.push(Resolution::new(source, target).event(target.add_effect(skill, stun)));
    results.push(Resolution::new(source, target).event(target.add_effect(skill, vulnerable)));

    return results;
}

fn strangle(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let target_stun = CrypEffect::new(Effect::Strangle, skill.duration())
        .set_tick(Cast::new_tick(source, target, Skill::StrangleTick));

    let attacker_immunity = CrypEffect::new(Effect::Strangling, skill.duration());

    results.push(Resolution::new(source, target).event(target.add_effect(skill, target_stun)));
    results.push(Resolution::new(source, source).event(source.add_effect(skill, attacker_immunity)));
    return strangle_tick(source, target, results, Skill::StrangleTick);
}

fn strangle_tick(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let amount = source.red_damage().pct(skill.multiplier());
    target.deal_red_damage(skill, amount)
        .into_iter()
        .for_each(|e| results.push(Resolution::new(source, target).event(e)));

    // remove immunity if target ko
    if target.is_ko() {
        let i = source.effects
            .iter()
            .position(|e| e.effect == Effect::Strangling)
            .expect("no strangling on cryp");
        source.effects.remove(i);
        results.push(Resolution::new(source, source).event(Event::Removal { effect: Effect::Strangling }));
    }

    return results;
}

fn block(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let block = CrypEffect::new(Effect::Block, skill.duration());
    results.push(Resolution::new(source, target).event(target.add_effect(skill, block)));
    return results;
}

fn parry(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let effect = CrypEffect::new(Effect::Parry, skill.duration());
    results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
    return results;
}

fn riposte(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let amount = source.red_damage().pct(skill.multiplier());
    target.deal_red_damage(skill, amount)
        .into_iter()
        .for_each(|e| results.push(Resolution::new(source, target).event(e)));

    return results;
}

fn snare(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let snare = CrypEffect::new(Effect::Snare, skill.duration());
    results.push(Resolution::new(source, target).event(target.add_effect(skill, snare)));
    
    let mut s_multi: u64 = 100;
    for cs in target.skills.iter() {
        s_multi += match cs.skill.category() {
            Category::Red => 45,
            _ => 0,
        }
    }

    let amount = source.red_damage().pct(skill.multiplier()).pct(s_multi);
    target.deal_red_damage(skill, amount)
        .into_iter()
        .for_each(|e| results.push(Resolution::new(source, target).event(e)));


    return results;
}

fn slay(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let amount = source.red_damage().pct(skill.multiplier());
    let slay_events = target.deal_red_damage(skill, amount);

    for e in slay_events {
        match e {
            Event::Damage { amount, mitigation: _, colour: _, skill: _ } => {
                results.push(Resolution::new(source, target).event(e));
                let heal = source.deal_green_damage(skill, amount);
                for h in heal {
                    results.push(Resolution::new(source, source).event(h));
                };
            },
            _ => results.push(Resolution::new(source, target).event(e)),
        }
    }

    return results;
}

fn heal(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let amount = source.green_damage().pct(skill.multiplier());
    target.deal_green_damage(skill, amount)
        .into_iter()
        .for_each(|e| results.push(Resolution::new(source, target).event(e)));
    return results;
}

fn triage(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let effect = CrypEffect::new(Effect::Triage, skill.duration())
        .set_tick(Cast::new_tick(source, target, Skill::TriageTick));

    results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
    return triage_tick(source, target, results, Skill::TriageTick);
}

fn triage_tick(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let amount = source.green_damage().pct(skill.multiplier());
    target.deal_green_damage(Skill::TriageTick, amount)
        .into_iter()
        .for_each(|e| results.push(Resolution::new(source, target).event(e)));
    return results;
}

fn chaos(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let mut rng = thread_rng();
    let b_rng: u64 = rng.gen_range(0, 30);
    let amount = source.blue_damage().pct(skill.multiplier() + b_rng);
    target.deal_blue_damage(skill, amount)
        .into_iter()
        .for_each(|e| results.push(Resolution::new(source, target).event(e)));
    let r_rng: u64 = rng.gen_range(0, 30);
    let amount = source.red_damage().pct(skill.multiplier() + r_rng);
    target.deal_red_damage(skill, amount)
        .into_iter()
        .for_each(|e| results.push(Resolution::new(source, target).event(e)));
    return results;
}

fn blast(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let amount = source.blue_damage().pct(skill.multiplier());
    target.deal_blue_damage(skill, amount)
        .into_iter()
        .for_each(|e| results.push(Resolution::new(source, target).event(e)));
    return results;
}

fn amplify(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let amplify = CrypEffect::new(Effect::Amplify, skill.duration());
    results.push(Resolution::new(source, target).event(target.add_effect(skill, amplify)));
    return results;;
}

fn haste(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let effect = CrypEffect::new(Effect::Haste, skill.duration());
    results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
    return results;;
}

fn slow(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let effect = CrypEffect::new(Effect::Slow, skill.duration());
    results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
    return results;;
}

fn decay(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let wither = CrypEffect::new(Effect::Wither, skill.duration());
    let decay = CrypEffect::new(Effect::Decay, skill.duration())
        .set_tick(Cast::new_tick(source, target, Skill::DecayTick));
    
    results.push(Resolution::new(source, target).event(target.add_effect(skill, decay)));
    results.push(Resolution::new(source, target).event(target.add_effect(skill, wither)));
    return decay_tick(source, target, results, Skill::DecayTick);
}

fn decay_tick(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let amount = source.blue_damage().pct(skill.multiplier());
    target.deal_blue_damage(skill, amount)
        .into_iter()
        .for_each(|e| results.push(Resolution::new(source, target).event(e)));
    return results;
}

// corrupt is the buff effect
// when attacked it runs corruption and applies a debuff
fn corrupt(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let effect = CrypEffect::new(Effect::Corrupt, skill.duration());
    results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
    return results;;
}

fn corruption(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let effect = CrypEffect::new(Effect::Corruption, skill.secondary_duration())
        .set_tick(Cast::new_tick(source, target, Skill::CorruptionTick));

    results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
    return corruption_tick(source, target, results, Skill::CorruptionTick);
}

fn corruption_tick(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let amount = source.blue_damage().pct(skill.multiplier());
    target.deal_blue_damage(skill, amount)
        .into_iter()
        .for_each(|e| results.push(Resolution::new(source, target).event(e)));
    return results;
}

fn ruin(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    // results.push(Resolution::new(source, target).event(Event::Skill { skill }));
    let effect = CrypEffect::new(Effect::Ruin, skill.duration());
    results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
    return results;;
}


fn hex(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let hex = CrypEffect::new(Effect::Hex, skill.duration());
    results.push(Resolution::new(source, target).event(target.add_effect(skill, hex)));
    return results;;
}

fn hostility(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let effect = CrypEffect::new(Effect::Hostility, skill.duration());
    results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
    return results;;
}

fn hatred(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, reflect_skill: Skill, amount: u64, skill: Skill) -> Resolutions {
    let effect = CrypEffect::new(Effect::Hatred, skill.secondary_duration())
        .set_meta(EffectMeta::AddedDamage(amount));

    results.push(Resolution::new(source, target).event(target.add_effect(reflect_skill, effect)));
    return results;;
}

fn curse(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let curse = CrypEffect::new(Effect::Curse, skill.duration());
    results.push(Resolution::new(source, target).event(target.add_effect(Skill::Curse, curse)));
    return results;;
}

fn impurity(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let effect = CrypEffect::new(Effect::Impurity, skill.duration());
    results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
    return results;;
}

fn invert(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let effect = CrypEffect::new(Effect::Invert, skill.duration());
    results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
    return results;;
}

fn reflect(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let effect = CrypEffect::new(Effect::Reflect, skill.duration());
    results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
    
    let blue_amount = source.blue_damage().pct(skill.multiplier());
    results.push(Resolution::new(source, target).event(target.recharge(skill, 0, blue_amount)));

    return results;;
}

fn recharge(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let red_amount = source.red_damage().pct(skill.multiplier());
    let blue_amount = source.blue_damage().pct(skill.multiplier());

    results.push(Resolution::new(source, target).event(target.recharge(skill, red_amount, blue_amount)));
    return results;
}

fn siphon(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let siphon = CrypEffect::new(Effect::Siphon, skill.duration())
        .set_tick(Cast::new_tick(source, target, Skill::SiphonTick));

    results.push(Resolution::new(source, target).event(target.add_effect(skill, siphon)));
    return siphon_tick(source, target, results, Skill::SiphonTick);
}

fn siphon_tick(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let amount = source.blue_damage().pct(skill.multiplier());
    let siphon_events = target.deal_blue_damage(Skill::SiphonTick, amount);

    for e in siphon_events {
        match e {
            Event::Damage { amount, mitigation: _, colour: _, skill: _ } => {
                results.push(Resolution::new(source, target).event(e));
                let heal = source.deal_green_damage(Skill::SiphonTick, amount);
                for h in heal {
                    results.push(Resolution::new(source, source).event(h));
                };
            },
            _ => results.push(Resolution::new(source, target).event(e)),
        }
    }

    return results;
}

fn scatter(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let scatter = CrypEffect::new(Effect::Scatter, skill.duration());
    results.push(Resolution::new(source, target).event(target.add_effect(skill, scatter)));
    return results;
}

fn silence(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let silence = CrypEffect::new(Effect::Silence, skill.duration());
    results.push(Resolution::new(source, target).event(target.add_effect(skill, silence)));
    
    let mut s_multi: u64 = 100;
    for cs in target.skills.iter() {
        s_multi += match cs.skill.category() {
            Category::Blue => 45,
            _ => 0,
        }
    }

    let amount = source.blue_damage().pct(skill.multiplier()).pct(s_multi);
    target.deal_blue_damage(skill, amount)
        .into_iter()
        .for_each(|e| results.push(Resolution::new(source, target).event(e)));

    return results;
}

fn purge(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    while let Some(i) = target.effects
        .iter()
        .position(|ce| [Category::Buff, Category::Buff].contains(&ce.effect.category())) {
            let ce = target.effects.remove(i);
            results.push(Resolution::new(source, target).event(Event::Removal { effect: ce.effect }));
        }

    return results;
}

fn purify(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    results.push(Resolution::new(source, target).event(Event::Skill { skill }));
    let amount = source.green_damage().pct(skill.multiplier());
    while let Some(i) = target.effects
        .iter()
        .position(|ce| [Category::Debuff, Category::Debuff].contains(&ce.effect.category())) {
            let ce = target.effects.remove(i);
            results.push(Resolution::new(source, target).event(Event::Removal { effect: ce.effect }));
            target.deal_green_damage(skill, amount)
                .into_iter()
                .for_each(|e| results.push(Resolution::new(source, target).event(e)));
        }

    return results;
}

fn banish(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
    let banish = CrypEffect::new(Effect::Banish, skill.duration());
    results.push(Resolution::new(source, target).event(target.add_effect(skill, banish)));
    return results;
}

#[cfg(test)]
mod tests {
    use skill::*;

    #[test]
    fn heal_test() {
        let mut x = Cryp::new()
            .named(&"muji".to_string())
            .learn(Skill::Heal);

        let mut y = Cryp::new()
            .named(&"camel".to_string())
            .learn(Skill::Heal);

        x.deal_red_damage(Skill::Attack, 5);

        heal(&mut y, &mut x, vec![], Skill::Heal);
    }

    #[test]
    fn decay_test() {
        let mut x = Cryp::new()
            .named(&"muji".to_string());

        let mut y = Cryp::new()
            .named(&"camel".to_string());

        decay(&mut x, &mut y, vec![], Skill::Decay);

        assert!(y.effects.iter().any(|e| e.effect == Effect::Decay));

        y.reduce_effect_durations();
        let _decay = y.effects.iter().find(|e| e.effect == Effect::Decay);
        // assert!(y.green_life() == y.green_life().saturating_sub(decay.unwrap().tick.unwrap().amount));
    }

    #[test]
    fn block_test() {
        let mut x = Cryp::new()
            .named(&"muji".to_string());

        let mut y = Cryp::new()
            .named(&"camel".to_string());

        // ensure it doesn't have 0 pd
        x.red_damage.force(100);
        y.green_life.force(500);

        block(&mut y.clone(), &mut y, vec![], Skill::Block);
        assert!(y.effects.iter().any(|e| e.effect == Effect::Block));

        let mut results = attack(&mut x, &mut y, vec![], Skill::TestAttack);

        let Resolution { source: _, target: _, event } = results.remove(0);
        match event {
            Event::Damage { amount, mitigation: _, colour: _, skill: _ } => assert_eq!(amount, 50),
            _ => panic!("not damage"),
        };
    }

    #[test]
    fn clutch_test() {
        let mut x = Cryp::new()
            .named(&"muji".to_string());

        let mut y = Cryp::new()
            .named(&"camel".to_string());

        x.red_damage.force(10000000000000); // multiplication of int max will cause overflow

        clutch(&mut y.clone(), &mut y, vec![], Skill::Clutch);
        assert!(y.affected(Effect::Clutch));

        let mut results = attack(&mut x, &mut y, vec![], Skill::Attack);
        assert!(y.green_life() == 1);

        let Resolution { source: _, target: _, event } = results.remove(0);
        match event {
            Event::Damage { amount, mitigation: _, colour: _, skill: _ } => assert_eq!(amount, 1023),
            _ => panic!("not damage"),
        };
    }

    #[test]
    fn injure_test() {
        let mut x = Cryp::new()
            .named(&"muji".to_string());

        let mut y = Cryp::new()
            .named(&"camel".to_string());

        resolve(Skill::Injure, &mut x, &mut y, vec![]);
        assert!(y.immune(Skill::Heal).is_some());
        // resolutions = heal(&mut y.clone(), &mut y, resolutions);
    }

    #[test]
    fn invert_test() {
        let mut x = Cryp::new()
            .named(&"muji".to_string());

        let mut y = Cryp::new()
            .named(&"camel".to_string());

        // give red shield but reduce to 0
        y.red_life.force(64);
        y.red_life.reduce(64);
        x.red_damage.force(256 + 64);
        invert(&mut y.clone(), &mut y, vec![], Skill::Invert);
        assert!(y.affected(Effect::Invert));

        // heal should deal green damage
        heal(&mut x, &mut y, vec![], Skill::TestHeal);
        assert!(y.green_life() == 768);

        // attack should heal and recharge red shield
        let mut results = attack(&mut x, &mut y, vec![], Skill::TestAttack);
        assert!(y.green_life() == 1024);

        match results.remove(0).event {
            Event::Inversion { skill } => assert_eq!(skill, Skill::TestAttack),
            _ => panic!("not inversion"),
        };

        match results.remove(0).event {
            Event::Healing { skill: _, overhealing: _, amount } => assert_eq!(amount, 256),
            _ => panic!("not healing from inversion"),
        };

        match results.remove(0).event {
            Event::Recharge { skill: _, red, blue: _ } => assert_eq!(red, 64),
            _ => panic!("not recharge from inversion"),
        };
    }

    #[test]
    fn reflect_test() {
        let mut x = Cryp::new()
            .named(&"muji".to_string());

        let mut y = Cryp::new()
            .named(&"camel".to_string());

        reflect(&mut y.clone(), &mut y, vec![], Skill::Reflect);
        assert!(y.affected(Effect::Reflect));

        let mut results = vec![];
        results = resolve(Skill::TestAttack, &mut x, &mut y, results);

        assert!(x.green_life() == 768);

        let Resolution { source: _, target: _, event } = results.remove(0);
        match event {
            Event::Reflection { skill } => assert_eq!(skill, Skill::TestAttack),
            _ => panic!("not reflection"),
        };

        let Resolution { source: _, target: _, event } = results.remove(0);
        match event {
            Event::Damage { amount, mitigation: _, colour: _, skill: _ } => assert_eq!(amount, 256),
            _ => panic!("not damage"),
        };
    }

    #[test]
    fn siphon_test() {
        let mut x = Cryp::new()
            .named(&"muji".to_string());

        let mut y = Cryp::new()
            .named(&"camel".to_string());

        x.green_life.reduce(512);

        let mut results = resolve(Skill::Siphon, &mut x, &mut y, vec![]);

        assert!(y.affected(Effect::Siphon));
        assert!(x.green_life() == (512 + 256.pct(Skill::SiphonTick.multiplier())));

        let Resolution { source: _, target: _, event } = results.remove(0);
        match event {
            Event::Effect { effect, skill: _, duration: _ } => assert_eq!(effect, Effect::Siphon),
            _ => panic!("not siphon"),
        };

        let Resolution { source: _, target: _, event } = results.remove(0);
        match event {
            Event::Damage { amount, skill: _, mitigation: _, colour: _} => assert_eq!(amount, 256.pct(Skill::SiphonTick.multiplier())),
            _ => panic!("not damage siphon"),
        };

        let Resolution { source: _, target, event } = results.remove(0);
        match event {
            Event::Healing { amount, skill: _, overhealing: _ } => {
                assert_eq!(amount, 256.pct(Skill::SiphonTick.multiplier()));
                assert_eq!(target.id, x.id);
            },
            _ => panic!("not healing"),
        };
    }


    #[test]
    fn corrupt_test() {
        let mut x = Cryp::new()
            .named(&"muji".to_string());

        let mut y = Cryp::new()
            .named(&"camel".to_string());

        corrupt(&mut y.clone(), &mut y, vec![], Skill::Corrupt);
        assert!(y.affected(Effect::Corrupt));

        resolve(Skill::Attack, &mut x, &mut y, vec![]);

        assert!(x.affected(Effect::Corruption));
    }

    #[test]
    fn hatred_test() {
        let mut x = Cryp::new()
            .named(&"muji".to_string());

        let mut y = Cryp::new()
            .named(&"camel".to_string());

        hostility(&mut y.clone(), &mut y, vec![], Skill::Hostility);
        assert!(y.affected(Effect::Hostility));

        resolve(Skill::TestAttack, &mut x, &mut y, vec![]);

        assert!(y.affected(Effect::Hatred));

        let mut results = resolve(Skill::TestAttack, &mut y, &mut x, vec![]);

        let Resolution { source: _, target: _, event } = results.remove(0);
        match event {
            Event::Damage { amount, skill: _, mitigation: _, colour: _} => assert_eq!(amount, 512),
            _ => panic!("not damage hatred"),
        };
    }

    #[test]
    fn triage_test() {
        let mut x = Cryp::new()
            .named(&"muji".to_string());

        let mut y = Cryp::new()
            .named(&"pretaliation".to_string());

        // ensure it doesn't have 0 sd
        x.blue_damage.force(50);

        // remove all mitigation
        y.red_life.force(0);
        y.blue_life.force(0);

        y.deal_red_damage(Skill::Attack, 5);
        let prev_hp = y.green_life();

        triage(&mut x, &mut y, vec![], Skill::Triage);

        assert!(y.effects.iter().any(|e| e.effect == Effect::Triage));
        assert!(y.green_life() > prev_hp);
    }

    #[test]
    fn recharge_test() {
        let mut x = Cryp::new()
            .named(&"muji".to_string());

        let mut y = Cryp::new()
            .named(&"pretaliation".to_string());

        y.red_life.force(50);
        y.blue_life.force(50);

        y.deal_red_damage(Skill::Attack, 5);
        y.deal_blue_damage(Skill::Blast, 5);

        let mut results = recharge(&mut x, &mut y, vec![], Skill::Recharge);

        let Resolution { source: _, target: _, event } = results.remove(0);
        match event {
            Event::Recharge { red, blue, skill: _ } => {
                assert!(red == 5);
                assert!(blue == 5);
            }
            _ => panic!("result was not recharge"),
        }
    }


    #[test]
    fn silence_test() {
        let mut x = Cryp::new()
            .named(&"muji".to_string());

        silence(&mut x.clone(), &mut x, vec![], Skill::Silence);
        assert!(x.effects.iter().any(|e| e.effect == Effect::Silence));
        assert!(x.disabled(Skill::Silence).is_some());
    }

    #[test]
    fn amplify_test() {
        let mut x = Cryp::new()
            .named(&"muji".to_string());

        x.blue_damage.force(50);

        amplify(&mut x.clone(), &mut x, vec![], Skill::Amplify);
        assert!(x.effects.iter().any(|e| e.effect == Effect::Amplify));
        assert_eq!(x.blue_damage(), 100);
    }

    #[test]
    fn purify_test() {
        let mut x = Cryp::new()
            .named(&"muji".to_string());

        decay(&mut x.clone(), &mut x, vec![], Skill::Decay);
        assert!(x.effects.iter().any(|e| e.effect == Effect::Decay));

        purify(&mut x.clone(), &mut x, vec![], Skill::Purify);
        assert!(!x.effects.iter().any(|e| e.effect == Effect::Decay));
    }
}






// pub enum Skill {
//     Attack,

//     // -----------------
//     // Nature
//     // -----------------
//     Block, // reduce damage
//     Parry, // avoid all damage
//     Snare,

//     Paralyse,

//     Strangle, // physical dot and disable
//     Strike,
//     Stun,
//     // Evade, // actively evade


//     // -----------------
//     // Technology
//     // -----------------
//     Replicate,
//     Swarm,
//     Orbit,
//     Repair,
//     Scan, // track?

//     // -----------------
//     // Nonviolence
//     // -----------------
//     Heal,
//     Triage, // hot
//     TriageTick,
//     Throw, // no damage stun, adds vulnerable
//     Charm,
//     Calm,
//     Rez,

//     // Sleep,
//     // Nightmare,

//     // -------------------
//     // Destruction
//     // -------------------
//     Blast,
//     Amplify,
//     Decay, // dot
//     DecayTick, // dot
//     Siphon,
//     SiphonTick,
//     Curse,
//     Corrupt, // aoe dot
//     Ruin, // aoe

//     // -----------------
//     // Purity
//     // -----------------
//     Empower,
//     Slay,
//     Shield,
//     Silence,
//     Inquiry,
//     Purify,
//     Purge,
//     // Precision,

//     // -----------------
//     // Chaos
//     // -----------------
//     Banish,
//     Hex,
//     Fear,
//     Taunt,
//     Pause, // speed slow
//     Haste,
//     Slow,

//     // used by tests, no cd, no damage
//     TestTouch,
//     TestStun,
//     TestBlock,
//     TestParry,
//     TestSiphon,
// }
