#![windows_subsystem = "windows"] // Don't show a console 

use eframe::egui;
use egui::*;

use image::DynamicImage;

use rfd::FileDialog;

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

use std::path::Path;
use std::collections::HashSet;
use std::rc::Rc;
use std::cell::RefCell;

fn main() {
    let native_options = eframe::NativeOptions::default();
    eframe::run_native("Board Builder", native_options, Box::new(|cc| Box::new(BoardBuilderApp::new(cc))));
}

#[derive(Default)]
struct BoardBuilderApp {
    board: Rc<RefCell<Board>>,
    texture: Option<TextureHandle>,
    image: Option<image::DynamicImage>,
    edit_mode: EditMode,
    selected_node: Option<usize>,
    create_node_dialog: CreateNodeDialog,
    edit_node_dialog: EditNodeDialog,
    edit_labels_dialog: EditLabelsDialog,
}

#[derive(Clone, Copy)]
enum EditMode {
    Nodes,
    Edges,
}
impl Default for EditMode {
    fn default() -> Self {
        EditMode::Nodes
    }
}

impl eframe::App for BoardBuilderApp {
    fn update(&mut self, ctx: &Context, _frame: &mut eframe::Frame) {
        TopBottomPanel::top("menubar").show(ctx, |ui| {
            menu::bar(ui, |ui| {
                ui.menu_button("File", |ui| {
                    if ui.button("New").clicked() {
                        self.board.replace(Board::default());
                        self.texture = None;
                        self.image = None;
                        self.edit_mode = EditMode::Nodes;
                        ui.close_menu();
                    }
                    if ui.button("Open...").clicked() {
                        if let Some(board_file) = FileDialog::new().pick_file() {
                            match read_board_from_file(&board_file) {
                                Ok((board, image)) => {
                                    self.board.replace(board);
                                    match image {
                                        None => { self.image = None; self.texture = None }
                                        Some(image) => self.load_image(ctx, image),
                                    }
                                }
                                Err(_) => panic!("Could not open file!"),
                            }
                        }
                        ui.close_menu();
                    }
                    if ui.button("Save As...").clicked() {
                        if let Some(board_file) = FileDialog::new().save_file() {
                            write_board_to_file(&self.board.borrow(), self.image.as_ref(), &board_file).expect("Something went wrong saving!");
                        }
                    }
                    if ui.button("Open Image...").clicked() {
                        let image_file = FileDialog::new()
                            .add_filter("Image", &["png", "jpg", "jpeg", "gif", "webp", "bmp", "tiff"])
                            .pick_file();
                        if let Some(image_file) = image_file {
                            self.load_image_file(ctx, &image_file).unwrap();
                        }
                    }
                });
                ui.menu_button("Edit", |ui| {
                    if ui.button("Edit Nodes").clicked() {
                        self.edit_mode = EditMode::Nodes;
                    }
                    if ui.button("Edit Edges").clicked() {
                        self.edit_mode = EditMode::Edges;
                    }
                    if ui.button("Edit Labels...").clicked() {
                        self.edit_labels_dialog.show(Rc::clone(&self.board));
                    }
                })
            });
        });
        CentralPanel::default().show(ctx, |ui| {
            if let Some(texture) = self.texture.as_ref() {
                let size = ui.available_size();
                let (response, painter) = ui.allocate_painter(size, Sense::click());
                let image = widgets::Image::new(texture, size);
                image.paint_at(ui, response.rect);
                let view = View(response.rect);
                self.draw_board(&painter, view);
                if let Some(pos) = response.interact_pointer_pos() {
                    let btn = if response.clicked() {
                        PointerButton::Primary
                    } else if response.secondary_clicked() {
                        PointerButton::Secondary
                    } else {
                        PointerButton::Middle
                    };
                    let (x, y) = view.xform(pos);
                    self.dispatch_click(btn, x, y);
                }
            }
        });
        
        self.create_node_dialog.ui(ctx);
        self.edit_node_dialog.ui(ctx);
        self.edit_labels_dialog.ui(ctx);
    }
}

