extern crate piston_window;
extern crate find_folder;
extern crate time;
extern crate graphics;
extern crate glob;
extern crate yaml_rust;
extern crate rand;
use std::ops::{Sub};
use regex::Regex;
use rustc_serialize::json;
use crypto::md5::Md5;
use crypto::digest::Digest;
use std::f64::*;
use self::yaml_rust::{YamlLoader, YamlEmitter, Yaml};
use std::fs::File;
use std::io::prelude::*;
use piston_window::*;
use std::collections::{HashMap, HashSet};
use lang::*;
use terrain;
use self::glob::glob;

#[derive(Clone, Debug)]
pub enum DeadTab {
   None,
   Memorial,
   New(Option<String>),
   Load(Option<String>,i64)
}

pub struct WindowState {
   pub tab: String,
   pub dead_tab: DeadTab,
   pub clicked: Option<String>,
   pub pressed: Option<String>,
   pub click_held: HashMap<String,String>,
   pub press_held: HashMap<String,String>,
   pub search_has_focus: bool,
   pub map_has_focus: bool,
   pub item_has_focus: i32,
   pub equip_has_focus: i32,
   pub spell_has_focus: String,
   pub do_pickup: bool,
   pub f_em: f64,
   pub i_em: u32,
   pub f_width: f64,
   pub i_width: u32,
   pub f_height: f64,
   pub i_height: u32,
   pub cursor: [f64; 2],
   pub monofont: Glyphs,
   pub macondo: Glyphs,
   pub textures: HashMap<String,G2dTexture>,
   pub savefiles: HashMap<String,Vec<(i64,String,String)>>
}
impl WindowState {
   pub fn load_savefile(&mut self, gm: &mut GameState, path: String) {
      //Load save data
      let fullpath = format!("src/assets/saves/{}", path);
      let mut file = File::open(fullpath.clone()).unwrap();
      let mut encoded = String::new();
      file.read_to_string(&mut encoded).unwrap();
      let decoded: GameSave = json::decode(&encoded.as_str()).unwrap();

      //Move data into live game
      gm.board_class = decoded.board_class;       
      gm.board_level = decoded.board_level;
      gm.turn_count = decoded.turn_count;
      gm.agent_name = decoded.avatar_name;
      gm.agent_class = decoded.avatar_class;
      gm.agent_inventory = decoded.inventory;
      gm.agent_equipped = decoded.equipped;
      gm.agent_library = decoded.library;
      gm.agent_stats = HashMap::new();
      gm.agent_stats.insert(0, decoded.avatar.clone());

      //Generate new board
      let board_class = gm.board_class.clone();
      let board_level = gm.board_level;
      gm.newboard(board_class, board_level);

      //Jump into gameplay
      self.tab = "Map".to_string();
      self.dead_tab = DeadTab::None;
      self.search_has_focus = false;
      self.spell_has_focus = "".to_string();
      self.map_has_focus = false;
      self.item_has_focus = -1;
      gm.alive = true;
   }
   pub fn load_saves(&mut self) {
      self.savefiles.clear();
      for entry in glob("src/assets/saves/*.json").expect("Failed to read glob pattern") {
         match entry {
            Ok(path) => {
               let ps = format!("{}",path.as_path().file_name().unwrap().to_str().unwrap());
               let mut split = ps.split("_");
               let vec: Vec<&str> = split.collect();
               let charname = vec[0].to_string();
               let charclass = vec[1].to_string();
               let charstep = vec[2].to_string().parse::<i64>().unwrap();
               if !self.savefiles.contains_key(&charclass) {
                  self.savefiles.insert(charclass.clone(), Vec::new());
               }
               let mut v = self.savefiles.get(&charclass).unwrap().clone();
               v.push((charstep, charname.clone(), ps.clone()));
               self.savefiles.insert(charclass.clone(), v);
            },
            Err(e) => println!("{:?}", e),
         }
      }
      let sf = self.savefiles.clone();
      for (cls, v) in &sf {
         let mut v = v.clone();
         v.sort_by(|a, b| b.0.cmp(&a.0) );
         self.savefiles.insert(cls.clone(), v);
      }
   }
   pub fn texture(&self, k: String) -> G2dTexture {
      let k = if k=="".to_string() { "unknown.png".to_string() } else { k };
      match self.textures.get(k.as_str()) {
         Some(t) => { t.clone() }
         None => { panic!("Could not find texture: {}", k); }
      }
   }
   pub fn pressed(&self, k: String) -> bool {
      self.press_held.contains_key(&k)
   }
   fn load_yaml(path: String) -> Yaml {
      let mut file = File::open(path.as_str()).unwrap();
      let mut contents = String::new();
      file.read_to_string(&mut contents).unwrap();
      YamlLoader::load_from_str(contents.as_str()).unwrap()[0].clone()
   }
   pub fn new(w: &PistonWindow) -> WindowState {
      let size = w.size();
      let mut textures: HashMap<String,G2dTexture> = HashMap::new();

      let assets = find_folder::Search::ParentsThenKids(3, 3)
         .for_folder("assets").unwrap();

      let ref font = assets.join("FiraMono-Regular.ttf");
      let factory = w.factory.clone();
      let monofont = Glyphs::new(font, factory).unwrap();

      let ref font = assets.join("Macondo-Regular.ttf");
      let factory = w.factory.clone();
      let macondo = Glyphs::new(font, factory).unwrap();

      for entry in glob("src/assets/textures/*.png").expect("Failed to read glob pattern") {
         match entry {
            Ok(path) => {
               let ps = format!("{}",path.display());
               let mut split = ps.split("/");
               let vec: Vec<&str> = split.collect();
               let pathname = vec[vec.len()-1];
               let mut factory = w.factory.clone();
               let texture = Texture::from_path(
                  &mut factory,
                  &path,
                  Flip::None,
                  &TextureSettings::new()
               ).unwrap();
               textures.insert(pathname.to_string(), texture);
            },
            Err(e) => println!("{:?}", e),
         }
      }

      WindowState {
         tab: "Map".to_string(),
         dead_tab: DeadTab::None,
         search_has_focus: false,
         map_has_focus: false,
         clicked: None,
         pressed: None,
         click_held: HashMap::new(),
         press_held: HashMap::new(),
         do_pickup: true,
         item_has_focus: -1,
         equip_has_focus: -1,
         spell_has_focus: "".to_string(),
         f_em: 24.0,
         i_em: 24,
         i_width: size.width,
         f_width: size.width as f64,
         i_height: size.height,
         f_height: size.height as f64,
         cursor: [0.0, 0.0],
         monofont: monofont,
         macondo: macondo,
         textures: textures,
         savefiles: HashMap::new()
      }
   }
   pub fn f_halfwidth(&self) -> f64 { self.f_width / 2.0 }
}

