use std::vec::Vec;
use std::vec;
use std::fs::{ read_dir, metadata, Metadata };
use std::path::PathBuf;

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::ipc::listen;

const HEIGHT: usize = 20;

struct DirectoryChild {
  //if some, use instead of file/dir name
  override_name: Option<String>,
  path: PathBuf,
  is_file: bool,
}

#[derive(Default, PartialEq)]
enum State {
  #[default]
  List,
  Info,
}

#[derive(Default)]
pub struct FileExplorer {
  dimensions: Dimensions,
  current_path: PathBuf,
  current_dir_contents: Vec<DirectoryChild>,
  //for scrolling and selecting dirs
  position: usize,
  top_position: usize,
  state: State,
  metadata: Option<Metadata>,
}

impl WindowLike for FileExplorer {
  fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse {
    match message {
      WindowMessage::Init(dimensions) => {
        self.current_path = PathBuf::from("/");
        self.dimensions = dimensions;
        self.current_dir_contents = self.get_current_dir_contents();
        WindowMessageResponse::JustRedraw
      },
      WindowMessage::ChangeDimensions(dimensions) => {
        self.dimensions = dimensions;
        WindowMessageResponse::JustRedraw
      },
      WindowMessage::KeyPress(key_press) => {
        self.state = State::List;
        if key_press.key == '𐘂' { //the enter key
          if self.current_dir_contents.len() > 0 {
            let selected_entry = &self.current_dir_contents[self.position];
            if !selected_entry.is_file {
              self.current_path = selected_entry.path.clone();
              self.current_dir_contents = self.get_current_dir_contents();
              self.position = 0;
              self.top_position = 0;
              return WindowMessageResponse::JustRedraw;
            }
          }
          WindowMessageResponse::DoNothing
        } else if key_press.key == 'j' || key_press.key == 'k' {
          if key_press.key == 'j' {
            //down
            if self.position == self.current_dir_contents.len() - 1 {
              self.position = 0;
            } else {
              self.position += 1;
            }
          } else {
            //up
            if self.position == 0 {
              self.position = self.current_dir_contents.len() - 1;
            } else {
              self.position -= 1;
            }
          }
          //calculate position
          let max_height = self.dimensions[1] - HEIGHT;
          if self.position > self.top_position {
            let current_height = (self.position - self.top_position + 1) * HEIGHT;
            if current_height > self.dimensions[1] {
              //somehow this is slightly off sometimes
              self.top_position += (current_height - max_height).div_ceil(HEIGHT);
            }
          } else {
            self.top_position = self.position;
          };
          WindowMessageResponse::JustRedraw
        } else if key_press.key == 'i' {
          self.state = State::Info;
          let selected_entry = &self.current_dir_contents[self.position];
          self.metadata = Some(metadata(&selected_entry.path).unwrap());
          WindowMessageResponse::JustRedraw
        } else {
          WindowMessageResponse::DoNothing
        }
      },
      _ => WindowMessageResponse::DoNothing,
    }
  }

  fn draw(&self, theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
    let mut instructions = Vec::new();
    if self.state == State::List {
      //top bar with path name
      instructions.push(DrawInstructions::Text([5, 0], vec!["times-new-roman".to_string(), "shippori-mincho".to_string()], "Current: ".to_string() + &self.current_path.to_string_lossy().to_string(), theme_info.text, theme_info.background, None, None));
      //the actual files and directories
      let mut start_y = HEIGHT;
      let mut i = self.top_position;
      for entry in self.current_dir_contents.iter().skip(self.top_position) {
        if start_y > self.dimensions[1] {
          break;
        }
        let is_selected = i == self.position;
        if is_selected {
          instructions.push(DrawInstructions::Rect([0, start_y], [self.dimensions[0], HEIGHT], theme_info.top));
        }
        //unwrap_or not used because "Arguments passed to unwrap_or are eagerly evaluated", apparently
        let name = entry.override_name.clone();
        let name = if name.is_none() {
          entry.path.file_name().unwrap().to_os_string().into_string().unwrap()
        } else {
          name.unwrap()
        };
        instructions.push(DrawInstructions::Text([5, start_y], vec!["times-new-roman".to_string(), "shippori-mincho".to_string()], name, if is_selected { theme_info.top_text } else { theme_info.text }, if is_selected { theme_info.top } else { theme_info.background }, None, None));
        start_y += HEIGHT;
        i += 1;
      }
    } else if self.state == State::Info {
      let metadata = self.metadata.clone().unwrap();
      let mut start_y = HEIGHT;
      let bytes_len = metadata.len();
      instructions.push(DrawInstructions::Text([5, start_y], vec!["times-new-roman".to_string()], format!("Size: {} mb ({} b)", bytes_len / (1024_u64).pow(2), bytes_len), theme_info.text, theme_info.background, None, None));
      start_y += HEIGHT;
      //todo: other stuff
    }
    instructions
  }

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

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

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

  fn resizable(&self) -> bool {
    true
  }
}

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

  //should include .. if not /
  fn get_current_dir_contents(&self) -> Vec<DirectoryChild> {
    let mut contents = Vec::new();
    if self.current_path != PathBuf::from("/") {
      contents.push(DirectoryChild {
        override_name: Some("..".to_string()),
        is_file: false,
        path: self.current_path.parent().unwrap().to_owned(),
      });
    }
    contents.extend(read_dir(&self.current_path).unwrap().map(|entry| {
      let path = entry.unwrap().path();
      DirectoryChild {
        override_name: None,
        is_file: path.is_file(),
        path,
      }
    }));
    contents
  }
}

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