extern crate rand;
extern crate yaml_rust;
use self::yaml_rust::yaml;
use std::collections::{HashSet,HashMap};
use model::*;
use lang::*;

pub fn generate(width: i32, height: i32, model: String) -> ((i32,i32),Vec<String>) {
   if model == "jungle".to_string() {
      jungle(width, height)
   } else {
      panic!("Unknown terrain model: {}", model);
   }
}

pub fn mob_tile_open(gm: &GameState, x: i32, y: i32) -> bool {
   let (ax,ay) = gm.agent_position;
   if x==ax && y==ay { return false; }
   tile_open(gm, x, y)
}
pub fn tile_open(gm: &GameState, x: i32, y: i32) -> bool {
   if x<0 || x>=gm.board_width || y<0 || y>=gm.board_height { return false; }
   let terrain = gm.board_terrain[(y*gm.board_width + x) as usize].clone();
   if terrain != "".to_string() {
      let ref passable = gm.terrain_info[terrain.as_str()]["passable"];
      if !passable.is_badvalue() && passable.as_bool()==Some(false) { return false; }
   }
   true
}

pub fn generate_item(gm: &mut GameState, name: String) -> Sentence {
   let suffix = gm.item_info[name.as_str()]["suffix"].as_vec();
   let suffix = match suffix {
      Some(ref ls) => {
         let ri = rand::random::<u64>() % (ls.len() as u64);
         Some( ls[ri as usize].as_str().unwrap().to_string() )
      }
      None => { None }
   };
   if suffix.is_some() {
      vec![Word::Audio(name), Word::Touch(gm.board_level), Word::Audio(suffix.unwrap())]
   } else {
      vec![Word::Audio(name), Word::Touch(gm.board_level)]
   }
}
pub fn generate_items(gm: &mut GameState) {
   for _ in 0..gm.board_width {
      loop {
         let ix = rand::random::<i32>().abs() % gm.board_width;
         let iy = rand::random::<i32>().abs() % gm.board_height;
         if tile_open(gm, ix, iy) {
            let mut ts = match gm.board_items[ (iy*gm.board_width + ix) as usize ].clone() {
               Some(ts) => { ts }
               None => { Vec::new() }
            };
            let terrain = gm.board_class.clone();
            let c = generate_random_population(&mut gm.item_population, &gm.item_info, terrain);
            let c = generate_item(gm, c);
            ts.push( c );
            gm.board_items[ (iy*gm.board_width + ix) as usize ] = Some(ts);
            break;
         }
      }
   }
}

pub fn generate_random_population(population: &mut HashMap<String,Vec<(u64,String)>>, info: &yaml::Yaml, terrain: String) -> String {
   let (p_size, p, empty) = match population.get(&terrain) {
      None => {
         let mut p_size: u64 = 0;
         let mut p : Vec<(u64, String)> = Vec::new();
         match info {
            &yaml::Yaml::Hash(ref h) => {
               for (k, v) in h {
                  let mut appears = false;
                  let mut appears_i = 0;
                  loop {
                     let ref b = v["appears"][appears_i];
                     if b.is_badvalue() { break; }
                     if b.as_str().unwrap().to_string()==terrain {
                        appears = true;
                        let rarity = match v["rarity"].as_i64() {
                           Some(i) => { i as u64 }
                           None => { 1 }
                        };
                        p.push( (rarity, k.as_str().unwrap().to_string()) );
                        p_size += rarity;
                        break;
                     }
                     appears_i += 1;
                  }
               }
            },
            _ => {
               panic!("npc.yaml should be a Hash ob");
            }
         }
         (p_size, p, true)
      }
      Some(ref cp) => {
         let mut p_size = 0;
         let mut p = Vec::new();
         for pi in 0..cp.len() {
            let (cc, ref cn) = cp[pi];
            p_size += cc;
            p.push((cc, cn.clone()));
         }
         (p_size, p.clone(), false)
      }
   };
   let mut creature = "".to_string();
   let mut creature_p = 0;
   let mut creature_die = if p_size>0 { rand::random::<u64>() % p_size } 
       else { panic!("Empty population in {}", terrain); };
   for pi in 0..p.len() {
      let (crarity, ref cname) = p[pi];
      creature_p += crarity;
      if creature_die < creature_p {
         creature = cname.clone();
         break;
      }
   }
   if empty {
      population.insert(terrain.clone(), p);
   }
   creature
}

