extern crate time;
extern crate rand;
extern crate yaml_rust;
use std::collections::HashSet;
use model::*;
use lang::*;
use piston::input::*;
use terrain;
use self::yaml_rust::{YamlLoader, YamlEmitter, Yaml};

pub fn react(gm: &mut GameState, v: &mut WindowState) {
   gm.turn_die = rand::random::<u32>() as i64;
   gm.turn_count += 1;
   if gm.alive {
      plan(gm, v);
      resolve(gm, v);
      act(gm, v);
   }
}

pub fn plan(gm: &mut GameState, v: &mut WindowState) {
   let p = v.pressed.clone();
   let p2 = v.pressed.clone();
   if v.search_has_focus {
   } else if v.item_has_focus >= 0 {
      if v.tab != "Inventory".to_string() { v.item_has_focus = -1; }
   } else if v.equip_has_focus >= 0 {
      if v.tab != "Character".to_string() { v.equip_has_focus = -1; }
   } else if p.is_some() && p.unwrap()=="S".to_string() {
      gm.agent_move_target = None;
      gm.agent_engaged = None;
   } else if gm.agent_engaged.is_none() && p2.is_some() && p2.unwrap()=="A".to_string() &&
             (v.tab == "Map".to_string() || v.tab == "Character".to_string() || v.tab == "Inventory" ) {
      let (ax,ay) = gm.agent_position;
      let mut nearest = None;
      let mut distance = 9999;
      for dx in -20i32..21i32 {
         for dy in -20i32..21i32 {
            let dst = dx.abs() + dy.abs();
            let mx = ax+dx;
            let my = ay+dy;
            if dst<distance && 0<=mx && mx<gm.board_width &&
                               0<=my && my<gm.board_height {
               let ref c = gm.board_creature[ (my * gm.board_width + mx) as usize ];
               if c.is_some() {
                  nearest = Some((mx, my, c.clone().unwrap()));
                  distance = dst;
               }
            }
         }
      }
      gm.agent_engaged = nearest;
   }
}

pub fn resolve(gm: &mut GameState, v: &mut WindowState) {

   if gm.every("babble".to_string(), 15.0 * 60.0) {
      gm.dialogue_index = 0;
   }
   if gm.every("babble".to_string(), 60.0) && gm.dialogue_index < gm.dialogue_content.len() {
      let d = gm.dialogue_content[gm.dialogue_index].clone();
      gm.say(d);
      gm.dialogue_index += 1;
   }

   if v.search_has_focus {
      if let Some(key) = v.pressed.clone() {
         v.pressed = None;
         if key == "Backspace" {
            gm.chat_bar.pop();
         } else if key == "Space" {
            gm.chat_bar.push_str(" ");
         } else if key == "Shift" {
         } else if key == "Return" {
            if gm.chat_bar.len() > 0 {
               let s = gm.chat_bar.clone();
               gm.isay(s);
            }
            gm.chat_bar = "".to_string();
            gm.dialogue_index = 0;
         } else {
            gm.chat_bar.push_str(key.to_lowercase().as_str());
         }
      }
   }

   if v.do_pickup {
      let (ax,ay) = gm.agent_position;
      let mut items = Vec::new();
      if let Some(ref its) = gm.board_items[(ay*gm.board_width + ax) as usize] {
         items = its.clone();
      }
      for ix in 0..items.len() {
         insert_agent_inventory(gm, &items[ix]);
      }
      gm.board_items[(ay*gm.board_width + ax) as usize] = None;
   }
}

pub fn insert_agent_inventory(gm: &mut GameState, item: &Sentence) {
   let mut found = false;
   for ix in 0..gm.agent_inventory.len() {
      if equals(&gm.agent_inventory[ix].0, item) {
         gm.agent_inventory[ix].1 += 1;
         found = true;
      }
   }
   if !found {
      gm.agent_inventory.push((item.clone(), 1));
   }
}

pub fn act(gm: &mut GameState, v: &mut WindowState) {
   agent_act(gm, v);
   creature_act(gm, v);
}

