use fltk::*;
use fltk::prelude::*;
use fltk::enums::*;
use fltk::image::PngImage;

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

use state::Storage;

use std::sync::Mutex;

use board_builder::Board;
use board_builder::{ encode_png, write_board_to_file, read_board_from_file, CoordTransformer };

//////////////////// Global State ////////////////////
// Don't @ me...
static STATE: Storage<Mutex<AppState>> = Storage::new();

//////////////////// Auxilary Dialogs ////////////////////
fn add_remove_labels_dialog() {
    let state = STATE.get().lock().unwrap();
    let mut win = window::Window::default()
	.with_size(300, 200)
	.center_of_parent();
    let flex = group::Flex::default()
	.size_of_parent()
	.center_of_parent();
    let mut col = group::Flex::default().column();
    let mut label_key_browser = browser::HoldBrowser::default();
    for key in state.board.labels.keys() {
	label_key_browser.add(key);
    }
    let brow = group::Flex::default();
    button::Button::default().with_label("-");
    button::Button::default().with_label("+");
    brow.end();
    col.set_size(&brow, 30);
    col.end();
    let mut label_value_browser = browser::HoldBrowser::default();
    label_key_browser.set_callback({
	let labels = state.board.labels.clone();
	move |k| {
	    label_value_browser.clear();
	    if let Some(key) = k.selected_text() {
		for val in &labels[&key] {
		    label_value_browser.add(val);
		}
	    }
	}
    });
    flex.end();
    win.end();
    win.make_resizable(true);
    win.make_modal(true);
    win.show();
    while win.shown() {
	app::wait();
    }
}   
    
fn node_create_dialog(pos_x: f32, pos_y: f32) {
    let mut win = window::Window::default()
	.with_size(100, 100)
	.with_pos(app::event_x_root(), app::event_y_root());
    let flex = group::Flex::default()
	.column()
	.size_of_parent()
	.center_of_parent();
    frame::Frame::default().with_label("Name:");
    let name = input::Input::default();
    let mut btn = button::Button::default()
	.with_label("Create");
    flex.end();
    win.end();
    win.make_resizable(true);
    win.make_modal(true);
    win.show();
    btn.set_callback({
	let mut win = win.clone();
	move |_| {
	    let mut state = STATE.get().lock().unwrap();
	    state.board.add_node(pos_x, pos_y, name.value());
	    win.hide();
	}
    });
    while win.shown() {
	app::wait();
    }
}

//////////////////// Dispatching Functions ////////////////////

mod dispatch {
    use super::*;
    type Coords = (f32, f32);
    pub(super) fn node_press(coords: Coords) {
	let (pos_x, pos_y) = coords;
	if app::event_button() == 1 {
	    node_create_dialog(pos_x, pos_y);
	} else if app::event_button() == 3 {
	    let mut state = STATE.get().lock().unwrap();
	    let id = state.board.nearest_node(pos_x, pos_y);
	    if let Some(id) = id {
		state.board.remove_node(id);
	    }
	}
	app::redraw();
    }
    pub(super) fn edge_press(coords: Coords) {
	let (pos_x, pos_y) = coords;
	let mut state = STATE.get().lock().unwrap();
	if app::event_button() == 1 {
	    match (state.selected_node, state.board.nearest_node(pos_x, pos_y)) {
		(Some(selected), Some(nearest)) => {
		    if selected == nearest {
			state.selected_node = None;
		    } else {
                        state.board.add_edge(selected, nearest);
		    }
		    app::redraw();
		}
		(None, Some(nearest)) => {
		    state.selected_node = Some(nearest);
		    app::redraw();
		}
		_ => (),
	    }
	}
    }
}

struct FrameView<'a>(&'a frame::Frame);
struct Pos(i32, i32);

impl<'a> FrameView<'a> {
    fn origin(&self) -> Pos {
	Pos(self.0.x(), self.0.y())
    }

    fn extremes(&self) -> Pos {
	Pos(self.0.x() + self.0.w(), self.0.y() + self.0.h())
    }
}

impl<'a> CoordTransformer<Pos> for FrameView<'a> {
    fn origin(&self) -> Pos { self.origin() }
    fn extremes(&self) -> Pos { self.extremes() }
}

impl From<(f32, f32)> for Pos {
    fn from((x, y): (f32, f32)) -> Self {
	Pos(x as i32, y as i32)
    }
}

impl From<Pos> for (f32, f32) {
    fn from(Pos(x, y): Pos) -> Self {
	(x as f32, y as f32)
    }
}
	

//////////////////// App State ////////////////////

#[derive(Clone, Copy)]
enum EditMode {
    Node,
    Edge,
}
struct AppState {
    pub board: Board,
    pub image_raw: Option<DynamicImage>,
    pub image: Option<PngImage>,
    pub selected_node: Option<usize>,
    pub edit_mode: EditMode,
}
impl AppState {
    fn new() -> Self {
	AppState {
	    board: Board::new(),
	    image_raw: None,
	    image: None,
	    selected_node: None,
	    edit_mode: EditMode::Node
	}
    }
}