pub fn generate_creatures(gm: &mut GameState) {
   for _ in 0..gm.board_width {
      loop {
         let ix = rand::random::<i32>().abs() % gm.board_width;
         let iy = rand::random::<i32>().abs() % gm.board_height;
         if tile_open(gm, ix, iy) {
            let terrain = gm.board_class.clone();
            let c = generate_random_population(&mut gm.creature_population, &gm.npc_info, terrain);
            let c = generate_creature(gm, c);
            gm.board_creature[ (iy*gm.board_width + ix) as usize ] = Some(c);
            break;
         }
      }
   }
}
pub fn generate_creature(gm: &mut GameState, name: String) -> Sentence {
   vec![Word::Audio(name), Word::Touch(gm.gen_uuid())]
}

pub fn map_is_independent( agent: (i32,i32), map: &Vec<String>, home: &HashSet<(i32,i32)>, width: i32, height: i32, x: i32, y: i32 ) -> bool {
   let (ax,ay) = agent;
   if ((x-ax).abs() + (y-ay).abs()) < 6 { return false; }
   let bounds = [
      (1, 1), (1, 0), (1, -1),
      (0, 1), (0, 0), (0, -1),
      (-1, 1), (-1, 0), (-1, -1)
   ];
   for di in 0..bounds.len() {
      let (dx,dy) = bounds[di];
      let (nx,ny) = (x+dx, y+dy);
      if nx<0 || nx>=width || ny<0 || ny>=height { continue; }
      if home.contains(&(nx,ny)) { continue; }
      if map[ (ny*width + nx) as usize ] != "".to_string() {
         return false;
      }
   }
   true
}


fn jungle(width: i32, height: i32) -> ((i32,i32),Vec<String>) {
   let mut map = vec!["".to_string(); (width*height) as usize];

   /* boundaries are formed by "deep_jungle" impassable terrain */
   for ix in 0..width {
      for iy in 0..height {
         if ix < 10 || iy < 10 || ix > (width-10) || iy > (height-10) {
            map[ (iy*width + ix) as usize ] = "deep_jungle".to_string();
         }
      }
   }

   /* place agent somewhere in the middle */
   let agent_x = width/4 + rand::random::<i32>().abs() % (height/2);
   let agent_y = height/4 + rand::random::<i32>().abs() % (height/2);

   /* add natural border along impassible square ends of jungle */

   /* add random swatches of impassable "deep jungle" */
   for i in 0.. 10*width {
      let mut seed_x = rand::random::<i32>().abs() % width;
      let mut seed_y = rand::random::<i32>().abs() % height;
      if seed_x==agent_x && seed_y==agent_y { continue; }
      let mut grove = HashSet::new();
      if !map_is_independent( (agent_x, agent_y), &map, &grove, width, height, seed_x, seed_y ) { continue; }
      grove.insert((seed_x, seed_y));
      map[ (seed_y*width + seed_x) as usize ] = "deep_jungle".to_string();
      for _ in 0..height {
         let r = rand::random::<u64>() % 4;
         let (dx,dy) = if r==0 { (1,0) }
         else if r==1 { (-1,0) }
         else if r==2 { (0,1) }
         else { (0,-1) };
         if (seed_x+dx)==agent_x && (seed_y+dy)==agent_y { continue; }
         if map_is_independent( (agent_x, agent_y), &map, &grove, width, height, seed_x+dx, seed_y+dy ) {
            seed_x += dx;
            seed_y += dy;
            grove.insert((seed_x, seed_y));
            map[ (seed_y*width + seed_x) as usize ] = "deep_jungle".to_string();
         }
      }
   }

   /* add random passable tiles to remaining empty space */
   for ix in 0..width {
      for iy in 0..height {
         let terrain = map[ (iy*width + ix) as usize ].clone();
         if terrain == "" {
            map[ (iy*width + ix) as usize ] = "dry_sod".to_string()
         }
      }
   }

   ((agent_x, agent_y), map)
}