pub fn creature_move(gm: &mut GameState, ox: i32, oy: i32, nx: i32, ny: i32) {
   let oc = gm.board_creature[ (oy*gm.board_width + ox) as usize ].clone();
   let (ax,ay) = gm.agent_position;
   let open = if let None = gm.board_creature[ (ny*gm.board_width + nx) as usize ] { true } else { false };
   if open && (ax != nx || ay != ny) {
      gm.board_creature[ (oy*gm.board_width + ox) as usize ] = None;
      gm.board_creature[ (ny*gm.board_width + nx) as usize ] = oc;
   }
}

pub fn creature_act(gm: &mut GameState, v: &mut WindowState) {
   if gm.every("global_motion".to_string(), 1.0) {
      let mut clean: HashSet<(i32,i32)> = HashSet::new();
      for x in 0..gm.board_width {
         for y in 0..gm.board_height {
            let ref cc = gm.board_creature[ (y*gm.board_width + x) as usize ].clone();
            if cc.is_some() && !clean.contains(&(x,y)) {
               let roll = (gm.turn_die + (11*x as i64) + (7*y as i64)) % 4;
               let (agent_x, agent_y) = gm.agent_position;
               let agent_dst = (x-agent_x).abs() + (y-agent_y).abs();
               if agent_dst <= 1 {
                  gm.attack(Some((x, y)), None);
               }
               else if roll == 0 && terrain::mob_tile_open(gm, x+1, y) { creature_move(gm, x, y, x+1, y); clean.insert((x+1, y)); }
               else if roll == 1 && terrain::mob_tile_open(gm, x-1, y) { creature_move(gm, x, y, x-1, y); clean.insert((x-1, y)); }
               else if roll == 2 && terrain::mob_tile_open(gm, x, y+1) { creature_move(gm, x, y, x, y+1); clean.insert((x, y+1)); }
               else if roll == 3 && terrain::mob_tile_open(gm, x, y-1) { creature_move(gm, x, y, x, y-1); clean.insert((x, y-1)); }
            }
         }
      }
   }
}

pub fn solve_move(gm: &mut GameState, agn: (i32, i32), dst: (i32, i32)) -> (i32, i32) {
   let mut manhattan = vec![9999; (gm.board_width * gm.board_height) as usize];
   let (dx, dy) = dst;
   manhattan[ (dy*gm.board_width + dx) as usize ] = 0;
   let motion = [[1,0], [-1,0], [0,1], [0,-1]];
   for _ in 0..100 {
      for x in 0..gm.board_width {
         for y in 0..gm.board_width {
            for mi in 0..motion.len() {
               let (dx, dy) = (x+motion[mi][0], y+motion[mi][1]);
               if dx>=0 && dx<gm.board_width && dy>=0 && dy<gm.board_height &&
               manhattan[(dy*gm.board_width + dx) as usize]+1 > manhattan[(y*gm.board_width + x) as usize] &&
               terrain::tile_open(gm, dx, dy) {
                  manhattan[(dy*gm.board_width + dx) as usize] =
                  manhattan[(y*gm.board_width + x) as usize] + 1;
               }
            }
         }
      }
   }
   let mut dist = 9999;
   let (mut nx, mut ny) = gm.agent_position;
   for mi in 0..motion.len() {
      let (ox, oy) = agn;
      let (dx, dy) = (ox+motion[mi][0], oy+motion[mi][1]);
      let ddist = manhattan[(dy*gm.board_width + dx) as usize];
      if ddist < dist {
         nx = dx;
         ny = dy;
         dist = ddist;
      }
   }
   (nx, ny)
}
pub fn solve_search(gm: &mut GameState, src: (i32, i32), tgt: (i32, i32, Sentence)) -> Option<(i32,i32)> {
   let mut distance = 9999;
   let mut nmy = None;
   let (ax, ay) = src;
   let (tx, ty, ts) = tgt;
   for dx in -5i32..6i32 {
     for dy in -5i32..6i32 {
        let (mx, my) = (ax+dx, ay+dy);
        if 0<=mx && mx<gm.board_width &&
           0<=my && my<gm.board_height &&
           gm.board_creature[ (my*gm.board_width + mx) as usize ].is_some() {
           let ms = gm.board_creature[ (my*gm.board_width + mx) as usize ].clone().unwrap();
           if ms[0]==ts[0] {
              let dst = (tx-mx).abs() + (ty-my).abs();
              if dst < distance {
                 distance = dst;
                 nmy = Some((mx, my));
              }
           }
        }
     }
   }
   nmy
}