//////////////////// Procedural App Creation ////////////////////

fn main() {
    // State
    STATE.set(Mutex::new(AppState::new()));
    // App setup
    let app = app::App::default()
	.with_scheme(app::Scheme::Gtk);
    let mut win = window::Window::default()
	.with_size(400, 300)
	.with_label("Board Builder");
    let mut flex = group::Flex::default()
	.size_of_parent();
    flex.set_type(group::FlexType::Column);

    // Menu
    let mut menubar = menu::MenuBar::default();
    menubar.add("File/New", Shortcut::None, menu::MenuFlag::Normal, move |_| {
	*STATE.get().lock().unwrap() = AppState::new();
	app::redraw();
    });
    menubar.add("File/Open...", Shortcut::None, menu::MenuFlag::Normal, move |_| {
	let mut state = AppState::new();
	let mut fc = dialog::NativeFileChooser::new(dialog::NativeFileChooserType::BrowseFile);
	fc.show();

	let (board, raw_image) = read_board_from_file(&fc.filename()).unwrap();

	state.board = board;
	state.image_raw = raw_image;
	if let Some(image) = state.image_raw.as_ref() {
	    state.image = Some(PngImage::from_data(&encode_png(image)).unwrap());
	} else {
	    state.image = None;
	}
	*STATE.get().lock().unwrap() = state;
	app::redraw();
    });
    menubar.add("File/Open Image...", Shortcut::None, menu::MenuFlag::Normal, move |_| {
	let mut fc = dialog::NativeFileChooser::new(dialog::NativeFileChooserType::BrowseFile);
	fc.show();
	let filename = fc.filename();

	match ImageReader::open(filename).map(|i| i.decode()) {
	    Ok(Ok(image)) => {
		let mut state = STATE.get().lock().unwrap();
		let data = encode_png(&image);
		state.image = Some(PngImage::from_data(&data).unwrap());
		state.image_raw = Some(image);
		app::redraw();
	    }
	    _ => dialog::alert_default("Error opening file"),
	}
    });
    menubar.add("File/Save As ...", Shortcut::None, menu::MenuFlag::Normal, move |_| {
	let state = STATE.get().lock().unwrap();
	let mut fc = dialog::NativeFileChooser::new(dialog::NativeFileChooserType::BrowseSaveFile);
	fc.show();
	let filename = fc.filename();
	write_board_to_file(&state.board, state.image_raw.as_ref(), &filename).unwrap();
    });
    menubar.add("Edit/Edit Nodes", Shortcut::None, menu::MenuFlag::Normal, |_| {
	let mut state = STATE.get().lock().unwrap();
	state.edit_mode = EditMode::Node;
    });
    menubar.add("Edit/Edit Edges", Shortcut::None, menu::MenuFlag::Normal, |_| {
	let mut state = STATE.get().lock().unwrap();
	state.edit_mode = EditMode::Edge;
    });
    menubar.add("Edit/Edit Labels", Shortcut::None, menu::MenuFlag::Normal, |_| add_remove_labels_dialog());
    
    flex.set_size(&menubar, 40);

    // Canvas
    let mut frame = frame::Frame::default();
    frame.draw(move |f| {
	use draw::*;
	let mut state = STATE.get().lock().unwrap();
	// Background
	let image = &mut state.image;
	if let Some(image) = image.as_mut() {
	    image.scale(f.w(), f.h(), false, true);
	    image.draw(f.x(), f.y(), f.w(), f.h());
	}
	// Nodes
	let board = &state.board;
	let f = FrameView(f);
	for (&id, node) in &board.nodes {
	    // Draw the node
	    let Pos(x, y) = f.inv_xform(node.x, node.y);
	    if state.selected_node == Some(id) {
		set_draw_color(Color::Red);
		draw_text(&node.name, x, y);
		set_draw_color(Color::Black);
	    } else {
		draw_text(&node.name, x, y);
	    }
	    // Draw edges
	    for other_id in &node.edges {
		let other = board.nodes.get(other_id).unwrap();
		let Pos(x1, y1) = f.inv_xform(other.x, other.y);
		draw_line(x, y, x1, y1);
	    }
	}
    });
    frame.handle(move |f, e| {
	match e {
	    Event::Push => {
		let f = FrameView(f);
		let edit_mode = STATE.get().lock().unwrap().edit_mode;
		let coords = f.xform(Pos(app::event_x(), app::event_y()));

		match edit_mode {
		    EditMode::Node => dispatch::node_press(coords),
		    EditMode::Edge => dispatch::edge_press(coords),
		}
		true
	    }
	    _ => false,
	}
    });

    flex.end();
    win.end();
    win.make_resizable(true);
    win.show();
    app.run().unwrap();
}