#[derive(Clone, Debug, RustcEncodable, RustcDecodable)]
pub struct Creation {
   //quick stats
   pub max_health: i64,
   pub cur_health: i64,
   pub max_energy: i64,
   pub cur_energy: i64,
   pub max_mana: i64,
   pub cur_mana: i64,

   //base stats
   pub strength: i64,
   pub agility: i64,
   pub intelligence: i64,
   pub speed: i64,
   pub vigor: i64,
   pub luck: i64,
   pub charm: i64,

   //weapon stats
   pub force: i64,
   pub accuracy: i64,
   pub enhancement: i64,

   //armor stats
   pub protect: i64,
   pub deflect: i64,
   pub absorb: i64
}
impl Creation {
   pub fn new() -> Creation {
      Creation {
         max_health: 0, cur_health: 0,
         max_energy: 0, cur_energy: 0,
         max_mana: 0, cur_mana: 0,

         strength: 0, agility: 0, intelligence: 0,
         speed: 0,
         vigor: 0, luck: 0, charm: 0,

         force: 0, accuracy: 0, enhancement: 0,
         protect: 0, deflect: 0, absorb: 0,
      }      
   }
   pub fn render(&self) -> RenderedCreation {
      RenderedCreation {
         max_health: self.max_health, cur_health: self.cur_health,
         max_energy: self.max_energy, cur_energy: self.cur_energy,
         max_mana: self.max_mana, cur_mana: self.cur_mana,

         strength: self.strength, agility: self.agility, intelligence: self.intelligence,
         speed: self.speed,
         vigor: self.vigor, luck: self.luck, charm: self.charm,

         force: self.force, accuracy: self.accuracy, enhancement: self.enhancement,
         protect: self.protect, deflect: self.deflect, absorb: self.absorb,
      }
   }
}
impl Sub for Creation {
   type Output = Creation;
   fn sub(self, other: Creation) -> Creation {
      let r = Creation {
         max_health: self.max_health - other.max_health,
         cur_health: self.cur_health - other.cur_health,
         max_energy: self.max_energy - other.max_energy,
         cur_energy: self.cur_energy - other.cur_energy,
         max_mana: self.max_mana - other.max_mana,
         cur_mana: self.cur_mana - other.cur_mana,

         strength: self.strength - other.strength,
         agility: self.agility - other.agility,
         intelligence: self.intelligence - other.intelligence,
         speed: self.speed - other.speed,
         vigor: self.vigor - other.vigor,
         luck: self.luck - other.luck,
         charm: self.charm - other.charm,

         force: self.force - other.force,
         accuracy: self.accuracy - other.accuracy,
         enhancement: self.enhancement - other.enhancement,
         protect: self.protect - other.protect,
         deflect: self.deflect - other.deflect,
         absorb: self.absorb - other.absorb
      };
      r
   }
}


#[derive(Clone, Debug, RustcEncodable, RustcDecodable)]
pub struct RenderedCreation {
   //quick stats
   pub max_health: i64,
   pub cur_health: i64,
   pub max_energy: i64,
   pub cur_energy: i64,
   pub max_mana: i64,
   pub cur_mana: i64,

   //base stats
   pub strength: i64,
   pub agility: i64,
   pub intelligence: i64,
   pub speed: i64,
   pub vigor: i64,
   pub luck: i64,
   pub charm: i64,

   //weapon stats
   pub force: i64,
   pub accuracy: i64,
   pub enhancement: i64,

   //armor stats
   pub protect: i64,
   pub deflect: i64,
   pub absorb: i64
}
impl RenderedCreation {
   pub fn normalize(&self) -> Creation {
      Creation {
         max_health: self.max_health, cur_health: self.cur_health,
         max_energy: self.max_energy, cur_energy: self.cur_energy,
         max_mana: self.max_mana, cur_mana: self.cur_mana,

         strength: self.strength, agility: self.agility, intelligence: self.intelligence,
         speed: self.speed,
         vigor: self.vigor, luck: self.luck, charm: self.charm,

         force: self.force, accuracy: self.accuracy, enhancement: self.enhancement,
         protect: self.protect, deflect: self.deflect, absorb: self.absorb,
      }
   }
}

#[derive(RustcEncodable, RustcDecodable, Debug)]
pub struct GameSave {
   board_class: String,
   board_level: i64,
   turn_count: i64,
   avatar_name: String,
   avatar_class: String,
   avatar: Creation,
   inventory: Vec<(Sentence,i64)>,
   equipped: Vec<Sentence>,
   library: HashMap<String,Sentence>
}

