use crate::application::{Action, State};
use crate::config::{APP_ID, PROFILE};
use crate::utils;
use crate::widgets::drawer::{Drawer, Filter};
use crate::window_state;
use gio::prelude::*;
use glib::Sender;
use gtk::prelude::*;
use std::cell::RefCell;
use std::rc::Rc;

#[derive(PartialEq, Debug)]
pub enum View {
    Empty,
    Photo,
}
const TARGET_URI: u32 = 1;

pub struct Window {
    pub widget: gtk::ApplicationWindow,
    sender: Sender<Action>,
    builder: gtk::Builder,
    drawer: RefCell<Rc<Drawer>>,
    settings: RefCell<gio::Settings>,
}

impl Window {
    pub fn new(sender: Sender<Action>) -> Rc<Self> {
        let settings = gio::Settings::new(APP_ID);
        let builder = gtk::Builder::new_from_resource("/com/belmoussaoui/Obfuscate/window.ui");
        get_widget!(builder, gtk::ApplicationWindow, window);
        let drawer = RefCell::new(Drawer::new(sender.clone()));

        let window_widget = Rc::new(Window {
            widget: window,
            sender,
            builder,
            settings: RefCell::new(settings),
            drawer,
        });

        window_widget.init();
        window_widget.setup_actions();
        window_widget.set_view(View::Empty);
        window_widget
    }

    pub fn set_view(&self, view: View) {
        get_widget!(self.builder, gtk::Stack, main_stack);
        get_widget!(self.builder, gtk::Stack, headerbar_stack);
        get_widget!(self.builder, gtk::Revealer, menu_revealer);
        match view {
            View::Empty => {
                headerbar_stack.set_visible_child_name("empty");
                main_stack.set_visible_child_name("empty");
                menu_revealer.set_reveal_child(false);
            }
            View::Photo => {
                headerbar_stack.set_visible_child_name("photo");
                main_stack.set_visible_child_name("photo");
                menu_revealer.set_reveal_child(true);
            }
        }

        let editor_actions = vec!["effect", "save", "zoom-in", "zoom-out"];
        let actions_group = self.widget.get_action_group("window").unwrap();
        let actions_group = actions_group.downcast::<gio::SimpleActionGroup>().unwrap();
        for action_name in editor_actions.iter() {
            if let Some(action) = actions_group.lookup_action(action_name) {
                let action = action.downcast::<gio::SimpleAction>().unwrap();
                action.set_enabled(view == View::Photo);
            }
        }
    }

    pub fn set_state(&self, state: State) {
        let action_group = self.widget.get_action_group("window").unwrap().downcast::<gio::SimpleActionGroup>().unwrap();
        match state {
            State::CanUndo(state) => {
                let undo_action = action_group.lookup_action("undo").unwrap().downcast::<gio::SimpleAction>().unwrap();
                undo_action.set_enabled(state);
            }
            State::CanRedo(state) => {
                let redo_action = action_group.lookup_action("redo").unwrap().downcast::<gio::SimpleAction>().unwrap();
                redo_action.set_enabled(state);
            }
            State::ZoomUp => {
                let new_zoom = self.drawer.borrow().get_zoom() + 0.1;
                self.set_zoom(new_zoom);
            }
            State::ZoomDown => {
                let new_zoom = self.drawer.borrow().get_zoom() - 0.1;
                self.set_zoom(new_zoom);
            }
            State::ZoomReset => {
                self.set_zoom(1.0);
            }
        }
    }

    pub fn set_open_file(&self, file: gio::File) -> Result<(), failure::Error> {
        let info = file.query_info("standard::display-name", gio::FileQueryInfoFlags::NONE, gio::NONE_CANCELLABLE)?;
        let display_name = info.get_attribute_as_string("standard::display-name").unwrap_or_else(|| "".into());

        get_widget!(self.builder, gtk::HeaderBar, headerbar);
        headerbar.set_subtitle(Some(&display_name));

        self.drawer.borrow_mut().draw_from_file(file)?;
        Ok(())
    }

    fn set_zoom(&self, new_zoom: f64) {
        if 0.1 <= new_zoom && new_zoom < 2.1 {
            let action_group = self.widget.get_action_group("window").unwrap().downcast::<gio::SimpleActionGroup>().unwrap();
            let zoom_out_action = action_group.lookup_action("zoom-out").unwrap().downcast::<gio::SimpleAction>().unwrap();
            let zoom_in_action = action_group.lookup_action("zoom-in").unwrap().downcast::<gio::SimpleAction>().unwrap();
            let zoom_reset_action = action_group.lookup_action("zoom-reset").unwrap().downcast::<gio::SimpleAction>().unwrap();
            get_widget!(self.builder, gtk::Label, zoom_level);

            zoom_level.set_text(&format!("{:.0}%", new_zoom * 100.0));

            zoom_out_action.set_enabled(new_zoom >= 0.1);
            zoom_in_action.set_enabled(new_zoom <= 2.0);
            zoom_reset_action.set_enabled((new_zoom - 1.0).abs() > std::f64::EPSILON);
            self.drawer.borrow().set_zoom(new_zoom);
        }
    }

