use std::cell::RefCell;
use std::fs;

use std::io::ErrorKind;
use std::path::Path;
use std::rc::Rc;

use crate::card::Card;
use crate::card_gobject::CardGObject;
use crate::db::*;
use crate::ui::cards::new::*;
use crate::widgets::card_entry::CardEntry;
use glib::subclass::InitializingObject;
use gtk::gio::ListStore;
use gtk::glib::object::ObjectExt;
use gtk::glib::{clone, closure_local, Object};
use gtk::prelude::WidgetExt;
use gtk::prelude::*;
use gtk::subclass::prelude::*;
use gtk::{
    gio, glib, Button, CompositeTemplate, FileChooserAction, ListView, NoSelection, ResponseType,
    ScrolledWindow, SearchEntry, Switch, Window,
};
use gtk::{ListItem, SignalListItemFactory};
use rusqlite::Connection;
use sha256::try_digest;

#[derive(CompositeTemplate, Default)]
#[template(resource = "/org/foxarmy/learn-hieroglyph/cards/edit/ui.xml")]
pub struct MemoryCardsEditScene {
    #[template_child]
    pub search_entry: TemplateChild<SearchEntry>,
    #[template_child]
    pub add_button: TemplateChild<Button>,
    #[template_child]
    pub cards_container: TemplateChild<ListView>,
    #[template_child]
    pub cards_scrolled_window: TemplateChild<ScrolledWindow>,
    #[template_child]
    pub back_button: TemplateChild<Button>,

    displaying_cards: RefCell<Vec<CardGObject>>,
}

#[glib::object_subclass]
impl ObjectSubclass for MemoryCardsEditScene {
    const NAME: &'static str = "MemoryCardsEditScene";
    type Type = super::MemoryCardsEditScene;
    type ParentType = gtk::ApplicationWindow;

    fn class_init(klass: &mut Self::Class) {
        klass.bind_template();
    }

    fn instance_init(obj: &InitializingObject<Self>) {
        obj.init_template();
    }
}

impl ObjectImpl for MemoryCardsEditScene {
    fn constructed(&self) {
        self.parent_constructed();
        let binding = self.obj();

        self.add_button.connect_closure("clicked",
                                        false,
                                        closure_local!(@strong binding => move |_b: &Button| {
                                        let new_win = Rc::new(MemoryCardsNewScene::new(&binding.application().unwrap()));
                                        new_win.get_file_choose_button().connect_clicked(clone!(@strong new_win => move |b: &Button| {
                                            b.set_visible(false);
                                            gtk::glib::MainContext::default().spawn_local(new_card_setup(Rc::clone(&new_win)));
                                        }));
                                        new_win.get_done_button().connect_closure("clicked", true, closure_local!(@strong binding => move |_w: &Button| {
                                            binding.imp().query_cards(None);
                                            binding.imp().build_factory();
                                        }));
                                        new_win.present();
        }));

        self.search_entry.connect_closure("activate", false, closure_local!(@strong binding => move |e: &SearchEntry| {
            binding.imp().query_cards(
                match e.text().as_str() {
                    "" => None,
                    _ => {
                        let search_option = e.text().to_string();
                        let conditions = format!("hieroglyph LIKE '%{}%' OR reading LIKE '%{}%' OR translation LIKE '%{}%'", search_option, search_option, search_option);
                        Some(conditions)
                    }
                }
            );
            // binding.imp().update_state();
        }));
        self.query_cards(None);
        self.build_factory();
    }
}