#[derive(Clone)]
pub struct GameState {
   pub alive: bool,
   pub turn_count: i64,
   pub turn_die: i64,
   pub explode: f64,
   pub dialogue_index: usize,
   pub dialogue_content: Vec<String>,
   pub terrain_info: Yaml,
   pub item_info: Yaml,
   pub spell_info: Yaml,
   pub npc_info: Yaml,
   pub start_time: f64,
   pub chat_bar: String,
   pub chat_log: Inbox,
   pub class_list: Vec<String>,
   pub timers: HashMap<String,f64>,
   pub agent_name: String,
   pub agent_class: String,
   pub agent_move_target: Option<(i32, i32)>,
   pub agent_position: (i32, i32),
   pub agent_inventory: Vec<(Sentence,i64)>,
   pub agent_equipped: Vec<Sentence>,
   pub agent_engaged: Option<(i32,i32,Sentence)>,
   pub agent_library: HashMap<String,Sentence>,
   pub agent_stats: HashMap<i64,Creation>,
   pub board_level: i64,
   pub board_class: String,
   pub board_width: i32,
   pub board_height: i32,
   pub board_terrain: Vec<String>,
   pub board_creature: Vec<Option<Sentence>>,
   pub board_items: Vec<Option<Vec<Sentence>>>,
   pub creature_population: HashMap<String,Vec<(u64,String)>>,
   pub item_population: HashMap<String,Vec<(u64,String)>>,
   pub creature_registry: HashSet<i64>
}
impl GameState {
   pub fn save(&mut self) {
      let s = self.agent_raw_stats();
      let save = GameSave {
         turn_count: self.turn_count,
         board_class: self.board_class.clone(),
         board_level: self.board_level,
         avatar_name: self.agent_name.clone(),
         avatar_class: self.agent_class.clone(),
         avatar: s,
         equipped: self.agent_equipped.clone(),
         inventory: self.agent_inventory.clone(),
         library: self.agent_library.clone()
      };
      let encoded = json::encode(&save).unwrap();

      let mut hasher = Md5::new();
      hasher.input_str(&encoded.as_str());
      let hash = hasher.result_str();

      let fs = format!("src/assets/saves/{}_{}_{}_{}.json", self.agent_name, self.agent_class, self.turn_count, hash);
      let mut file = File::create(fs.as_str()).unwrap();
      let bs = encoded.as_bytes();
      file.write_all(bs);
   }
   pub fn newboard(&mut self, board_class: String, board_level: i64) {
      self.board_class = board_class;
      self.board_level = board_level;
      self.board_width = 100;
      self.board_height = 100;
      let (agent_position, board_terrain) = terrain::generate(self.board_width, self.board_height, self.board_class.clone());
      let board_size = (self.board_width * self.board_height) as usize;
      self.dialogue_index = 0;
      self.dialogue_content = vec![
         "...".to_string(),
         "You hear a voice calling you home".to_string()
      ];
      self.chat_bar = "".to_string();
      self.chat_log = Vec::new();
      self.agent_position = agent_position;
      self.board_terrain = board_terrain;
      self.board_creature = vec![None; board_size];
      self.board_items = vec![None; board_size];
      self.creature_population = HashMap::new();
      self.item_population = HashMap::new();
      self.creature_registry = HashSet::new();

      terrain::generate_items(self);
      terrain::generate_creatures(self);
   }
   pub fn newgame(&mut self, classname: String) {
      let board_width = 100;
      let board_height = 100;
      let board_class = "jungle".to_string();
      let (agent_position, board_terrain) = terrain::generate(board_width, board_height, board_class.clone());
      let board_size = (board_width * board_height) as usize;

      self.alive = true;
      self.turn_die = 0;
      self.turn_count = 0;
      self.explode = 1.2f64;
      self.dialogue_index = 0;
      self.dialogue_content = vec![
         "...".to_string(),
         "You hear a voice calling you home".to_string()
      ];
      self.start_time = time::precise_time_s();
      self.chat_bar = "".to_string();
      self.chat_log = Vec::new();
      self.timers = HashMap::new();
      self.agent_class = classname.clone();
      self.agent_name = "Anonymous".to_string();
      self.agent_move_target = None;
      self.agent_position = agent_position;
      self.agent_inventory = vec![ (vec![Word::Audio("handcloth".to_string())], 1),
                                (vec![Word::Audio("apple".to_string())], 3),
                                (vec![Word::Audio("hemlock_seed".to_string())], 2) ];
      self.agent_equipped = Vec::new();
      self.agent_engaged = None;
      self.agent_stats = HashMap::new();
      self.board_level = 1;
      self.board_class = board_class;
      self.board_width = board_width;
      self.board_height = board_height;
      self.board_terrain = board_terrain;
      self.board_creature = vec![None; board_size];
      self.board_items = vec![None; board_size];
      self.creature_population = HashMap::new();
      self.item_population = HashMap::new();
      self.creature_registry = HashSet::new();

      terrain::generate_items(self);
      terrain::generate_creatures(self);
   }
   pub fn new() -> GameState {
      let board_width = 100;
      let board_height = 100;
      let board_class = "jungle".to_string();
      let (agent_position, board_terrain) = terrain::generate(board_width, board_height, board_class.clone());
      let board_size = (board_width * board_height) as usize;

      let mut gm = GameState {
         alive: false,
         turn_die: 0,
         turn_count: 0,
         explode: 1.2f64,
         dialogue_index: 0,
         dialogue_content: vec![
            "...".to_string(),
            "You hear a voice calling you home".to_string()
         ],
         class_list: vec!["warrior".to_string(), "brawler".to_string(), "crusader".to_string(), "assassin".to_string(),
          "vagabond".to_string(), "druid".to_string(), "rogue".to_string(), "monk".to_string(), "spellthief".to_string(), "mage".to_string()],
         item_info: WindowState::load_yaml("src/assets/items.yaml".to_string()),
         spell_info: WindowState::load_yaml("src/assets/spells.yaml".to_string()),
         terrain_info: WindowState::load_yaml("src/assets/terrain.yaml".to_string()),
         npc_info: WindowState::load_yaml("src/assets/npc.yaml".to_string()),
         start_time: time::precise_time_s(),
         chat_bar: "".to_string(),
         chat_log: Vec::new(),
         timers: HashMap::new(),
         agent_class: "vagabond".to_string(),
         agent_name: "Anonymous".to_string(),
         agent_move_target: None,
         agent_position: agent_position,
         agent_inventory: vec![ (vec![Word::Audio("handcloth".to_string())], 1),
                                (vec![Word::Audio("apple".to_string())], 3),
                                (vec![Word::Audio("hemlock_seed".to_string())], 2) ],
         agent_equipped: Vec::new(),
         agent_library: HashMap::new(),
         agent_engaged: None,
         agent_stats: HashMap::new(),
         board_level: 1,
         board_class: board_class,
         board_width: board_width,
         board_height: board_height,
         board_terrain: board_terrain,
         board_creature: vec![None; board_size],
         board_items: vec![None; board_size],
         creature_population: HashMap::new(),
         item_population: HashMap::new(),
         creature_registry: HashSet::new()
      };
      terrain::generate_items(&mut gm);
      terrain::generate_creatures(&mut gm);
      gm
   }
   pub fn say(&mut self, s: String) {
      let c = Statement {
        timestamp: time::precise_time_s(),
        source: Word::Pause,
        content: vec![Word::Audio(
           s
        )],
        reach: 0
      };
      self.chat_log.push(c);
   }
   pub fn item_verb(&mut self, item: &Sentence) -> String {
      match item[0].clone() {
         Word::Audio(name) => {
            let on_use = self.item_info[name.as_str()]["verb"].as_str();
            match on_use {
               Some(s) => { s.to_string() }
               None => { "Use".to_string() }
            }
         }
         _ => { "Use".to_string() }
      }
   }
   pub fn item_do_use(&mut self, item: &Sentence) {
      let effects = self.item_use(item);
      let mut a = self.agent_stats();
      let mut isay = "".to_string();
      for ei in 0..effects.len() {
         let (effect, param) = effects[ei].clone();
         if effect == "death".to_string() {
            a.cur_health = 0;
            self.alive = false;
         } else if effect == "health".to_string() {
            a.cur_health += param.parse::<i64>().unwrap();
            if a.cur_health < 0 { a.cur_health = 0; }
            else if a.cur_health > a.max_health { a.cur_health = a.max_health; }
         } else if effect == "energy".to_string() {
            a.cur_energy += param.parse::<i64>().unwrap();
            if a.cur_energy < 0 { a.cur_energy = 0; }
            else if a.cur_energy > a.max_energy { a.cur_energy = a.max_energy; }
         } else if effect == "mana".to_string() {
            a.cur_mana += param.parse::<i64>().unwrap();
            if a.cur_mana < 0 { a.cur_mana = 0; }
            else if a.cur_mana > a.max_mana { a.cur_mana = a.max_mana; }
         } else if effect == "study".to_string() {
            let element = match item[2].clone() {
               Word::Audio(ref e) => { e.clone() }
               et => { panic!("expected element, got: {:?}", et); }
            };
            let spellbook = self.spell_info.as_hash().unwrap();
            let mut elemented: Vec<String> = Vec::new();
            for (spellname, spellconfig) in spellbook {
               let spellname = spellname.as_str().unwrap().to_string();
               if spellconfig["element"].as_str().unwrap() == element.as_str() &&
                  !self.agent_library.contains_key(&element) {
                  elemented.push(spellname.clone());
               }
            }
            if elemented.len() > 0 {
               let ei = rand::random::<u64>() % (elemented.len() as u64);
               let learn = elemented[ei as usize].clone();
               self.agent_library.insert(learn, Vec::new());
            } else {
               isay = "I don't understand any of this".to_string();
            }
         } else {
            panic!("effect not matched: {} = {}", effect, param);
         }
      }
      self.agent_stats.insert(0, a.normalize());
      for inv_i in 0..self.agent_inventory.len() {
         let (itm, cnt) = self.agent_inventory[inv_i].clone();
         if equals(&itm, item) {
            if cnt > 1 {
               self.agent_inventory[inv_i] = (itm, cnt-1);
            } else {
               self.agent_inventory.remove(inv_i);
            }
            break;
         }
      }
      if isay.len()>0 { self.isay(isay) }
   }
   pub fn item_use(&mut self, item: &Sentence) -> Vec<(String,String)> {
      match item[0].clone() {
         Word::Audio(name) => {
            let on_use = self.item_info[name.as_str()]["use"].as_hash();
            match on_use {
               Some(ls) => {
                  let mut u = Vec::new();
                  for (key, value) in ls.iter() {
                     let key = key.as_str().unwrap().to_string();
                     let v = match *value {
                        Yaml::Real(ref s) => { s.clone() }
                        Yaml::Integer(ref i) => { format!("{}", i) }
                        Yaml::String(ref s) => { s.clone() }
                        Yaml::Boolean(ref b) => { format!("{}", b) }
                        Yaml::Null => { "null".to_string() }
                        ref val => { format!("{:?}", val) }
                     };
                     u.push((key, v));
                  }
                  u
               }
               None => { panic!("Bad value 'use': {:?}", self.item_info[name.as_str()]); }
            }
         }
         _ => { panic!("Could not determine use for item: {:?}", item); }
      }
   }
   pub fn item_level(&mut self, item: &Sentence) -> i64 {
      if item.len() <= 1 { 1 }
      else {match item[1].clone() {
         Word::Touch(lvl) => { lvl as i64 }
         _ => { panic!("Could not determine slot for equip item: {:?}", item) }
      }}
   }
   pub fn item_slot(&mut self, item: &Sentence) -> String {
      match item[0].clone() {
         Word::Audio(name) => {
            self.item_info[name.as_str()]["slot"].as_str().unwrap().to_string()
         }
         _ => { panic!("Could not determine slot for equip item: {:?}", item) }
      }
   }
   pub fn item_type(&mut self, item: &Sentence) -> String {
      match item[0].clone() {
         Word::Audio(name) => {
            self.item_info[name.as_str()]["type"].as_str().unwrap().to_string()
         }
         _ => { panic!("Could not determine type for equip item: {:?}", item) }
      }
   }
   pub fn item_class(&mut self, item: &Sentence) -> String {
      match item[0].clone() {
         Word::Audio(name) => {
            self.item_info[name.as_str()]["class"].as_str().unwrap().to_string()
         }
         _ => { panic!("Could not determine class for equip item: {:?}", item) }
      }
   }
   pub fn item_suffix(&mut self, item: &Sentence) -> Option<String> {
      if item.len() >= 3 {
         match item[2] {
            Word::Audio(ref s) => { Some(s.clone()) }
            _ => { None }
         }
      } else { None }
   }
   pub fn item_display_suffix(&mut self, item: &Sentence) -> String {
      let sfx = self.item_suffix(item);
      if sfx.is_some() {
         let mut s = " of ".to_string();
         s.push_str(sfx.unwrap().as_str());
         s
      } else { "".to_string() }
   }
   pub fn equip(&mut self, eni: i32) {
      //get item to equip
      if eni < 0 { panic!("Equip out of index: {}", eni) }
      let (equip_now, count) = self.agent_inventory[ eni as usize ].clone();
      let en_slot = self.item_slot(&equip_now);

      //remove inventory selected x1 from inventory
      if count==1 { self.agent_inventory.remove(eni as usize); }
      else { self.agent_inventory[ eni as usize ] = (equip_now.clone(), count-1); }

      //move equipped into inventory
      let mut remove_equipped_i: i32 = -1;
      for rei in 0..self.agent_equipped.len() {
         let are = self.agent_equipped[rei].clone();
         let re_slot = self.item_slot( &are );
         if re_slot == en_slot { remove_equipped_i = (rei as i32); }
      }
      if remove_equipped_i >= 0 {
         let re = self.agent_equipped[remove_equipped_i as usize].clone();
         self.agent_equipped.remove(remove_equipped_i as usize);
         let mut has_inv = false;
         for ini in 0..self.agent_inventory.len() {
            let (cm, cmi) = self.agent_inventory[ini].clone();
            if re == cm {
               self.agent_inventory[ini] = (cm, cmi+1);
               has_inv = true;
               break;
            }
         }
         if !has_inv { self.agent_inventory.push((re, 1)); }
      }
      
      //move inventory selected into equipped
      self.agent_equipped.push( equip_now );
   }
   pub fn isay(&mut self, s: String) {
      let mut avatar_texture = "avatar_".to_string();
      avatar_texture.push_str(self.agent_class.as_str());
      avatar_texture.push_str(".png");
      let c = Statement {
         timestamp: time::precise_time_s(),
         source: Word::Audio(avatar_texture),
         content: vec![Word::Audio(s.clone())],
         reach: 10
      };
      self.chat_log.push(c);

      lazy_static! {
         static ref NAMEME: Regex = Regex::new("my name is ([a-zA-Z0-9]+)").unwrap();
      }
      for cap in NAMEME.captures_iter(s.as_str()) {
         self.agent_name = cap[1].to_string();
      }
   }
   pub fn expone_ryb(&self, ryb: (i64, i64, i64), lvl: i64) -> (i64, i64, i64) {
      let (mut r,mut y,mut b) = ryb;
      r = r * (self.explode.powi(lvl as i32) as i64);
      y = y * (self.explode.powi(lvl as i32) as i64);
      b = b * (self.explode.powi(lvl as i32) as i64);
      (r, y, b)
   }
   pub fn normalize_ryb(&self, mut red: i64, mut yellow: i64, mut blue: i64) -> (i64,i64,i64) {
      let mut nred = 0;
      let mut nyellow = 0;
      let mut nblue = 0;
      let n3 = (red + yellow + blue) / 3;
      for _ in 0..3 {
         if red >= n3 { nred += 1; red -= n3; }
         else if yellow >= n3 { nyellow += 1; yellow -= n3; }
         else { nblue += 1; blue -= n3; }
      }
      (nred, nyellow, nblue)
   }
   pub fn to_ryb_past(&self, c: &Creation) -> (i64,i64,i64) {
      let red = c.strength + c.vigor - 1;
      let yellow = c.agility + c.luck - 1;
      let blue = c.intelligence + c.charm - 1;
      self.normalize_ryb(red, yellow, blue)
   }
   pub fn to_classlvl(&self, c: &Creation) -> (String, i64) {
      let (r, y, b) = self.to_ryb_past(c);
      let lvl = (r + y + b) / 3;
      let (r, y, b) = self.normalize_ryb(r, y, b);
      let cls = self.to_class( r, y, b );
      (cls, lvl)
   }
   pub fn display_equipment(&mut self, e: &Sentence) -> String {
      if e.len() == 0 { panic!("equipment must have a name: {:?}", e.clone()); }
      let name = e[0].clone();
      let level = self.item_level(&e);
      let suffix = self.item_display_suffix(&e);
      match (name, level) {
         (Word::Audio(name), lvl) => {
            if lvl==1 {
               format!("{}{}", name.clone(), suffix.clone())
            } else {
               format!("{}{} (+{})", name.clone(), suffix.clone(), lvl-1)
            }
         }
         _ => { panic!("unknown") }
      }
   }
   pub fn equipment(&mut self, key: String, lvl: i64) -> Sentence {
      vec![
         Word::Audio(key),
         Word::Touch(lvl as i64)
      ]
   }
   pub fn to_class(&self, red: i64, yellow: i64, blue: i64) -> String {
      match (red, yellow, blue) {
         (3, 0, 0) => { "warrior".to_string() }
         (0, 3, 0) => { "rogue".to_string() }
         (0, 0, 3) => { "mage".to_string() }
         (2, 1, 0) => { "brawler".to_string() }
         (2, 0, 1) => { "crusader".to_string() }
         (1, 2, 0) => { "assassin".to_string() }
         (0, 2, 1) => { "monk".to_string() }
         (1, 0, 2) => { "druid".to_string() }
         (0, 1, 2) => { "spellthief".to_string() }
         (1, 1, 1) => { "vagabond".to_string() }
         _ => { "vagabond".to_string() }
      }
   }
   pub fn from_class(&self, cls: String) -> (i64, i64, i64) {
      match cls.as_str() {
         "warrior" => {(3, 0, 0)}
         "rogue" => {(0, 3, 0)}
         "mage" => {(0, 0, 3)}
         "crusader" => {(2, 0, 1)}
         "brawler" => {(2, 1, 0)}
         "assassin" => {(1, 2, 0)}
         "monk" => {(0, 2, 1)}
         "druid" => {(1, 0, 2)}
         "spellthief" => {(0, 1, 2)}
         "vagabond" => {(1, 1, 1)}
         _ => { panic!("from_classlvl unknown: {}", cls); }
      }
   }
   pub fn from_classlvl(&self, cls: String, lvl: i64) -> Creation {
      let (red, yellow, blue) = self.from_class(cls);
      let (red, yellow, blue) = (lvl*red, lvl*yellow, lvl*blue);
      Creation {
            //resource stats
            max_health: 5 + (self.explode.powi(red as i32) as i64),
            cur_health: 5 + (self.explode.powi(red as i32) as i64),
            max_energy: 5 + (self.explode.powi(yellow as i32) as i64),
            cur_energy: 5 + (self.explode.powi(yellow as i32) as i64),
            max_mana: 5 + (self.explode.powi(blue as i32) as i64),
            cur_mana: 5 + (self.explode.powi(blue as i32) as i64),

            //base stats
            strength: red,
            agility: yellow,
            intelligence: blue,
            speed: 1,
            vigor: red,
            luck: yellow,
            charm: blue,

            //weapon stats
            force: 1,
            accuracy: 1,
            enhancement: 1,

            //armor stats
            protect: 1,
            deflect: 1,
            absorb: 1,
      }      
   }
   pub fn initial_equipped(&mut self, cls: String) {
       if cls == "warrior".to_string() {
          let item = self.equipment("sword".to_string(), 1); self.agent_equipped.push(item);
          let item = self.equipment("breastplate".to_string(), 1); self.agent_equipped.push(item);
       } else if cls == "rogue".to_string() {
          let item = self.equipment("dagger".to_string(), 1); self.agent_equipped.push(item);
          let item = self.equipment("cuirass".to_string(), 1); self.agent_equipped.push(item);
       } else if cls == "mage".to_string() {
          let item = self.equipment("staff".to_string(), 1); self.agent_equipped.push(item);
          let item = self.equipment("robe".to_string(), 1); self.agent_equipped.push(item);
       } else if cls == "crusader".to_string() {
          let item = self.equipment("mace".to_string(), 1); self.agent_equipped.push(item);
          let item = self.equipment("breastplate".to_string(), 1); self.agent_equipped.push(item);
       } else if cls == "brawler".to_string() {
          let item = self.equipment("hammer".to_string(), 1); self.agent_equipped.push(item);
          let item = self.equipment("breastplate".to_string(), 1); self.agent_equipped.push(item);
       } else if cls == "assassin".to_string() {
          let item = self.equipment("dagger".to_string(), 1); self.agent_equipped.push(item);
          let item = self.equipment("cuirass".to_string(), 1); self.agent_equipped.push(item);
       } else if cls == "monk".to_string() {
          let item = self.equipment("dagger".to_string(), 1); self.agent_equipped.push(item);
          let item = self.equipment("cuirass".to_string(), 1); self.agent_equipped.push(item);
       } else if cls == "druid".to_string() {
          let item = self.equipment("staff".to_string(), 1); self.agent_equipped.push(item);
          let item = self.equipment("robe".to_string(), 1); self.agent_equipped.push(item);
       } else if cls == "spellthief".to_string() {
          let item = self.equipment("staff".to_string(), 1); self.agent_equipped.push(item);
          let item = self.equipment("robe".to_string(), 1); self.agent_equipped.push(item);
       } else {
          let item = self.equipment("dagger".to_string(), 1); self.agent_equipped.push(item);
          let item = self.equipment("sandals".to_string(), 1); self.agent_equipped.push(item);
       }
   }
   pub fn item_stats(&mut self, itm: Sentence) -> Creation {
      let icls = self.item_class(&itm);
      let ilvl = self.item_level(&itm);
      let islot = self.item_slot(&itm);
      let (r,y,b) = self.from_class(icls.to_string());
      let pow = (self.explode.powi(ilvl as i32) as i64);
      let (atk,def) = if islot=="mainhand".to_string() { (1,0) }
                      else { (0,1) };

      let mut stats = Creation::new();
      if r>0 && atk>0 { stats.force += atk*pow*r; }
      if y>0 && atk>0 { stats.accuracy += atk*pow*y; }
      if b>0 && atk>0 { stats.enhancement += atk*pow*b; }
      if r>0 && def>0 { stats.protect += def*pow*r; }
      if y>0 && def>0 { stats.deflect += def*pow*y; }
      if b>0 && def>0 { stats.absorb += def*pow*b; }
      stats
   }
   pub fn stats_requirements(&mut self, neq: i32) -> Creation {
      let mut c = Creation::new();
      let itm = self.agent_inventory[neq as usize].clone().0;
      let icls = self.item_class(&itm);
      let ilvl = self.item_level(&itm);
      let ryb = self.from_class(icls);
      let (r,y,b) = self.expone_ryb(ryb, ilvl);
      c.strength = r;
      c.agility = y;
      c.intelligence = b;
      c
   }
   pub fn compare_equipped(&mut self, neq: i32) -> Creation {
      let estats = Creation::new();
      let (nitem, count) = self.agent_inventory[neq as usize].clone();
      let nslot = self.item_slot(&nitem);
      let nstats = self.item_stats(nitem);

      let mut estats = Creation::new();
      for aei in 0..self.agent_equipped.len() {
         let eitem = self.agent_equipped[aei as usize].clone();
         let eslot = self.item_slot(&eitem);
         if eslot==nslot { estats = self.item_stats(eitem); break; }
      }

      nstats - estats
   }
   pub fn augment_equipped(&self, stats: &mut RenderedCreation, eq: Sentence) {
      let (name, lvl) = if eq.len() >= 2 {
         match (eq[0].clone(), eq[1].clone()) {
            (Word::Audio(name), Word::Touch(lvl)) => {
               (name, lvl)
            }
            _ => { panic!("unknown") }
         }
      } else {
         match eq[0].clone() {
            Word::Audio(name) => {
               (name, 1)
            }
            _ => { panic!("unknown") }
         }
      };
      let class = self.item_info[name.as_str()]["class"].as_str().unwrap();
      let slot = self.item_info[name.as_str()]["slot"].as_str().unwrap();
      let (r,y,b) = self.from_class(class.to_string());
      let (atk, def) = if slot == "twohand" { (2,0) }
          else if slot == "mainhand" { (1,0) }
          else { (0,1) };
      let pow = (self.explode.powi(lvl as i32) as i64);
      if r>0 && atk>0 { stats.force += atk*pow*r; }
      if y>0 && atk>0 { stats.accuracy += atk*pow*y; }
      if b>0 && atk>0 { stats.enhancement += atk*pow*b; }
      if r>0 && def>0 { stats.protect += def*pow*r; }
      if y>0 && def>0 { stats.deflect += def*pow*y; }
      if b>0 && def>0 { stats.absorb += def*pow*b; }
   }
   pub fn adjust_equipped(&mut self, stats: &mut RenderedCreation) {
      stats.force = 1;
      stats.accuracy = 1;
      stats.enhancement = 1;
      stats.protect = 1;
      stats.deflect = 1;
      stats.absorb = 1;
      for ei in 0..self.agent_equipped.len() {
         self.augment_equipped(stats, self.agent_equipped[ei].clone());
      }
   }
   pub fn agent_raw_stats(&mut self) -> Creation {
      if !self.agent_stats.contains_key(&0) {
         let cls = self.agent_class.clone();
         let mut base_stats = self.from_classlvl(cls.clone(), 1);
         base_stats.max_health += 30;
         base_stats.max_energy += 10;
         base_stats.max_mana += 10;
         //generate equipment
         self.initial_equipped(cls.clone());
         self.agent_stats.insert(0, base_stats);
      }
      let s = self.agent_stats.get(&0).unwrap().clone();
      s
   }
   pub fn agent_stats(&mut self) -> RenderedCreation {
      let mut s = self.agent_raw_stats().render();
      self.adjust_equipped(&mut s);
      s
   }
   pub fn default_enemy_stats(&self, enemy: &Sentence) -> Creation {
      match enemy[0].clone() {
         Word::Audio(cls) => {
            let class = self.npc_info[cls.as_str()]["body"].as_str().unwrap();
            self.from_classlvl("warrior".to_string(), 1)
         }
         _ => { panic!("Invalid enemy template: {:?}", enemy) }
      }
   }
   pub fn creature_id(&self, cr: &Sentence) -> i64 {
      match cr[1] {
         Word::Touch(ci) => { ci }
         _ => { panic!("Unexpected code path") }
      }
   }
   pub fn enemy_stats(&mut self, enemy: &Sentence) -> RenderedCreation {
      let uuid = self.creature_id(enemy);
      if !self.agent_stats.contains_key(&uuid) {
         let mut hm = self.default_enemy_stats(enemy);
         self.agent_stats.insert(uuid, hm);
      }
      self.agent_stats.get(&uuid).unwrap().clone().render()
   }
   pub fn gen_uuid(&mut self) -> i64 {
      let mut uuid = rand::random::<i64>();
      while uuid==0 || self.creature_registry.contains(&uuid) {
         uuid = rand::random::<i64>();
      }
      self.creature_registry.insert(uuid);
      uuid
   }
   pub fn unwrap_cragent(&mut self, t: Option<(i32, i32)>) -> (i32,i32,i64,RenderedCreation) {
      match t {
         Some((cx,cy)) => {
            let cs = self.board_creature[ (cy*self.board_width + cx) as usize ].clone().unwrap();
            let cid = self.creature_id(&cs);
            let c = self.enemy_stats(&cs);
            (cx, cy, cid, c)
         }
         None => {
            let (ax,ay) = self.agent_position;
            let c = self.agent_stats();
            (ax, ay, 0, c)
         }
      }
   }