impl BoardBuilderApp {
    fn new(cc: &eframe::CreationContext<'_>) -> Self {
        let mut style = (*cc.egui_ctx.style()).clone();
        let mut button = style::TextStyle::Button.resolve(&style);
        button.size = 20.0;
        style.text_styles.insert(style::TextStyle::Button, button);
        cc.egui_ctx.set_style(style);

        BoardBuilderApp::default()
    }
    fn load_image_file(&mut self, ctx: &Context, image_file: &Path) -> Result<(), image::ImageError> {
        let image = image::io::Reader::open(image_file)?.decode()?;
        self.load_image(ctx, image);

        Ok(())
    }

    fn load_image(&mut self, ctx: &Context, image: DynamicImage) {
        let egui_image = egui::ColorImage::from_rgba_unmultiplied(
            [image.width() as _, image.height() as _],
            image.to_rgba8().as_flat_samples().as_slice(),
        );

        self.image = Some(image);
        self.texture = Some(ctx.load_texture("board-image", egui_image));
    }

    fn draw_board(&self, painter: &Painter, view: View) {
        let board = self.board.borrow();
        for (&id, node) in &board.nodes {
            let color = if Some(id) == self.selected_node {
                Color32::RED
            } else {
                Color32::BLACK
            };
            painter.text(
                view.inv_xform(node.x, node.y),
                Align2::CENTER_CENTER,
                &node.name,
                FontId::proportional(16.0),
                color,
            );
            let stroke = Stroke { width: 1.0, color: Color32::BLACK };
            for edge in &node.edges {
                let other_node = &board.nodes[edge];
                painter.line_segment(
                    [view.inv_xform(node.x, node.y), view.inv_xform(other_node.x, other_node.y)],
                    stroke,
                );
            }
        }
    }

    fn dispatch_click(&mut self, btn: PointerButton, x: f32, y: f32) {
        use EditMode::*;
        use PointerButton::*;
        match (btn, self.edit_mode) {
            (Primary, Nodes) => {
                let board = Rc::clone(&self.board);
                self.create_node_dialog.show(x, y, board);
            }
            (Primary, Edges) => self.select_edge(x, y),
            (Secondary, Nodes) => {
                let board = Rc::clone(&self.board);
                if let Some(id) = self.board.borrow().nearest_node(x, y) {
                   self.edit_node_dialog.show(id, board);
                }
            },
            _ => {},
        }
    }

    fn select_edge(&mut self, x: f32, y: f32) {
        let mut board = self.board.borrow_mut();
        if let Some(nearest_id) = board.nearest_node(x, y) {
            match self.selected_node {
                None => self.selected_node = Some(nearest_id),
                Some(id) if id == nearest_id => self.selected_node = None,
                Some(id) => board.add_edge(id, nearest_id),
            }
        }
    }
}

#[derive(Clone, Copy)]
struct View(Rect);
impl CoordTransformer<Pos2> for View {
    fn origin(&self) -> Pos2 { self.0.min }
    fn extremes(&self) -> Pos2 { self.0.max }
}

#[derive(Default)]
struct CreateNodeDialog {
    open: bool,
    node: Node,
    board: Rc<RefCell<Board>>,
}

impl CreateNodeDialog {
    fn show(&mut self, x: f32, y: f32, board: Rc<RefCell<Board>>) {
        self.open = true;
        self.board = board;

        self.node = Node {
            x,
            y,
            ..Node::default()
        }
    }

    fn ui(&mut self, ctx: &Context) {
        if !self.open { return }
        Window::new("Create Node")
            .collapsible(false)
            .show(ctx, |ui| {
                let mut board = self.board.borrow_mut();
                node_common_ui(ui, &mut self.node, &board);
                if ui.button("Ok").clicked() {
                    board.insert_node(self.node.clone());
                    self.open = false;
                }
                if ui.button("Cancel").clicked() {
                    self.open = false;
                }
            });
    }
}

#[derive(Default)]
struct EditNodeDialog {
    open: bool,
    id: usize,
    node: Node,
    board: Rc<RefCell<Board>>,
}

impl EditNodeDialog {
    fn show(&mut self, id: usize, board: Rc<RefCell<Board>>) {
        self.open = true;
        self.id = id;
        self.board = board;
        self.node = self.board.borrow().nodes.get(&self.id).unwrap().clone();
    }