    fn init(&self) {
        if PROFILE == "Devel" {
            self.widget.get_style_context().add_class("devel");
        }
        // load latest window state
        let settings = self.settings.borrow().clone();
        window_state::load(&self.widget, &settings);
        // save window state on delete event
        self.widget.connect_delete_event(move |window, _| {
            if let Err(err) = window_state::save(&window, &settings) {
                warn!("Failed to save window state {}", err);
            }
            Inhibit(false)
        });

        get_widget!(self.builder, gtk::Box, drawing_container);
        drawing_container.pack_start(&self.drawer.borrow_mut().widget, true, true, 0);

        get_widget!(self.builder, gtk::Stack, main_stack);

        let uri_target = gtk::TargetEntry::new("text/uri-list", gtk::TargetFlags::OTHER_APP, TARGET_URI);
        main_stack.drag_dest_set(gtk::DestDefaults::ALL, &[uri_target], gdk::DragAction::COPY);

        main_stack.connect_drag_data_received(clone!(@strong self.sender as sender => move |_, _, _, _, data, target_id, _| {
            if target_id == TARGET_URI {
                let uris = data.get_uris();
                if let Some(dropped_uri) = uris.get(0) {
                    let file = gio::File::new_for_uri(dropped_uri);
                    send!(sender, Action::LoadFile(file));
                }
            }
        }));
    }

    fn setup_actions(&self) {
        let actions = gio::SimpleActionGroup::new();
        utils::action(
            &actions,
            "open",
            None,
            clone!(@strong self.sender as sender => move |_, _| {
                send!(sender, Action::OpenFile);
            }),
        );

        // Undo
        let undo = utils::action(
            &actions,
            "undo",
            None,
            clone!(@strong self.drawer as drawer =>  move |_,  _| {
                drawer.borrow().undo();
            }),
        );
        undo.set_enabled(false);
        // Redo
        let redo = utils::action(
            &actions,
            "redo",
            None,
            clone!(@strong self.drawer as drawer => move |_, _| {
                drawer.borrow().redo();
            }),
        );
        redo.set_enabled(false);

        // Copy
        utils::action(
            &actions,
            "copy",
            None,
            clone!(@strong self.drawer as drawer => move |_, _| {
                drawer.borrow().copy();
            }),
        );
        // Paste
        utils::action(
            &actions,
            "paste",
            None,
            clone!(@strong self.drawer as drawer => move |_, _| {
                drawer.borrow().paste(drawer.borrow().clone());
            }),
        );
        // Save
        utils::action(
            &actions,
            "save",
            None,
            clone!(@strong self.drawer as drawer, @weak self.widget as parent => move |_, _| {
                let dialog = gtk::FileChooserNative::new(Some("Save File"), Some(&parent), gtk::FileChooserAction::Save, Some("Save"), Some("Cancel"));
                utils::setup_image_filters(&dialog);

                if dialog.run() == gtk::ResponseType::Accept {
                    let file = dialog.get_filename().unwrap();
                    drawer.borrow().save_to(file);
                };
                dialog.destroy();
            }),
        );
        get_widget!(self.builder, gtk::ToggleButton, blur_btn);
        get_widget!(self.builder, gtk::ToggleButton, filled_btn);

        blur_btn.bind_property("active", &filled_btn, "active").flags(glib::BindingFlags::INVERT_BOOLEAN).build();
        filled_btn.bind_property("active", &blur_btn, "active").flags(glib::BindingFlags::INVERT_BOOLEAN).build();

        blur_btn.connect_toggled(clone!(@strong self.drawer as drawer => move |toggle_btn| {
            if toggle_btn.get_active() {
                drawer.borrow_mut().set_filter(Filter::Blur);
            }
        }));
        filled_btn.connect_toggled(clone!(@strong self.drawer as drawer => move |toggle_btn| {
            if toggle_btn.get_active() {
                drawer.borrow_mut().set_filter(Filter::Filled);
            }
        }));

        utils::action(
            &actions,
            "zoom-in",
            None,
            clone!(@strong self.sender as sender => move |_, _| {
                send!(sender, Action::SetState(State::ZoomUp));
            }),
        );

        utils::action(
            &actions,
            "zoom-out",
            None,
            clone!(@strong self.sender as sender => move |_, _| {
                send!(sender, Action::SetState(State::ZoomDown));
            }),
        );

        let zoom_reset = utils::action(
            &actions,
            "zoom-reset",
            None,
            clone!(@strong self.sender as sender => move |_, _| {
                send!(sender, Action::SetState(State::ZoomReset));
            }),
        );
        zoom_reset.set_enabled(false); // start with zoom-reset disabled
        self.widget.insert_action_group("window", Some(&actions));
    }
}