pub fn agent_move(gm: &mut GameState, v: &mut WindowState) -> bool {
   let (agent_x, agent_y) = gm.agent_position;
   let (offset_x, offset_y) = gm.agent_move_target.unwrap();
   let agn = gm.agent_position.clone();
   let (nx, ny) = solve_move(gm, agn, (agent_x+offset_x, agent_y+offset_y));
   if terrain::mob_tile_open(gm, nx, ny) {
      gm.agent_position = (nx, ny);
      let offset_x = (agent_x+offset_x) - nx;
      let offset_y = (agent_y+offset_y) - ny;
      gm.agent_move_target = Some((offset_x, offset_y));
      if offset_x==0 && offset_y==0 {
         gm.agent_move_target = None;
      }
      return true;
   }
   return false;
}

pub fn agent_attack(gm: &mut GameState, v: &mut WindowState) -> bool {
   let (agent_x, agent_y) = gm.agent_position;
   let (enemy_x, enemy_y, enemy_w) = gm.agent_engaged.clone().unwrap();
   match solve_search(gm, (agent_x, agent_y), (enemy_x, enemy_y, enemy_w.clone())) {
      Some((nx, ny)) => {
         let dst = (agent_x-nx).abs() + (agent_y-ny).abs();
         if gm.agent_stats().speed >= (dst as i64) {
           gm.attack(None, Some((nx, ny)));
           true
         } else { false }
      }
      None => { false }
   }
}

pub fn agent_chase(gm: &mut GameState, v: &mut WindowState) -> bool {
   let (agent_x, agent_y) = gm.agent_position;
   let (enemy_x, enemy_y, enemy_w) = gm.agent_engaged.clone().unwrap();
   match solve_search(gm, (agent_x, agent_y), (enemy_x, enemy_y, enemy_w.clone())) {
      Some((nmx, nmy)) => {
         let agn = gm.agent_position.clone();
         let (nx, ny) = solve_move(gm, agn, (nmx, nmy));
         gm.agent_engaged = Some((nmx, nmy, enemy_w));
         if terrain::mob_tile_open(gm, nx, ny) {
            gm.agent_position = (nx, ny);
         }
      }
      None => {
         gm.agent_engaged = None;
      }
   }
   false
}

pub fn agent_act(gm: &mut GameState, v: &mut WindowState) {
   if gm.every("agent_motion".to_string(), 0.25) {
      let mut s = gm.agent_stats();
      if (rand::random::<u32>() % 30) == 0 && s.cur_health<s.max_health {
         s.cur_health += s.speed;
         if s.cur_health > s.max_health { s.cur_health = s.max_health; }
      }
      if (rand::random::<u32>() % 30) == 0 && s.cur_energy<s.max_energy {
         s.cur_energy += s.speed;
         if s.cur_energy > s.max_energy { s.cur_energy = s.max_energy; }
      }
      if (rand::random::<u32>() % 30) == 0 && s.cur_mana<s.max_mana {
         s.cur_mana += s.speed;
         if s.cur_mana > s.max_mana { s.cur_mana = s.max_mana; }
      }
      let ns = s.normalize();
      gm.agent_stats.insert(0, ns);
      gm.clear("agent_motion".to_string());
      if gm.agent_engaged.is_some() {
        agent_attack(gm, v) ||
        agent_chase(gm, v);
      } else if gm.agent_move_target.is_some() {
        agent_move(gm, v);
      }
   }
}
