use super::utils;
use crate::config;
use crate::widgets::{View, Window};
use gio::prelude::*;
use gtk::prelude::*;
use gtk::SettingsExt;
use std::collections::HashMap;
use std::env;
use std::{cell::RefCell, rc::Rc};

use glib::{Receiver, Sender};
pub enum Action {
    OpenFile,
    LoadFile(gio::File),
    ViewEmpty,
    ViewPhoto,
    ResizeWindow(i32, i32),
    SetState(State),
}

pub enum State {
    CanUndo(bool),
    CanRedo(bool),
    ZoomUp,
    ZoomDown,
    ZoomReset,
}

pub struct Application {
    gtk_app: gtk::Application,
    windows: Rc<RefCell<HashMap<gtk::Window, Rc<Window>>>>,
    sender: Sender<Action>,
    receiver: RefCell<Option<Receiver<Action>>>,
}

impl Application {
    pub fn new() -> Rc<Self> {
        let gtk_app = gtk::Application::new(Some(config::APP_ID), Default::default()).unwrap();

        let (sender, r) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
        let receiver = RefCell::new(Some(r));

        let application = Rc::new(Self {
            gtk_app,
            windows: Rc::new(RefCell::new(HashMap::new())),
            sender,
            receiver,
        });
        application.setup_signals(application.clone());
        application.setup_css();
        application
    }

    pub fn run(&self, app: Rc<Self>) {
        info!("{}Obfuscate({})", config::NAME_PREFIX, config::APP_ID);
        info!("Version: {} ({})", config::VERSION, config::PROFILE);
        info!("Datadir: {}", config::PKGDATADIR);

        let receiver = self.receiver.borrow_mut().take().unwrap();
        receiver.attach(None, move |action| app.do_action(action));

        let args: Vec<String> = env::args().collect();
        self.gtk_app.run(&args);
    }

    fn get_window(&self) -> Rc<Window> {
        let gtk_window = self.gtk_app.get_active_window().expect("Failed to get a GtkWindow");
        self.windows.borrow().get(&gtk_window).expect("Failed to get a Window").clone()
    }

    fn create_window(&self) -> Rc<Window> {
        let window = Window::new(self.sender.clone());

        let builder = gtk::Builder::new_from_resource("/com/belmoussaoui/Obfuscate/shortcuts.ui");
        get_widget!(builder, gtk::ShortcutsWindow, shortcuts);
        window.widget.set_help_overlay(Some(&shortcuts));

        window.widget.set_application(Some(&self.gtk_app));
        self.gtk_app.add_window(&window.widget);

        let gtk_window = window.widget.clone();

        // TODO: fix this once we have subclasses
        self.windows.borrow_mut().insert(gtk_window.upcast::<gtk::Window>(), window.clone());
        window
    }

    fn setup_signals(&self, app: Rc<Self>) {
        self.gtk_app.connect_activate(clone!(@weak app => move |_| {
            let gtk_settings = gtk::Settings::get_default().unwrap();
            gtk_settings.set_property_gtk_application_prefer_dark_theme(true);
            app.get_window().widget.present();
        }));
        self.gtk_app.connect_window_removed(clone!(@weak app => move |_, window| {
            app.windows.borrow_mut().remove(window);
        }));
        self.gtk_app.connect_startup(clone!(@weak app => move |_| {
            app.create_window();
            app.setup_gactions(app.clone());
        }));
    }

    fn setup_gactions(&self, app: Rc<Self>) {
        // Quit
        utils::action(
            &self.gtk_app,
            "quit",
            None,
            clone!(@weak app => move |_ , _| {
                app.get_window().widget.destroy();
            }),
        );
        // About
        utils::action(
            &self.gtk_app,
            "about",
            None,
            clone!(@weak app => move |_, _| {
                let builder = gtk::Builder::new_from_resource("/com/belmoussaoui/Obfuscate/about_dialog.ui");
                get_widget!(builder, gtk::AboutDialog, about_dialog);
                let window = app.get_window().widget.clone();
                about_dialog.set_transient_for(Some(&window));

                about_dialog.connect_response(|dialog, _| dialog.destroy());
                about_dialog.show();
            }),
        );
        // New Window
        utils::action(
            &self.gtk_app,
            "new-window",
            None,
            clone!(@weak app => move |_, _| {
                let window = app.create_window();
                window.widget.present();
            }),
        );

        self.gtk_app.set_accels_for_action("app.new-window", &["<primary>n"]);
        self.gtk_app.set_accels_for_action("app.quit", &["<primary>q"]);
        self.gtk_app.set_accels_for_action("window.open", &["<primary>o"]);
        self.gtk_app.set_accels_for_action("window.save", &["<primary>s"]);
        self.gtk_app.set_accels_for_action("window.undo", &["<primary>z"]);
        self.gtk_app.set_accels_for_action("window.redo", &["<primary><shift>z"]);
        self.gtk_app.set_accels_for_action("window.paste", &["<primary>v"]);
        self.gtk_app.set_accels_for_action("window.copy", &["<primary>c"]);
        self.gtk_app.set_accels_for_action("window.zoom-in", &["<primary>plus"]);
        self.gtk_app.set_accels_for_action("window.zoom-out", &["<primary>minus"]);
        self.gtk_app.set_accels_for_action("win.show-help-overlay", &["<primary>question"]);
    }

    fn setup_css(&self) {
        if let Some(theme) = gtk::IconTheme::get_default() {
            theme.add_resource_path("/com/belmoussaoui/Obfuscate/icons");
        }
        let p = gtk::CssProvider::new();
        gtk::CssProvider::load_from_resource(&p, "/com/belmoussaoui/Obfuscate/style.css");
        if let Some(screen) = gdk::Screen::get_default() {
            gtk::StyleContext::add_provider_for_screen(&screen, &p, 500);
        }
    }

    fn open_file(&self) {
        let open_dialog = gtk::FileChooserNative::new(Some("Open File"), Some(&self.get_window().widget), gtk::FileChooserAction::Open, Some("Open"), Some("Cancel"));
        utils::setup_image_filters(&open_dialog);
        if open_dialog.run() == gtk::ResponseType::Accept {
            let file = open_dialog.get_file().unwrap();
            send!(self.sender, Action::LoadFile(file));
        };
        open_dialog.destroy();
    }

    fn load_file(&self, file: gio::File) {
        send!(self.sender, Action::ViewEmpty);
        if self.get_window().set_open_file(file).is_ok() {
            send!(self.sender, Action::ViewPhoto);
        }
    }

    fn do_action(&self, action: Action) -> glib::Continue {
        match action {
            Action::OpenFile => self.open_file(),
            Action::LoadFile(file) => self.load_file(file),
            Action::ViewEmpty => self.get_window().set_view(View::Empty),
            Action::ViewPhoto => self.get_window().set_view(View::Photo),
            Action::ResizeWindow(width, height) => self.get_window().widget.resize(width, height),
            Action::SetState(state) => self.get_window().set_state(state),
        };

        glib::Continue(true)
    }
}