impl MemoryCardsEditScene {
    pub fn build_factory(&self) {
        let model = ListStore::new::<CardGObject>();
        model.extend_from_slice(&self.displaying_cards.borrow());

        let factory = SignalListItemFactory::new();
        factory.connect_setup(move |_, list_item| {
            let card_entry = CardEntry::new(None);
            list_item
                .downcast_ref::<ListItem>()
                .unwrap()
                .set_child(Some(&card_entry));
        });
        let self_binding = self.obj();

        //setting up every card's buttons behavior: editing, switch flipping and deleting.
        factory.connect_closure("bind", false, closure_local!(@strong self_binding => move |_f: &SignalListItemFactory, list_item: &Object| {
            let card_gobject = &list_item
                .downcast_ref::<ListItem>()
                .unwrap()
                .item()
                .and_downcast::<CardGObject>()
                .unwrap();

            let card_object_to_display = list_item
                .downcast_ref::<ListItem>()
                .unwrap()
                .child()
                .and_downcast::<CardEntry>()
                .unwrap();
            card_object_to_display.get_edit_button_widget().connect_closure("clicked", false, closure_local!(@strong self_binding as binding, @strong card_gobject as card => move |_b: &Button| {
                let new_win = Rc::new(MemoryCardsNewScene::new(&binding.application().unwrap()));

                //setting corresponding properties
                new_win.get_picture_widget().set_file(Some(&gio::File::for_path(card.imagepath())));
                new_win.get_hieroglyph_entry().set_text(&card.hieroglyph());
                new_win.get_reading_entry().set_text(&card.reading());
                new_win.get_translation_entry().set_text(&card.translation());

                new_win.get_file_choose_button().connect_clicked(clone!(@strong new_win, @strong card => move |b: &Button| {
                    b.set_visible(false);
                    gtk::glib::MainContext::default().spawn_local(new_card_setup(Rc::clone(&new_win)));
                }));
                new_win.get_done_button().connect_closure("clicked", true, closure_local!(@strong new_win as w, @strong card => move |_w: &Button| {
                    let conn = Connection::open(get_db_path()).unwrap();

                    let hieroglyph = w.get_hieroglyph_input();
                    let reading = w.get_reading_input();
                    let translation = w.get_translation_input();
                    let imagepath = w.get_picture_widget().file();
                    conn.execute("INSERT OR REPLACE INTO cards ( id,
                        hieroglyph,
                        reading,
                        translation,
                        is_learning,
                        imagename
                    ) VALUES (
                        (SELECT id FROM cards WHERE hieroglyph = ?1),
                        ?1,
                        ?2,
                        ?3,
                        (SELECT is_learning FROM cards WHERE hieroglyph = ?1),
                        (SELECT imagename FROM cards WHERE hieroglyph = ?1)
                    )", (&hieroglyph, &reading, &translation)).unwrap();
                    let imagepath = imagepath.unwrap().path().unwrap();
                    let imagepath = imagepath.to_str();

                    card.set_imagepath(imagepath.unwrap());
                    card.set_hieroglyph(hieroglyph);
                    card.set_reading(reading);
                    card.set_translation(translation);

                    w.close();
                }));
                new_win.present();
            }));

            card_object_to_display.get_is_learning_switch_widget().connect_closure("state-set", false, closure_local!(@strong card_gobject as card => move |_s: &Switch, is_enabled: bool| {
                let connection = Connection::open(get_db_path()).unwrap();
                let is_learning = is_enabled;
                let hieroglyph = card.hieroglyph();
                connection.execute("UPDATE cards SET is_learning = ?1 WHERE hieroglyph = ?2", (&is_learning, &hieroglyph)).unwrap();
                glib::Propagation::Proceed
            }));

            card_object_to_display.get_delete_button_widget().connect_closure("clicked", false, closure_local!(@strong card_gobject as card, @strong self_binding => move |_b: &Button| {
                let connection = Connection::open(get_db_path()).unwrap();
                let imagepath = &card.imagepath();
                let imagename = &Path::new(&imagepath).file_name().unwrap().to_str().unwrap();
                match fs::remove_file(get_images_store_path() + "/" + &imagename) {
                    Ok(()) => (),
                    Err(_) => ()
                };
                connection.execute("DELETE FROM cards WHERE hieroglyph = ?1", [&card.hieroglyph()]).unwrap();
                let position = self_binding.imp().displaying_cards.borrow().iter().position(|c| *c == card).unwrap();
                self_binding.imp().displaying_cards.borrow_mut().remove(position);
                // self_binding.imp().update_state();
            }));

            card_gobject.bind_property("imagepath", card_object_to_display.get_image_widget(), "file").sync_create().build();
            card_gobject.bind_property("hieroglyph", &card_object_to_display, "hieroglyph").sync_create().build();
            card_gobject.bind_property("reading", &card_object_to_display, "reading").sync_create().build();
            card_gobject.bind_property("translation", &card_object_to_display, "translation").sync_create().build();
            card_gobject.bind_property("islearning", &card_object_to_display, "islearning").sync_create().build();
        }));

        let no_selection_model = NoSelection::new(Some(model));
        let list_view = ListView::new(Some(no_selection_model), Some(factory));
        self.cards_scrolled_window.set_child(Some(&list_view));
    }