   pub fn tagged_kill(&mut self, src_uuid: i64, tgt_x: i32, tgt_y: i32, tgt_uuid: i64) {
      if tgt_uuid == 0 { self.alive = false; }
      else {
         let enemy_stats = { self.agent_stats.get(&tgt_uuid).unwrap().clone() };
         let mut agent_stats = self.agent_stats();
         let (ecls, elvl) = self.to_classlvl(&enemy_stats);
         let (ar, ay, ab) = self.from_class(self.agent_class.clone());

         fn g(r: u64) -> [u64; 3] { 
            if r==3 { [1, 1, 1] } else 
           if r==33 { [2, 2, 29] } else
           if r==63 { [3, 3, 57] } else
           if r==93 { [4, 4, 85] } else
           { panic!("Invalid gen num: {}", r) } }


         for opportunity in 0 .. (3*elvl) {
            let (rr, ry, rb) = (30*(ar as u64)+3, 30*(ay as u64)+3, 30*(ab as u64)+3);
            assert!(rr + ry + rb == 99);

            let roll = rand::random::<u64>() % 100;
            if roll < rr {
               let rg = g(rr);
               let rc = roll - 0;
               if rc < rg[0]              { agent_stats.strength += 1; }
               else if rc < (rg[0]+rg[1]) { agent_stats.vigor += 1; }
               else                       { agent_stats.max_health += 1; }
            } else if roll < (rr + ry) {
               let yg = g(rr);
               let ry = roll - rr;
               if ry < yg[0]              { agent_stats.agility += 1; }
               else if ry < (yg[0]+yg[1]) { agent_stats.luck += 1; }
               else                       { agent_stats.max_energy += 1; }
            } else if roll < (rr + ry + rb) {
               let bg = g(rr);
               let rb = (roll - rr) - ry;
               if rb < bg[0]              { agent_stats.intelligence += 1; }
               else if rb < (bg[0]+bg[1]) { agent_stats.charm += 1; }
               else                       { agent_stats.max_mana += 1; }
            } else {
               agent_stats.speed += 1;
            }
         }
         self.agent_stats.insert(0, agent_stats.normalize());
         self.board_creature[ (tgt_y*self.board_width + tgt_x) as usize ] = None;
      }
   }

