use serde::{ Serialize, Deserialize };
use std::collections::{ HashMap, HashSet };

#[derive(Serialize, Deserialize, Debug, Default, Clone)]
#[serde(default)]
pub struct Node {
    pub x: f32,
    pub y: f32,

    pub name: String,
    pub edges: HashSet<usize>,
    pub labels: HashMap<String, String>,
}
#[derive(Serialize, Deserialize, Debug, Default)]
#[serde(default)]
pub struct Board {
    pub labels: HashMap<String, HashSet<String>>,
    pub nodes: HashMap<usize, Node>,
}
impl Board {
    pub fn new() -> Self {
        let nodes = HashMap::new();
        let labels = HashMap::new();
        Board { nodes, labels }
    }

    pub fn add_node(&mut self, x: f32, y: f32, name: String) {
        self.nodes.insert(self.next_id(), Node {
            x,
            y,
            name,
            edges: HashSet::new(),
            labels: HashMap::new(),
        });
    }

    pub fn insert_node(&mut self, node: Node) -> usize {
        let id = self.next_id();
        self.nodes.insert(id, node);
        id
    }

    pub fn remove_node(&mut self, id: usize) {
        // We remove this node from the graph, then drop it from each
        // other nodes edge.
        self.nodes.remove(&id);
        for node in self.nodes.values_mut() {
            node.edges.remove(&id);
        }
    }

    pub fn remove_label_key(&mut self, key: &str) {
        self.labels.remove(key);
    }

    pub fn remove_label_value(&mut self, key: &str, value: &str) {
        if let Some(l) = self.labels.get_mut(key) {
            l.remove(value);
        }
    }

    pub fn add_edge(&mut self, from: usize, to: usize) {
	let node = self.nodes.get_mut(&from).expect("Could not find node");
	node.edges.insert(to);
    }

    pub fn nearest_node(&self, x: f32, y: f32) -> Option<usize> {
        let f = |n: &Node| dist_sq((n.x, n.y), (x, y));
        let mut iter = self.nodes.iter();
        if let Some((id, node)) = iter.next() {
            let mut min = *id;
            let mut min_val = f(node);
            for (id, node) in iter {
                let val = f(node);
                if val < min_val {
                    min = *id;
                    min_val = val;
                }
            }
            Some(min)
        } else {
            None
        }
    }

    fn next_id(&self) -> usize {
        for i in 0 .. {
            if !self.nodes.contains_key(&i) {
                return i;
            }
        }
        unreachable!();
    }
}

fn dist_sq((x0, y0): (f32, f32), (x1, y1): (f32, f32)) -> f32 {
    (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)
}

use std::io;
use std::io::{ Write, Read, Cursor };
use std::fs::File;
use std::path::Path;

use image::io::Reader as ImageReader;
use image::{ DynamicImage };

pub fn write_board_to_file(board: &Board, image: Option<&DynamicImage>, path: &Path) -> io::Result<()> {
    let file = File::create(path)?;
    let mut ar = zip::ZipWriter::new(file);
    let options = zip::write::FileOptions::default();

    ar.start_file("graph.json", options)?;
    ar.write_all(&serde_json::to_vec(board)?)?;
    if let Some(image) = image {
        ar.start_file("image.png", options)?;
        let data = encode_png(image);
        ar.write_all(&data)?;
        ar.flush()?;
    }
    ar.finish()?;
    Ok(())
}

pub fn read_board_from_file(path: &Path) -> io::Result<(Board, Option<DynamicImage>)> {
    let file = File::open(path)?;
    let mut ar = zip::ZipArchive::new(file)?;

    let json_file = ar.by_name("graph.json")?;
    let board = serde_json::from_reader(json_file)?;
    let image = ar.by_name("image.png").ok();
    if image.is_none() {
        return Ok((board, None))
    }
    let mut image_file = image.unwrap();
    let mut buf = Vec::new();
    image_file.read_to_end(&mut buf)?;
    let image = decode_png(&buf);
    Ok((board, Some(image)))
}

pub fn encode_png(image: &DynamicImage) -> Vec<u8> {
    let mut cursor = Cursor::new(Vec::new());
    image.write_to(&mut cursor, image::ImageOutputFormat::Png).unwrap();
    cursor.into_inner()
}

pub fn decode_png(buf: &[u8]) -> DynamicImage {
    let cursor = Cursor::new(buf);
    let mut reader = ImageReader::new(cursor);
    reader.set_format(image::ImageFormat::Png);
    reader.decode().unwrap()
}

fn lerp(v0: f32, v1: f32, t: f32) -> f32 {
    v0 + t * (v1 - v0)
}

fn inv_lerp(v0: f32, v1: f32, a: f32) -> f32 {
    (a - v0) / (v1 - v0)
}

pub trait CoordTransformer<I: Into<(f32, f32)> + From<(f32, f32)>> {
    fn origin(&self) -> I;
    fn extremes(&self) -> I;
    fn xform(&self, pos: I) -> (f32, f32) {
        let (sx, sy) = self.origin().into();
        let (ex, ey) = self.extremes().into();
        let (x, y) = pos.into();
        (
            inv_lerp(sx, ex, x),
            inv_lerp(sy, ey, y),
        )
    }
    fn inv_xform(&self, x: f32, y: f32) -> I {
        let (sx, sy) = self.origin().into();
        let (ex, ey) = self.extremes().into();
        (lerp(sx, ex, x), lerp(sy, ey, y)).into()
    }
}
