use std::vec::Vec;
use std::vec;
use std::collections::VecDeque;
use core::convert::TryFrom;

use ming_wm::window_manager::{ DrawInstructions, WindowLike, WindowLikeType };
use ming_wm::messages::{ WindowMessage, WindowMessageResponse };
use ming_wm::framebuffer::Dimensions;
use ming_wm::themes::ThemeInfo;
use ming_wm::utils::{ u8_to_hex, hex_to_u8, HEX_CHARS };
use ming_wm::ipc::listen;

//16x16 with 40 mines

#[derive(Default)]
struct MineTile {
  mine: bool,
  revealed: bool,
  touching: u8,
}

#[derive(Default, PartialEq)]
enum State {
  #[default]
  Seed,
  BeforePlaying,
  Playing,
  Won,
  Lost,
}

#[derive(Default)]
struct Minesweeper {
  dimensions: Dimensions,
  state: State,
  tiles: [[MineTile; 16]; 16],
  random_chars: String,
  random_seed: u32, //user types in random keyboard stuff at beginning
  first_char: char, //defaults to '\0'
}

impl WindowLike for Minesweeper {
  fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse {
    match message {
      WindowMessage::Init(dimensions) => {
        self.dimensions = dimensions;
        WindowMessageResponse::JustRedraw
      },
      WindowMessage::KeyPress(key_press) => {
        if self.state == State::Seed {
          if self.random_chars.len() == 4 {
            let mut r_chars = self.random_chars.chars();
            self.random_seed = ((r_chars.next().unwrap() as u8 as u32) << 24) | ((r_chars.next().unwrap() as u8 as u32) << 16) | ((r_chars.next().unwrap() as u8 as u32) << 8) | (r_chars.next().unwrap() as u8 as u32);
            self.random_chars = String::new();
            self.state = State::BeforePlaying;
          } else {
            if u8::try_from(key_press.key).is_ok() {
              self.random_chars.push(key_press.key);
            }
          }
          WindowMessageResponse::JustRedraw
        } else if self.state == State::BeforePlaying || self.state == State::Playing {
          if key_press.key == '𐘁' { //backspace
            self.first_char = '\0';
            WindowMessageResponse::DoNothing
          } else if self.first_char == '\0' {
            if HEX_CHARS.iter().find(|c| c == &&key_press.key).is_some() {
              self.first_char = key_press.key;
            }
            WindowMessageResponse::DoNothing
          } else if HEX_CHARS.iter().find(|c| c == &&key_press.key).is_some() {
            let u = hex_to_u8(self.first_char, key_press.key) as usize;
            let y = u / 16;
            let x = u % 16;
            if HEX_CHARS.iter().find(|c| c == &&key_press.key).is_some() {
              if self.state == State::BeforePlaying {
                loop {
                  self.new_tiles();
                  if self.tiles[y][x].touching == 0 && !self.tiles[y][x].mine {
                    break;
                  }
                }
              }
              self.state = State::Playing;
              //if that tile not reveal it, reveal it and all adjacent zero touching squares, etc
              if self.tiles[y][x].mine {
                self.tiles[y][x].revealed = true;
                self.state = State::Lost;
              } else if self.tiles[y][x].touching == 0 {
                let mut queue = VecDeque::new();
                queue.push_back([x, y]);
                let mut to_reveal = Vec::new();
                while queue.len() > 0 {
                  let current = queue.pop_front().unwrap();
                  self.on_adjacent_tiles(current[0], current[1], |x2, y2| {
                    if !queue.contains(&[x2, y2]) && !to_reveal.contains(&[x2, y2]) {
                      if self.tiles[y2][x2].touching == 0 {
                        queue.push_back([x2, y2]);
                      } else {
                        to_reveal.push([x2, y2]);
                      }
                    }
                  }, false);
                  to_reveal.push(current);
                }
                for r in to_reveal {
                  self.tiles[r[1]][r[0]].revealed = true;
                }
              } else {
                self.tiles[y][x].revealed = true;
              }
              self.first_char = '\0';
              if self.state != State::Lost {
                //check for win
                let mut won = true;
                for y in 0..16 {
                  for x in 0..16 {
                    let tile = &self.tiles[y][x];
                    if !tile.revealed && !tile.mine {
                      won = false;
                    }
                  }
                }
                if won {
                  self.state = State::Won;
                }
              }
              WindowMessageResponse::JustRedraw
            } else {
              WindowMessageResponse::DoNothing
            }
          } else {
            WindowMessageResponse::DoNothing
          }
        } else {
          self.tiles = Default::default();
          self.state = State::Seed;
          WindowMessageResponse::DoNothing
        }
      },
      _ => WindowMessageResponse::DoNothing,
    }
  }