    fn ui(&mut self, ctx: &Context) {
        if !self.open { return }
        Window::new("Edit Node")
            .collapsible(false)
            .show(ctx, |ui| {
                let mut board = self.board.borrow_mut();
                node_common_ui(ui, &mut self.node, &board);
                if ui.button("Ok").clicked() {
                    board.nodes.insert(self.id, self.node.clone());
                    self.open = false;
                }
                if ui.button("Delete").clicked() {
                    board.remove_node(self.id);
                    self.open = false;
                }
                if ui.button("Cancel").clicked() {
                    self.open = false;
                }
            });
    }
}


#[derive(Default)]
struct EditLabelsDialog {
    open: bool,
    board: Rc<RefCell<Board>>,
    selected_label_key: String,
    selected_label_value: String,
    add_key_dialog: StringDialog,
    add_value_dialog: StringDialog,
}

impl EditLabelsDialog {
    fn show(&mut self, board: Rc<RefCell<Board>>) {
        self.board = board;
        self.open = true;
    }

    fn ui(&mut self, ctx: &Context) {
        Window::new("Edit Labels")
            .collapsible(false)
            .open(&mut self.open)
            .show(ctx, |ui| {
                let mut board = self.board.borrow_mut();
                let selected_label_key = &mut self.selected_label_key;
                let selected_label_value = &mut self.selected_label_value;
                ui.columns(2, |col| {
                    col[0].group(|ui| {
                        for key_label in board.labels.keys() {
                            ui.selectable_value(selected_label_key, key_label.clone(), key_label);
                        }
                    });
                    col[0].horizontal(|ui| {
                        if ui.button("+").clicked() {
                            self.add_key_dialog.show();
                        }
                        if ui.button("-").clicked() {
                            board.remove_label_key(selected_label_key);
                            *selected_label_key = String::new();
                            *selected_label_value = String::new();
                        }
                    });
                    col[1].group(|ui| {
                        if let Some(value_labels) = board.labels.get(selected_label_key) {
                            for value_label in value_labels {
                                ui.selectable_value(selected_label_value, value_label.clone(), value_label);
                            }
                        }
                    });
                    col[1].horizontal(|ui| {
                        if ui.button("+").clicked() {
                            self.add_value_dialog.show();
                        }
                        if ui.button("-").clicked() {
                            board.remove_label_value(selected_label_key, selected_label_value);
                            *selected_label_value = String::new();
                        }
                    });
                });
                if self.add_key_dialog.open {
                    if let StringDialogResponse::Accepted(key) =
                        self.add_key_dialog.ui(ui, "Add Key") {
                            board.labels.insert(key, HashSet::new());
                        }
                }
                if self.add_value_dialog.open && selected_label_key != &String::new() {
                    if let StringDialogResponse::Accepted(value) =
                        self.add_value_dialog.ui(ui, "Add Value") {
                            board.labels.get_mut(selected_label_key).unwrap().insert(value);
                        }
                    }
            });
    }
}


#[derive(Default)]
struct StringDialog {
    string: String,
    open: bool,
}

enum StringDialogResponse {
    Null,
    Cancelled,
    Accepted(String)
}

impl StringDialog {
    fn show(&mut self) {
        self.open = true;
        self.string = String::new();
    }

    fn ui(&mut self, ui: &Ui, label: &str) -> StringDialogResponse {
        match Window::new(label)
            .collapsible(false)
            .show(ui.ctx(), |ui| {
                ui.text_edit_singleline(&mut self.string);
                (ui.button("Ok"), ui.button("Cancel"))
            }) {
                Some(InnerResponse { inner: Some((ok, cancel)), ..}) => {
                    if ok.clicked() {
                        self.open = false;
                        StringDialogResponse::Accepted(self.string.clone())
                    } else if cancel.clicked() {
                        self.open = false;
                        StringDialogResponse::Cancelled
                    } else {
                        StringDialogResponse::Null
                    }

                }
                _ => StringDialogResponse::Null,
            }
    }
}

fn node_common_ui(ui: &mut Ui, node: &mut Node, board: &Board) {
    ui.text_edit_singleline(&mut node.name);
    for (key, choices) in &board.labels {
        let choices = choices.clone();
        let default = choices.iter().next().unwrap().clone();
        let current = node.labels.entry(key.clone()).or_insert(default);
        ComboBox::from_label(key)
            .selected_text(current.to_string())
            .show_ui(ui, |ui| {
                for choice in choices {
                    ui.selectable_value(current, choice.clone(), choice);
                }
            });
    }
}