    pub fn query_cards(&self, options: Option<String>) {
        let conn = Connection::open(get_db_path()).unwrap();

        let selector = match options {
            Some(s) => "WHERE ".to_owned() + &s,
            None => "".to_owned(),
        };

        let sql = format!(
            "SELECT imagename, hieroglyph, reading, translation, is_learning FROM cards {selector}"
        );

        let mut stmt = conn.prepare(sql.as_str()).unwrap();

        let cards_iter = stmt
            .query_map([], |row| {
                let c = Card::new(
                    Some(get_images_store_path() + &row.get(0).unwrap_or(String::from(""))),
                    Some(row.get(1).unwrap_or(String::from(""))),
                    Some(row.get(2).unwrap_or(String::from(""))),
                    Some(row.get(3).unwrap_or(String::from(""))),
                    Some(row.get(4).unwrap_or(false)),
                );
                let entry = CardGObject::new(Some(c));
                Ok(entry)
            })
            .unwrap();
        self.displaying_cards.borrow_mut().clear();
        for c in cards_iter {
            self.displaying_cards.borrow_mut().push(c.unwrap());
        }
    }
}

async fn new_card_setup<W: IsA<gtk::Window>>(window: Rc<W>) {
    let w: &MemoryCardsNewScene = Into::<&Window>::into(window.upcast_ref())
        .downcast_ref()
        .unwrap(); // Weird casting from &Window as passed in func to &MemoryCardsNewScene

    let picture_widget = w.get_picture_widget();
    let dialog = gtk::FileChooserDialog::new(
        Some("Open File"),
        Some(Into::<&Window>::into(w.upcast_ref())),
        FileChooserAction::Open,
        &[
            ("_Cancel", ResponseType::Cancel),
            ("_Open", ResponseType::Accept),
        ],
    );

    let path = RefCell::new(String::from(""));
    dialog.run_async(
        clone!( @strong path, @strong w, @strong picture_widget => move |a, _b| {
            let c = a.file().unwrap()
                     .path().unwrap()
                     .as_os_str().to_owned()
                     .to_str().unwrap()
                     .to_string(); // 0_0
            *path.borrow_mut() = c;
            a.close();

            picture_widget.set_file(Some(&gio::File::for_path(Path::new(path.borrow().as_str()))));

            let images_store_path = get_program_home_path() + "/images";

            match fs::create_dir_all(&images_store_path) {
                Ok(_) => {}
                Err(error) => match error.kind() {
                    ErrorKind::AlreadyExists => {}
                    _ => panic!("Could not create directory for storing images!"),
                },
            };

            w.get_done_button().connect_closure(
                "clicked",
                false,
                closure_local!(@strong w, @strong path => move |_b: &Button| {
                    let p = path.borrow();
                    let p = p.as_str();
                    let hash = try_digest(path.borrow().as_str()).unwrap();
                    let extenstion = Path::new(p).extension().unwrap().to_str().unwrap();
                    let new_filename: String = hash.as_str().to_owned() + "." + extenstion;
                    let stored_image_path = Path::new(&images_store_path).join(&new_filename);
                    fs::copy(&p, stored_image_path).expect("Error copying image to store");

                    let hieroglyph = w.get_hieroglyph_input();
                    let reading = w.get_reading_input();
                    let translation = w.get_translation_input();
                    let conn = Connection::open(get_db_path()).unwrap();

                    conn.execute("INSERT OR REPLACE INTO cards ( id,
                        imagename,
                        hieroglyph,
                        reading,
                        translation,
                        is_learning
                    ) VALUES (
                        (SELECT id FROM cards WHERE hieroglyph = ?2),
                        ?1,
                        ?2,
                        ?3,
                        ?4,
                        (SELECT is_learning FROM cards WHERE hieroglyph = ?2 OR TRUE)
                    )", (&new_filename, &hieroglyph, &reading, &translation)).unwrap();
                    w.close();
                }),
            );

        }),
    );
}

impl WidgetImpl for MemoryCardsEditScene {}

impl WindowImpl for MemoryCardsEditScene {}

impl ApplicationWindowImpl for MemoryCardsEditScene {}