  fn draw(&self, theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
    if self.state == State::Seed {
      vec![
        DrawInstructions::Text([4, 4], vec!["times-new-roman".to_string()], "Type in random characters to initalise the seed".to_string(), theme_info.text, theme_info.background, None, None),
        DrawInstructions::Text([4, 4 + 16], vec!["times-new-roman".to_string()], self.random_chars.clone(), theme_info.text, theme_info.background, None, None),
      ]
    } else {
      let mut instructions = vec![
        //top border
        DrawInstructions::Rect([1, 0], [self.dimensions[0] - 7, 5], [128, 128, 128]),
        DrawInstructions::Rect([self.dimensions[0] - 6, 0], [4, 1], [128, 128, 128]),
        DrawInstructions::Rect([self.dimensions[0] - 6, 1], [3, 1], [128, 128, 128]),
        DrawInstructions::Rect([self.dimensions[0] - 6, 2], [2, 1], [128, 128, 128]),
        DrawInstructions::Rect([self.dimensions[0] - 6, 3], [1, 1], [128, 128, 128]),
        DrawInstructions::Rect([self.dimensions[0] - 6, 4], [1, 1], [128, 128, 128]),
        //left border
        DrawInstructions::Rect([1, 0], [5, self.dimensions[1] - 5], [128, 128, 128]),
        DrawInstructions::Rect([1, self.dimensions[1] - 5], [1, 4], [128, 128, 128]),
        DrawInstructions::Rect([2, self.dimensions[1] - 5], [1, 3], [128, 128, 128]),
        DrawInstructions::Rect([3, self.dimensions[1] - 5], [1, 2], [128, 128, 128]),
        DrawInstructions::Rect([4, self.dimensions[1] - 5], [1, 1], [128, 128, 128]),
        //bottom border
        DrawInstructions::Rect([6, self.dimensions[1] - 6], [self.dimensions[0] - 2, 5], [255, 255, 255]),
        DrawInstructions::Rect([5, self.dimensions[1] - 5], [1, 4], [255, 255, 255]),
        DrawInstructions::Rect([4, self.dimensions[1] - 4], [1, 3], [255, 255, 255]),
        DrawInstructions::Rect([3, self.dimensions[1] - 3], [1, 2], [255, 255, 255]),
        DrawInstructions::Rect([2, self.dimensions[1] - 2], [1, 1], [255, 255, 255]),
        //right border
        DrawInstructions::Rect([self.dimensions[0] - 6, 5], [5, self.dimensions[1]], [255, 255, 255]),
        DrawInstructions::Rect([self.dimensions[0] - 2, 0], [1, 5], [255, 255, 255]),
        DrawInstructions::Rect([self.dimensions[0] - 3, 1], [1, 4], [255, 255, 255]),
        DrawInstructions::Rect([self.dimensions[0] - 4, 2], [1, 3], [255, 255, 255]),
        DrawInstructions::Rect([self.dimensions[0] - 5, 3], [1, 2], [255, 255, 255]),
        DrawInstructions::Rect([self.dimensions[0] - 6, 4], [1, 1], [255, 255, 255]),
      ];
      let tile_size = (self.dimensions[0] - 10) / 16;
      for y in 0..16 {
        for x in 0..16 {
          let tile = &self.tiles[y][x];
          if tile.revealed {
            if tile.mine {
              instructions.push(DrawInstructions::Text([x * tile_size + tile_size / 2 + 2, y * tile_size + tile_size / 2], vec!["times-new-roman".to_string()], "x".to_string(), [255, 0, 0], theme_info.background, None, None));
            } else {
              let color = match tile.touching {
                1 => [0, 0, 255],
                2 => [0, 255, 0],
                3 => [255, 0, 0],
                4 => [128, 0, 128],
                5 => [176, 48, 96],
                6 => [127, 255, 212],
                7 => [0, 0, 0],
                //8
                _ => [128, 128, 128],
              };
              instructions.push(DrawInstructions::Text([x * tile_size + tile_size / 2 + 5, y * tile_size + tile_size / 2 + 2], vec!["times-new-roman".to_string()], tile.touching.to_string(), color, theme_info.background, None, None));
            }
          } else {
            let top_left = [x * tile_size + 6, y * tile_size + 5];
            //do not do the corners in respect of our poor poor heap (vector size too big would be bad)
            instructions.extend(vec![
              //top border
              DrawInstructions::Rect([top_left[0], top_left[1]], [tile_size - 3, 3], [255, 255, 255]),
              //
              //left border
              DrawInstructions::Rect([top_left[0], top_left[1]], [3, tile_size - 3], [255, 255, 255]),
              //
              //bottom border
              DrawInstructions::Rect([top_left[0] + 3, top_left[1] + tile_size - 4], [tile_size - 4, 3], [128, 128, 128]),
              //
              //right bottom
              DrawInstructions::Rect([top_left[0] + tile_size - 4, top_left[1] + 3], [3, tile_size - 4], [128, 128, 128]),
              //
              DrawInstructions::Text([x * tile_size + tile_size / 2 - 2, y * tile_size + tile_size / 2], vec!["times-new-roman".to_string()], u8_to_hex((y * 16 + x) as u8), theme_info.text, theme_info.background, None, None),
            ]);
          }
        }
      }
      if self.state == State::Lost {
        instructions.extend(vec![DrawInstructions::Text([4, 4], vec!["times-new-roman".to_string()], "You LOST!!! Press a key to play again.".to_string(), theme_info.text, theme_info.background, None, None)]);
      } else if self.state == State::Won {
        instructions.extend(vec![DrawInstructions::Text([4, 4], vec!["times-new-roman".to_string()], "You WON!!! Press a key to play again.".to_string(), theme_info.text, theme_info.background, None, None)]);
      }
      instructions
    }
  }

