use std::path::{Path, PathBuf};
use std::fs::File;
use std::collections::{HashMap, BinaryHeap};
use std::cmp::Reverse;
use std::io::Write;

use crate::game::{Game, Coord};

/// An error when writing to a file
#[derive(Debug)]
pub enum WriterError {
    FileAccessError,
    WriteError,
}


/// Write the given game to the file at the given path. 
pub fn write_game_at_path(path: PathBuf, game: &Game) -> Result<(), WriterError> {
    let mut file = create_file(path.as_path())?;
    write_game(&mut file, game)
}

/// Write the given game to the writer. Currently only accept `std::io::Write` implementors. 
pub fn write_game(writer: &mut impl Write, game: &Game) -> Result<(), WriterError> {
    //let path = Path::new(path);
    let mut string = String::new();
    let mut values = CellRows::new();
    for i in game.cells() {
        values.insert(i);
    }
    for y in values.smallest_y..values.greatest_y+1 {
        if let Some(row) = values.rows.get_mut(&y) {
            for x in values.smallest_x..values.greatest_x+1 {
                if let Some(ch) = row.peek() {
                    if ch.0 == x {
                        string.push_str("*");
                        row.pop();
                    } else {
                        string.push_str(".");
                    }
                } else {
                    string.push_str(".");
                }
            }
        }
        string.push_str("\n");
    }
    match write!(writer, "{}", string) {
        Ok(_) => Ok(()),
        Err(_) => Err(WriterError::WriteError),
    }
}

fn create_file(path: &Path) -> Result<File, WriterError> {
    match File::create(path) {
        Ok(file) => Ok(file),
        Err(_) => Err(WriterError::FileAccessError),
    }
}

#[derive(Default)]
struct CellRows {
    rows: HashMap<isize, BinaryHeap<Reverse<isize>>>,
    smallest_x: isize,
    greatest_x: isize,
    smallest_y: isize,
    greatest_y: isize,
    smallest_x_set: bool,
    smallest_y_set: bool,
    greatest_x_set: bool,
    greatest_y_set: bool,
}

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

    pub fn insert(&mut self, (x, y): Coord) {
        if x < self.smallest_x || !self.smallest_x_set { self.smallest_x = x; self.smallest_x_set = true };
        if y < self.smallest_y || !self.smallest_y_set { self.smallest_y = y; self.smallest_y_set = true };
        if x > self.greatest_x || !self.greatest_x_set { self.greatest_x = x; self.greatest_x_set = true };
        if y > self.greatest_y || !self.greatest_y_set { self.greatest_y = y; self.greatest_y_set = true };
        self.rows.entry(y).and_modify(|heap| {heap.push(Reverse(x))}).or_insert({let mut heap = BinaryHeap::new(); heap.push(Reverse(x)); heap});
    }
}