   pub fn attack(&mut self, src: Option<(i32, i32)>, tgt: Option<(i32, i32)>) -> bool {
      let (src_x, src_y, src_uuid, src_stats) = self.unwrap_cragent(src);
      let (tgt_x, tgt_y, tgt_uuid, mut tgt_stats) = self.unwrap_cragent(tgt);

      let mut ra = src_stats.strength * src_stats.force; if ra==0 { ra = 1; }
      let mut rd = tgt_stats.vigor * tgt_stats.protect; if rd==0 { rd = 1; }
      let mut ya = src_stats.agility * src_stats.accuracy; if ya==0 { ya = 1; }
      let mut yd = tgt_stats.luck * tgt_stats.deflect; if yd==0 { yd = 1; }
      let mut ba = src_stats.intelligence * src_stats.enhancement; if ba==0 { ba = 1; }
      let mut bd = tgt_stats.charm * tgt_stats.absorb; if bd==0 { bd = 1; }
      let mut na = 2 * ra * (rand::random::<i64>() % (3*ya));
      let mut nd = rd * (rand::random::<i64>() % (3*yd)); if nd==0 { nd = 1; }
      let mut dmg = 1 + na / nd + (2 * ba);
      if bd >= dmg { dmg = 1; }
      else { dmg -= bd }

      if tgt_stats.cur_health < dmg { tgt_stats.cur_health = 0; }
      else { tgt_stats.cur_health -= dmg; }

      if tgt_stats.cur_health == 0 {
         self.tagged_kill(src_uuid, tgt_x, tgt_y, tgt_uuid);
      } else {
         self.agent_stats.insert(tgt_uuid, tgt_stats.normalize());
      }

      true
   }
   pub fn circuit(&self, circuit: f64) -> f64 {
      (time::precise_time_s() - self.start_time) % circuit
   }
   pub fn clear(&mut self, k: String) {
      self.timers.remove(&k);
   }
   pub fn empty(&mut self, k: String) -> bool {
      match self.timers.get(&k) {
         Some(_) => { false }
         None => { true }
      }
   }
   pub fn rotated(&mut self, k: String, rt: f64) -> bool {
      match self.timers.get(&k) {
         Some(t_prev) => {
            let t_now = time::precise_time_s();
            (t_now - t_prev) > rt
         }
         None => { false }
      }
   }
   pub fn once(&mut self, k: String, rt: f64) -> f64 {
      let mut empty = false;
      let mut dt = match self.timers.get(&k) {
         Some(t_prev) => {
            let t_now = time::precise_time_s();
            t_now - t_prev
         }
         None => {
            empty = true;
            0.0
         }
      };
      if empty {
         self.timers.insert(k.clone(), time::precise_time_s());
      }
      if dt > rt { rt } else { dt }
   }
   pub fn rotation(&mut self, k: String, rt: f64) -> f64 {
      let mut empty = false;
      let mut dt = match self.timers.get(&k) {
         Some(t_prev) => {
            let t_now = time::precise_time_s();
            t_now - t_prev
         }
         None => {
            empty = true;
            0.0
         }
      };
      if empty {
         self.timers.insert(k.clone(), time::precise_time_s());
      }
      (dt % rt)
   }
   pub fn spell_name(&mut self, item: Sentence) -> String {
      match item[0].clone() {
         Word::Audio(s) => { s.clone() }
         _ => { panic!("Unexpected spell: {:?}", item) }
      }
   }
   pub fn item_description(&mut self, item_name: String) -> Yaml {
      self.item_info[item_name.as_str()].clone()
   }
   pub fn item_rarity(&mut self, item_name: String) -> Option<f64> {
      match self.item_population.get(&self.board_class) {
         Some(its) => {
            let mut rarity = 0;
            let mut total = 0;
            for tti in 0..its.len() {
               let (ct, itm) = its[tti].clone();
               total += ct;
               if itm==item_name { rarity = ct; }
            }
            if rarity==0 { None }
            else { Some( (total as f64) / (rarity as f64) ) }
         }
         None => None
      }
   }
   pub fn every(&mut self, k: String, dt: f64) -> bool {
      let mut empty = false;
      let b = match self.timers.get(&k) {
         Some(t_prev) => {
            let t_now = time::precise_time_s();
            (t_now-t_prev) > dt
         }
         None => {
            empty = true;
            false
         }
      };
      if b || empty {
         self.timers.insert(k.clone(), time::precise_time_s());
      } b
   }
}