  //properties

  fn title(&self) -> String {
    "Minesweeper".to_string()
  }

  fn subtype(&self) -> WindowLikeType {
    WindowLikeType::Window
  }

  fn ideal_dimensions(&self, _dimensions: Dimensions) -> Dimensions {
    [410, 410]
  }
}

impl Minesweeper {
  pub fn new() -> Self {
    Default::default()
  }

  //https://en.wikipedia.org/wiki/Xorshift
  //from 0 to 15
  pub fn random(&mut self) -> usize {
    let mut x = self.random_seed;
    x ^= x << 13;
    x ^= x >> 17;
    x ^= x << 5;
    self.random_seed = x;
    self.random_seed as usize % 16
  }

  pub fn on_adjacent_tiles(&self, x: usize, y: usize, mut action: impl FnMut(usize, usize) -> (), if_mine: bool) {
    if y > 0 {
      //above
      if self.tiles[y - 1][x].mine == if_mine {
        action(x, y - 1);
      }
      if x > 0 {
        //above to the left
        if self.tiles[y - 1][x - 1].mine == if_mine {
          action(x - 1, y - 1);
        }
      }
      if x < 15 {
        //above to the right
        if self.tiles[y - 1][x + 1].mine == if_mine {
          action(x + 1, y - 1);
        }
      }
    }
    if x > 0 {
      //to the left
      if self.tiles[y][x - 1].mine == if_mine {
        action(x - 1, y);
      }
    }
    if x < 15 {
      //to the right
      if self.tiles[y][x + 1].mine == if_mine {
        action(x + 1, y);
      }
    }
    if y < 15 {
      //below
      if self.tiles[y + 1][x].mine == if_mine {
        action(x, y + 1);
      }
      if x > 0 {
        //below to the left
        if self.tiles[y + 1][x - 1].mine == if_mine {
          action(x - 1, y + 1);
        }
      }
      if x < 15 {
        //below to the right
        if self.tiles[y + 1][x + 1].mine == if_mine {
          action(x + 1, y + 1);
        }
      }
    }
  }

  pub fn new_tiles(&mut self) {
    self.tiles = Default::default();
    //40 mines
    for _ in 0..40 {
      loop {
        let x = self.random();
        let y = self.random();
        //
        if !self.tiles[y][x].mine {
          self.tiles[y][x].mine = true;
          break;
        }
      }
    }
    //calculate touching
    for y in 0..16 {
      for x in 0..16 {
        let mut touching = 0;
        self.on_adjacent_tiles(x, y, |_, _| {
          touching += 1;
        }, true);
        self.tiles[y][x].touching = touching;
      }
    }
  }
  //
}

pub fn main() {
  listen(Minesweeper::new());
}

