root/src/main.rs

use mpd::*;
mod config;
mod controls;
mod data;
mod filter_page;
mod pmpdc_config;
mod queue_page;

// use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use anyhow::Result;
use lexical_sort::{natural_lexical_cmp, StringSort};

extern crate gtk;
// extern crate gio;

use self::config::{GETTEXT_PACKAGE, LOCALEDIR, RESOURCES_FILE};
use crate::controls::Controls;
use crate::data::{make_time_string, track_subtitle, track_title, Action, ListPage, ListPageItem};
use crate::filter_page::FilterPage;
use crate::queue_page::QueuePage;

use adw::prelude::*;
use gettextrs::{gettext, LocaleCategory};

use glib::clone;
use gtk::gio;
use gtk::glib;
use gtk::ApplicationWindow;
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc; // mpris
use url::Url;

use mpris_player::MprisPlayer;

const MPD_VERSION: f64 = 0.16;

struct ConfigPage {
    root: gtk::Box,
    wip_config: pmpdc_config::ConfigFile,
    server_inputs_page: ServerInputsPage,
    server_list: gtk::ListBox,
}
#[derive(Clone)]
struct ServerInputsPage {
    root: gtk::Box,
    address: gtk::Entry,
    password: gtk::Entry,
    name: gtk::Entry,

    active: Option<usize>,
    ignore_change: i32, // TODO ugly WORKAROUND because i got no idea how to set text without emitting changed signal
}
impl ConfigPage {
    fn add_server(
        &self,
        server: &pmpdc_config::Server,
        sender: &glib::Sender<Action>,
        index: usize,
    ) {
        let server_item = ConfigPage::make_server_item(server, sender, index);

        self.server_list.append(&server_item);
    }

    fn make_server_item(
        server: &pmpdc_config::Server,
        sender: &glib::Sender<Action>,
        index: usize,
    ) -> gtk::Box {
        let server_item = gtk::Box::new(gtk::Orientation::Horizontal, 10);
        server_item.append(&gtk::Label::new(Some(&server.address)));
        server_item.append(&gtk::Label::new(Some(&server.password)));
        server_item.append(&gtk::Label::new(Some(
            &server.name.as_ref().unwrap_or(&"".to_string()),
        )));

        let remove_button = gtk::Button::new();
        remove_button.set_child(Some(&gtk::Image::from_icon_name("list-remove-symbolic")));
        remove_button.connect_clicked(clone!(@strong sender => move |_| {
            sender.send(Action::ConfigRemoveServer {index}).unwrap();
        }));
        server_item.append(&remove_button);

        server_item
    }

    fn update_server(
        &self,
        index: i32,
        server: &pmpdc_config::Server,
        sender: &glib::Sender<Action>,
    ) {
        println!("update i {:#?}", server);
        if let Some(row) = self.server_list.row_at_index(index) {
            let server_item = ConfigPage::make_server_item(server, sender, index as usize);
            self.server_list.insert(&server_item, index);
            self.server_list.remove(&row);
        }
    }
    fn remove_server(&self, sender: &glib::Sender<Action>) {
        while let Some(c) = self.server_list.last_child() {
            self.server_list.remove(&c);
        }
        self.init_servers(sender);
    }
    fn init_servers(&self, sender: &glib::Sender<Action>) {
        let servers = self.wip_config.get_servers();
        let mut i = 0;
        if !servers.is_empty() {
            for server in servers {
                self.add_server(&server, &sender.clone(), i);
                i += 1;
            }
        } else {
            self.server_list
                .append(&gtk::Label::new(Some("No Servers added yet")));
        }
    }
}

impl ServerInputsPage {
    fn set_active(&mut self, index: Option<usize>, server: &pmpdc_config::Server) {
        self.ignore_change = 3;
        self.active = index;
        self.address.set_text(&server.address);
        self.password.set_text(&server.password);
        self.name
            .set_text(&server.name.as_ref().unwrap_or(&"".to_string()));
    }
}

// #[derive(Debug)]
struct State {
    sender: glib::Sender<Action>,
    pool: Rc<glib::ThreadPool>,
    status: Option<Status>,
    client: Option<Client>,
    idle_client: Option<Client>,
    mpris: Option<Arc<MprisPlayer>>,
    // ui
    stack: gtk::Stack,
    controls: Option<Controls>,
    queue_page: Option<QueuePage>,
    filter_page: FilterPage,
    config_page: Option<ConfigPage>,
    // data
    tracks: Vec<Track>,
    initialized: bool,
    config: Option<pmpdc_config::ConfigFile>,
    window: gtk::ApplicationWindow,
    app: adw::Application,
}

impl ListPageItem {
    fn find(&self, text: &str) -> Option<usize> {
        match self {
            // TODO get rid of ascii_lowercase
            crate::ListPageItem::ListPageString { string } => {
                string.to_ascii_lowercase().find(text)
            }
            crate::ListPageItem::ListPageTrack { track } => {
                track.file.to_ascii_lowercase().find(text)
            }
        }
    }
}

impl ListPage {
    fn create_location_bar(&self, sender: &glib::Sender<Action>) -> gtk::Box {
        let root = gtk::Box::new(gtk::Orientation::Horizontal, 10);
        let mut i = 0;
        let top_levels = self.top_levels.clone();
        for (label, tag) in &self.top_levels {
            let level_tag = tag.clone();
            let level_label = label.clone();
            let button = gtk::Button::new();
            let mut top_levels2 = top_levels.clone();
            let previous_top_levels: Vec<(String, Tag)> = top_levels2.drain(..i).collect();
            let mut sub_pages: Vec<Tag> = vec![]; //top_levels2.drain(i..).map(|(_,t)| t).collect();
            sub_pages.push(self.tag.clone());
            sub_pages.append(&mut self.sub_pages.clone());
            button.set_child(Some(&gtk::Label::new(Some(&label))));
            button.connect_clicked(clone!(@strong sender => move |_| {
                let cmd = Action::FindListPage {
                    page: ListPage {
                        tag: level_tag.clone(),
                        value: level_label.clone(),
                        top_levels: previous_top_levels.clone(),
                        sub_pages: sub_pages.clone(),
                    }
                };
                sender.send(cmd).unwrap();
            }));
            root.append(&button);
            root.append(&gtk::Label::new(Some(&"/")));
            i += 1;
        }
        if self.value != "" {
            root.append(&gtk::Label::new(Some(&self.value)));
        }
        root
    }

    fn create_items(
        &self,
        sender: &glib::Sender<Action>,
        items: &Vec<&ListPageItem>,
    ) -> gtk::Overlay {
        let overlay = gtk::Overlay::new();
        let root = gtk::Box::new(gtk::Orientation::Vertical, 0);
        let location_bar = self.create_location_bar(&sender);
        let scroll = gtk::ScrolledWindow::new();
        let items_box = gtk::Box::new(gtk::Orientation::Vertical, 0);

        let all_files: Vec<String> = items
            .iter()
            .map(|i| match &i {
                ListPageItem::ListPageString { string } => string.to_owned(),
                ListPageItem::ListPageTrack { track } => track.file.clone(),
            })
            .collect();

        let _is: Vec<gtk::Box> = items
            .iter()
            .map(|i| {
                let item = gtk::Box::new(gtk::Orientation::Vertical, 10);
                item.append(&gtk::Separator::new(gtk::Orientation::Horizontal));
                let button = gtk::Button::new();
                button.style_context().add_class("flat");
                let label = match &i {
                    ListPageItem::ListPageString { string } => gtk::Label::new(Some(&string)),
                    ListPageItem::ListPageTrack { track } => {
                        gtk::Label::new(Some(&track.title.as_ref().unwrap_or(&track.file)))
                    }
                };
                let file = match &i {
                    ListPageItem::ListPageString { string } => string.to_owned(),
                    ListPageItem::ListPageTrack { track } => track.file.clone(),
                };

                label.set_ellipsize(gtk::pango::EllipsizeMode::End);
                button.set_child(Some(&label));
                item.append(&button);
                items_box.append(&item);
                let sub_page = self.sub_pages.first().map(|t| t.clone());
                let sub_pages: Vec<mpd::Tag> = self.sub_pages.clone().drain(1..).collect();
                let mut top_levels = self.top_levels.clone();
                let is_top_level = self.value == "";
                if !is_top_level {
                    top_levels.push((self.value.clone(), self.tag.clone()));
                }
                button.connect_clicked(clone!(@strong sender => move |_| {
                    match &sub_page {
                        None |  // TODO should never happen?
                        Some(mpd::Tag::Track) => {
                            let cmd = Action::AddFilesToQueue {files:vec![file.clone()]};
                            sender.send(cmd).unwrap();
                        }
                        Some(p) => {
                            let cmd = Action::FindListPage {
                                page: ListPage {
                                    tag: p.clone(),
                                    value: file.clone(),
                                    top_levels: top_levels.clone(),
                                    sub_pages: sub_pages.clone(),
                                },
                            };
                            sender.send(cmd).unwrap();
                        }
                    }
                }));
                item
            })
            .collect();
        items_box.set_margin_start(30);
        items_box.set_margin_end(30);
        items_box.show();
        scroll.set_child(Some(&items_box));
        scroll.set_policy(gtk::PolicyType::Never, gtk::PolicyType::Automatic);
        scroll.set_vexpand(true);
        // root.append(&location_bar, false, true, 0);
        // root.append(&scroll, true, true, 1);
        root.append(&location_bar);
        root.append(&scroll);
        overlay.set_child(Some(&root));
        if self.tag == mpd::Tag::Album {
            let play_all_button = gtk::Button::new();
            play_all_button.set_child(Some(&gtk::Image::from_icon_name(
                "media-playback-start-symbolic",
            )));
            play_all_button.connect_clicked(clone!(@strong sender => move |_| {
                // TODO use addMultiple command
                let cmd = Action::AddFilesToQueue {files:all_files.clone()};
                sender.send(cmd).unwrap();
            }));
            play_all_button.style_context().add_class("play_all_button");
            play_all_button.set_halign(gtk::Align::End);
            play_all_button.set_valign(gtk::Align::End);
            play_all_button.set_margin_bottom(20);
            play_all_button.set_margin_end(20);
            play_all_button.set_can_focus(false); // ugly focus border
            overlay.add_overlay(&play_all_button);
        }
        overlay
    }
}

fn make_server_config_page(state: &State) -> ServerInputsPage {
    let root = gtk::Box::new(gtk::Orientation::Vertical, 10);
    let address = gtk::Entry::new();
    let password = gtk::Entry::new();
    let name = gtk::Entry::new();
    address.set_placeholder_text(Some(&"MPD Server ip:port (e.g.: 192.168.1.5:6600)"));
    password.set_placeholder_text(Some(&"Password"));
    password.set_visibility(false);
    let index;
    match &state.config.as_ref().and_then(|c| c.active_server()) {
        Some(server) => {
            address.set_text(&server.address);
            password.set_text(&server.password);
            name.set_text(&server.name.as_ref().unwrap_or(&"".to_string()));
            index = Some(0);
        }
        _ => {
            index = None;
        }
    }
    name.set_placeholder_text(Some("Name (e.g.: Livingroom Server)"));

    root.append(&gtk::Label::new(Some(&"Config")));
    root.append(&address);
    root.append(&password);
    root.append(&name);
    ServerInputsPage {
        address: address,
        name: name,
        password: password,
        root: root,
        active: index,
        ignore_change: 0,
    }
}

fn make_config_page(state: &State) -> ConfigPage {
    let root = gtk::Box::new(gtk::Orientation::Vertical, 10);
    let sender = &state.sender;
    let save_button = gtk::Button::new();

    save_button.set_child(Some(&gtk::Label::new(Some(&"Save"))));

    let config = state
        .config
        .clone()
        .unwrap_or(pmpdc_config::ConfigFile::new());
    let server_inputs_page = make_server_config_page(state);

    let server_list = gtk::ListBox::new();
    server_list.connect_row_activated(clone!(@strong sender => move |list,row| {
        let mut index = 0;
        for c in list.observe_children().iter::<gtk::ListBoxRow>() {
            if let Ok(c) = c {
                if row == &c {
                    sender.send(Action::ConfigSetActiveServer {index}).unwrap();
                }
            }
            index += 1;
        }
    }));

    let add_button = gtk::Button::new();
    add_button.set_child(Some(&gtk::Label::new(Some(&"Add Server"))));
    add_button.connect_clicked(clone!(@strong sender => move |_| {
        sender.send(Action::ConfigAddServer).unwrap();
    }));

    let center = gtk::Box::new(gtk::Orientation::Vertical, 10);

    server_inputs_page
        .address
        .connect_changed(clone!(@strong sender => move |_| {
            sender.send(Action::ConfigUpdateServerInput).unwrap();
        }));
    server_inputs_page
        .password
        .connect_changed(clone!(@strong sender => move |_| {
            sender.send(Action::ConfigUpdateServerInput).unwrap();
        }));
    server_inputs_page
        .name
        .connect_changed(clone!(@strong sender => move |_| {
            sender.send(Action::ConfigUpdateServerInput).unwrap();
        }));

    center.append(&server_inputs_page.root);

    save_button.connect_clicked(clone!(@strong sender  => move |_| {
        let cmd = Action::SaveWipConfig;
        sender.send(cmd).unwrap();
        let cmd = Action::InitClient;
        sender.send(cmd).unwrap();
    }));

    center.append(&gtk::Label::new(Some(&"Servers:")));
    center.append(&server_list);
    center.append(&add_button);
    root.set_margin_start(30);
    root.set_margin_end(30);
    root.set_margin_bottom(30);
    root.set_margin_top(30);
    root.append(&center);
    root.append(&save_button);
    let page = ConfigPage {
        root: root,
        wip_config: config.clone(),
        server_inputs_page: server_inputs_page,
        server_list: server_list,
    };
    page.init_servers(&sender.clone());
    page
}

fn make_time_label(time: u16) -> gtk::Label {
    gtk::Label::new(Some(&make_time_string(time)))
}

fn make_track_item(
    sender: &glib::Sender<Action>,
    i: usize,
    track: &Track,
    is_active: bool,
) -> gtk::Button {
    let title_label = gtk::Label::new(Some(&format!("{} - {}", i + 1, track_title(track))));
    title_label.style_context().add_class("title_label");
    title_label.set_ellipsize(gtk::pango::EllipsizeMode::End);
    let artist_album_label = gtk::Label::new(Some(&track_subtitle(track)));
    artist_album_label.set_ellipsize(gtk::pango::EllipsizeMode::End);
    let time_label = make_time_label(track.time);
    let item_box = gtk::Box::new(gtk::Orientation::Vertical, 10);
    let second_row = gtk::Box::new(gtk::Orientation::Horizontal, 3);
    second_row.set_hexpand(true);
    artist_album_label.set_hexpand(true);
    second_row.append(&artist_album_label);
    second_row.append(&time_label);
    item_box.append(&gtk::Separator::new(gtk::Orientation::Horizontal));
    item_box.append(&title_label);
    item_box.append(&second_row);
    item_box.set_margin_start(10);
    item_box.set_margin_end(10);
    item_box.set_margin_bottom(10);
    let button = gtk::Button::new();
    button.set_margin_start(30);
    button.set_margin_end(30);
    button.connect_clicked(clone!(@strong sender => move |_| {
        let cmd = Action::Play {index: i};
        sender.send(cmd).unwrap();
    }));
    //         eventbox.connect("button-press-event", self.on_button_press_event)
    let artist = track.artist.as_ref().map(|a| a.clone());
    let album = track.album.as_ref().map(|a| a.clone());
    let popover = gtk::Popover::new();

    button.connect_destroy(clone!(@weak popover => move |_| {
        popover.unparent()
    }));

    // popover.set_child(Some(&button));
    popover.set_parent(&button);
    let on_rightclick = clone!(@weak button, @weak popover, @strong sender => move |(x,y)| {
        let popover_box = gtk::Box::new(gtk::Orientation::Vertical, 0);
        let remove_track_button = gtk::Button::new();
        remove_track_button.set_child(Some(&gtk::Label::new(Some(&"remove"))));
        remove_track_button.connect_clicked(clone!(@weak button, @weak popover, @strong sender => move |_| {
            popover.connect_closed(clone!(@weak popover, @strong sender => move |_| {
                let cmd = Action::RemoveTrack {index: i};
                sender.send(cmd).unwrap();
            }));
            popover.popdown();
        }));

        let queue_next_button = gtk::Button::new();
        queue_next_button.set_child(Some(&gtk::Label::new(Some(&"Play Next"))));
        queue_next_button.connect_clicked(clone!(@strong sender => move |_| {
            let cmd = Action::QueueNext {index: i};
            sender.send(cmd).unwrap();
        }));

        match &artist {
            Some(artist) => {
                let artist2 = artist.clone();
                let list_artist_button = gtk::Button::new();
                list_artist_button.set_child(Some(&gtk::Label::new(Some(&"List artist"))));
                list_artist_button.connect_clicked(clone!(@strong sender, @weak popover => move |_| {
                    let cmd = Action::FindListPage {
                        page:ListPage {
                            tag: mpd::Tag::Artist,
                            value: artist2.clone(),
                            top_levels: vec![],
                            sub_pages: vec![mpd::Tag::Album,mpd::Tag::Track],
                        }
                    };
                    sender.send(cmd).unwrap();
                    popover.popdown();
                }));
                popover_box.append(&list_artist_button);
            }
            _ => ()
        }
        match &album {
            Some(album) => {
                let top_levels = artist.as_ref().map_or(vec![], |a| vec![(a.clone(), mpd::Tag::Artist)]);
                let album2 = album.clone();
                let list_album_button = gtk::Button::new();
                list_album_button.set_child(Some(&gtk::Label::new(Some(&"List album"))));
                list_album_button.connect_clicked(clone!(@strong sender, @weak popover => move |_| {
                    let cmd = Action::FindListPage {
                        page:ListPage {
                            tag: mpd::Tag::Album,
                            value: album2.clone(),
                            top_levels: top_levels.clone(),
                            sub_pages: vec![mpd::Tag::Track],
                        }
                    };
                    sender.send(cmd).unwrap();
                    popover.popdown();
                }));
                popover_box.append(&list_album_button);
            }
            _ => ()
        }

        popover_box.append(&queue_next_button);
        popover_box.append(&remove_track_button);
        popover.set_child(Some(&popover_box));
        popover.popup();
        popover.set_pointing_to(Some(&gtk::gdk::Rectangle::new(x as i32,y as i32, 1, 1)));
    });

    let on_rightclick2 = on_rightclick.clone();
    let long_press = gtk::GestureLongPress::new();
    long_press.connect_pressed(move |_, x, y| {
        on_rightclick((x, y));
    });
    let right_click = gtk::GestureClick::builder().button(3).build();

    right_click.connect_pressed(move |_, _, x, y| {
        on_rightclick2((x, y));
    });
    button.add_controller(long_press);
    button.add_controller(right_click);
    // println!("{:#?}", button.parent());
    if is_active {
        button.style_context().add_class("track_active");
    }
    button.set_child(Some(&item_box));
    button.style_context().add_class("flat");

    button
}

fn open_menu_popup(state: &State, menu_button: &gtk::Button) {
    let sender = &state.sender;
    let consume_status = state.status.as_ref().map(|s| s.consume);
    let single_status = state.status.as_ref().map(|s| s.single);

    let popover = gtk::Popover::new();
    menu_button.connect_destroy(clone!(@weak popover => move |_| {
        popover.unparent()
    }));
    popover.set_parent(menu_button);

    let popover_box = gtk::Box::new(gtk::Orientation::Vertical, 0);
    let clear_queue_button = gtk::Button::new();
    clear_queue_button.set_child(Some(&gtk::Label::new(Some(&"Clear queue"))));
    clear_queue_button.connect_clicked(clone!(@strong sender, @weak popover => move |_| {
        let cmd = Action::ClearQueue;
        sender.send(cmd).unwrap();
        popover.popdown();
    }));

    let refresh_button = gtk::Button::new();
    refresh_button.set_child(Some(&gtk::Label::new(Some(&"Refresh"))));
    refresh_button.connect_clicked(clone!(@strong sender, @weak popover => move |_| {
        let cmd = Action::InitClient;
        sender.send(cmd).unwrap();
        popover.popdown();
    }));

    // let toggle_single_mode_button = gtk::Button::new();
    // let single_label = match single_status {
    //     Some(mpd::SingleMode::On) => "[✓] Single Mode",
    //     Some(mpd::SingleMode::Off) | Some(mpd::SingleMode::Oneshot) | _ => "[ ] Single Mode",
    // };

    let toggle_single_mode_switch = gtk::Switch::builder()
        .state(Some(mpd::SingleMode::On) == single_status)
        .vexpand(false)
        .build();
    // toggle_single_mode_button.set_child(Some(&gtk::Label::new(Some(&single_label))));
    toggle_single_mode_switch.connect_state_set(
        clone!(@strong sender, @weak popover => @default-return gtk::Inhibit(false), move |_, _| {
            let cmd = Action::ToggleSingleMode;
            sender.send(cmd).unwrap();
            popover.popdown();
            gtk::Inhibit(false)
        }),
    );

    let toggle_single_mode_row = adw::ActionRow::builder()
        .title("Single mode")
        .activatable_widget(&toggle_single_mode_switch)
        .activatable(true)
        .can_focus(false)
        .build();
    toggle_single_mode_row.add_suffix(&toggle_single_mode_switch);

    let consume_label = match consume_status {
        Some(true) => "[✓] Consume Mode",
        _ => "[ ] Consume Mode",
    };
    let toggle_consume_mode_button = gtk::Button::new();
    toggle_consume_mode_button.set_child(Some(&gtk::Label::new(Some(&consume_label))));
    toggle_consume_mode_button.connect_clicked(clone!(@strong sender, @weak popover => move |_| {
        let cmd = Action::ToggleConsumeMode;
        sender.send(cmd).unwrap();
        popover.popdown();
    }));

    let open_config_button = gtk::Button::new();
    open_config_button.set_child(Some(&gtk::Label::new(Some(&"Open config"))));
    open_config_button.connect_clicked(clone!(@strong sender => move |_| {
        let cmd = Action::OpenConfig;
        sender.send(cmd).unwrap();
    }));

    popover.style_context().add_class("menu");
    popover_box.append(&toggle_single_mode_row);
    popover_box.append(&toggle_consume_mode_button);
    popover_box.append(&clear_queue_button);
    popover_box.append(&refresh_button);
    popover_box.append(&open_config_button);
    match &state.config.as_ref().map(|c| (c, c.active_server())) {
        Some((c, Some(server))) => {
            // only add the switcher if we have multiple servers
            if c.server_count() >= 2 {
                let server_switcher_box = make_server_switcher(sender, server);
                popover_box.append(&server_switcher_box);
            }
        }
        _ => (), // No server means no switching ui
    }
    popover.set_child(Some(&popover_box));
    popover.popup();
}

fn make_server_switcher(sender: &glib::Sender<Action>, server: &pmpdc_config::Server) -> gtk::Box {
    let root = gtk::Box::new(gtk::Orientation::Horizontal, 10);
    let previous_button = gtk::Button::new();
    let next_button = gtk::Button::new();
    previous_button.connect_clicked(clone!(@strong sender => move |_| {
        let cmd = Action::SelectServerDelta {delta:-1};
        sender.send(cmd).unwrap();
        let cmd = Action::InitClient;
        sender.send(cmd).unwrap();
    }));
    next_button.connect_clicked(clone!(@strong sender => move |_| {
        let cmd = Action::SelectServerDelta {delta:1};
        sender.send(cmd).unwrap();
        let cmd = Action::InitClient;
        sender.send(cmd).unwrap();
    }));
    previous_button.set_child(Some(&gtk::Image::from_icon_name("go-previous-symbolic")));
    next_button.set_child(Some(&gtk::Image::from_icon_name("go-next-symbolic")));

    let current_label = gtk::Label::new(Some(&server.get_name()));
    root.append(&previous_button);
    root.append(&current_label);
    root.append(&next_button);
    root
}

fn do_action(state: &mut State, action: Action) -> glib::Continue {
    match action {
        Action::Idle => (),
        Action::SecondPassed => (),
        Action::GotClient { client: _ } => println!("got client"),
        Action::GotIdleClient { idle_client: _ } => println!("got idle client"),
        Action::GotCover {
            ref uri,
            texture: _,
        } => println!("got cover for {}", &uri),
        _ => println!("{:#?}", action),
    }
    match action {
        Action::Init => {
            println!("init");
            let config = pmpdc_config::read_config();
            state.config = config;
            match state.config.as_ref() {
                Some(_) => {
                    do_action(state, Action::InitClient);
                }
                None => {
                    do_action(state, Action::OpenConfig);
                }
            }
        }
        Action::InitClient => {
            let sender = &state.sender;
            let (server, password) = state
                .config
                .as_ref()
                .and_then(|c| c.active_server())
                .map_or(("".to_owned(), "".to_owned()), |s| {
                    (s.address.clone(), s.password.clone())
                });

            state
                .pool
                .push(clone!(@strong sender => move || {
                    // println!("connecting to {:#?}", server);
                    match futures_lite::future::block_on(init_client(server,password)) {
                        Ok((client, status, tracks, idle_client)) => {
                            send!(sender, Action::GotQueue {tracks: tracks});
                            send!(sender, Action::GotClient {client: client});
                            send!(sender, Action::GotStatus {status: status});
                            send!(sender, Action::GotIdleClient {idle_client: idle_client});
                            send!(sender, Action::InitMpris);

                            // let cmd4 = Action::List {tag: mpd::Tag::Comment};
                            // sender.send(cmd4).unwrap();
                            // let cmd4 = Action::Search {tag: FindTag::Album, value: "gothic lolita".to_string()};
                            // sender.send(cmd4).unwrap();
                        }
                        Err(e) => println!("init failure {:#?}", e),
                    }
                }))
                .expect("failed push to thread pool (Init)");
        }
        Action::GotClient { client } => {
            state.client = Some(client);
        }
        Action::GotIdleClient { idle_client } => {
            state.idle_client = Some(idle_client);
        }
        Action::Quit => {
            state.window.close();
            state.app.quit();
        }
        Action::Idle => {
            // with idle client here
            let sender = &state.sender;
            let mut c2 = None;
            std::mem::swap(&mut state.idle_client, &mut c2);
            match c2 {
                Some(mut c) => {
                    let _ = state.pool.push(clone!(@strong sender => move || {
                        let result = futures_lite::future::block_on(c.idle());
                        let cmd = Action::GotIdleClient {idle_client: c};
                        sender.send(cmd).unwrap();
                        match result {
                            Ok((new_status, new_queue)) => {
                                if new_queue {
                                    let cmd = Action::UpdateQueue;
                                    sender.send(cmd).unwrap();
                                }
                                if new_status {
                                    let cmd = Action::UpdateStatus;
                                    sender.send(cmd).unwrap();
                                }
                            }
                            e => println!("failed to idle: {:#?}", e)
                        }
                    }));
                }
                None => (),
            }
        }
        Action::SecondPassed => {
            match state.status.as_ref().map(|s| &s.state) {
                Some(Playback::Playing) => state.controls.as_ref().map(|c| c.add_second()),
                _ => None,
            };
        }
        Action::GotStatus { status } => {
            // println!("status coming through {:#?}", status);
            match &state.controls {
                Some(controls) => {
                    // controls.do_action(ControlsAction::SyncWithStatus {status:&status}, state)
                    controls.sync_with_status(Some(&status));
                    let pos = status.song.as_ref().map(|s| s.pos);
                    if pos != controls.last_played_index() {
                        let track = pos.and_then(|pos| state.tracks.get(pos));
                        if let (Some(track), Some(pos)) = (track, pos) {
                            controls.set_track(Some((track, pos)));
                        } else {
                            controls.set_track(None);
                        }
                    }
                }
                None => (),
            }
            match &mut state.queue_page {
                Some(qp) => {
                    let _ = qp.sync_with_status(&status);
                }
                None => (),
            }
            if let Some(mpris) = state.mpris.as_ref() {
                let pos = status.song.as_ref().map_or(0, |s| s.pos);
                let track = &state.tracks.get(pos);
                sync_mpris_with_status(mpris, Some(&status), track);
            }

            {
                // FETCH COVER
                let new_pos = status.song.as_ref().map(|s| s.pos);
                let old_pos = state
                    .status
                    .as_ref()
                    .and_then(|s| s.song.as_ref().map(|s| s.pos));
                if new_pos != old_pos {
                    if let Some(uri) = new_pos
                        .and_then(|np| state.tracks.get(np))
                        .map(|t| t.file.clone())
                    {
                        let uri2 = uri.clone();
                        with_client_and_result_err(
                            state,
                            move |c| futures_lite::future::block_on(c.cover(uri2)),
                            move |sender, bytes| {
                                if let Ok(bytes) = bytes {
                                    // TODO load at max size, once the cover page widget is in?
                                    let strm: gtk::gio::MemoryInputStream =
                                        gtk::gio::MemoryInputStream::from_bytes(
                                            &glib::Bytes::from(&bytes),
                                        );
                                    let cancellable: Option<&gtk::gio::Cancellable> = None;
                                    let pixbuf = gtk::gdk_pixbuf::Pixbuf::from_stream_at_scale(
                                        &strm,
                                        60,
                                        60,
                                        true,
                                        cancellable,
                                    );
                                    if let Ok(pixbuf) = pixbuf {
                                        let texture = gtk::gdk::Texture::for_pixbuf(&pixbuf);
                                        send!(
                                            sender,
                                            Action::GotCover {
                                                uri,
                                                texture: Some(texture)
                                            }
                                        );
                                    } else {
                                        send!(sender, Action::GotCover { uri, texture: None });
                                    }

                                    write_mpris_tmp_cover(&bytes.into());
                                    send!(sender, Action::SetMprisCover);
                                } else {
                                    delete_mpris_cover();
                                    send!(sender, Action::GotCover { uri, texture: None });
                                }
                            },
                        );
                    }
                }
            }

            state.status = Some(status);
        }
        Action::GotCover { uri: _, texture } => {
            if let Some(c) = state.controls.as_mut() {
                let _ = c.set_cover(texture);
            }
        }
        Action::SetMprisCover => {
            if let (Some(mpris), Some(status)) = (state.mpris.as_ref(), state.status.as_ref()) {
                let pos = status.song.as_ref().map_or(0, |s| s.pos);
                let track = &state.tracks.get(pos);
                sync_mpris_with_status(mpris, Some(&status), track);
            }
        }
        Action::OpenFilterPage => {
            let transition = gtk::StackTransitionType::SlideRight;
            state.stack.set_visible_child_full("filters", transition);
            if !state.filter_page.did_fetch_data_before() {
                send!(
                    state.sender,
                    Action::List {
                        tag: mpd::Tag::Artist
                    }
                )
            }
        }
        Action::OpenQueuePage => {
            state
                .stack
                .set_visible_child_full("queue", gtk::StackTransitionType::SlideLeft);
        }
        Action::InitMpris => {
            println!("mprs init");
            let pos = state
                .status
                .as_ref()
                .and_then(|s| s.song.clone())
                .map_or(0, |s| s.pos);
            let track = &state.tracks.get(pos);
            let mpris = init_mpris(&state.sender, state.status.as_ref(), track);
            state.mpris = Some(mpris);
        }
        // Action::OpenMenuPopup => {
        //     state.queue_page.as_ref().map(|tl| {
        //         open_menu_popup(state, &tl.menu_button);
        //     });
        // }
        Action::SelectServerDelta { delta } => match state.config.as_mut() {
            Some(c) => c.move_active(delta),
            None => (),
        },
        Action::GotQueue { tracks } => {
            // TODO don't recreate controls all the time, only update queue_page
            // println!("the tracks are here {:#?}", tracks);
            let sender = &state.sender;
            let mut i: usize = 0;
            let track_list = gtk::Box::new(gtk::Orientation::Vertical, 0);
            let mut controls = state.controls.clone();
            let active_track = state
                .status
                .as_ref()
                .map_or(None, |s| s.song.as_ref().map(|s| s.pos));
            let mut track_items = Vec::new();
            track_items.reserve(tracks.len());
            for track in &tracks {
                let is_active = Some(i) == active_track;
                let track_item = make_track_item(sender, i, &track, is_active);
                track_list.append(&track_item);
                if is_active {
                    // if let Some(c) = state.controls.as_ref() {
                    //     c.sync_with_status(state.status.as_ref());
                    //     c.set_track(Some((&track, i)));
                    // } else {
                    //     controls = Some(Controls::new(&state.sender, Some((&track, i)), state.status.as_ref()))
                    // }
                    controls = Some(Controls::new(
                        &state.sender,
                        Some((&track, i)),
                        state.status.as_ref(),
                    ))
                }
                i += 1;
                track_items.push(track_item);
            }
            let controls = controls.unwrap_or(Controls::new(&state.sender, None, None));
            let queue_page = QueuePage::new(
                &state.sender,
                track_list,
                track_items,
                &controls,
                active_track,
            );
            // root.append(&controls.root);

            let show_queue = !state.initialized
                || state
                    .stack
                    .visible_child_name()
                    .map_or(false, |s| s == "queue");
            match state.stack.child_by_name("queue") {
                Some(old) => state.stack.remove(&old),
                _ => (),
            };

            // root.set_widget_name("queue");
            state.stack.add_named(&queue_page, Some("queue"));
            if show_queue {
                state.stack.set_visible_child_name("queue");
                state.initialized = true;
            }

            // track_scroll.set_vadjustment(adjustment); // force adjustment creation
            //                                           // restore scroll position
            // state.queue_page.as_ref().and_then(|_tl| {
            //     // println!("gonna scroll {:#?}", tl.scroll.get_vadjustment());
            //     // let f = tl.scroll.get_vadjustment()?.get_value();
            //     // println!("should scroll to {}", f);
            //     let f = 1888.0;
            //     let adjustment = track_scroll.vadjustment();
            //     adjustment.set_value(f);
            //     // track_scroll.set_vadjustment(Some(&adjustment));
            //     Some(f)
            // });

            state.controls = Some(controls);
            state.queue_page = Some(queue_page);
            state.tracks = tracks;
        }
        Action::SetVolume { volume } => {
            with_client(state, move |c| {
                futures_lite::future::block_on(
                    c.command(format!("setvol {}\n", volume.clamp(0, 100)).as_bytes()),
                )
            });
        }
        Action::RaiseVolume => {
            state
                .status
                .as_ref()
                .map(|s| s.volume + 1)
                .map(|v| do_action(state, Action::SetVolume { volume: v }));
        }
        Action::LowerVolume => {
            state
                .status
                .as_ref()
                .map(|s| s.volume - 1)
                .map(|v| do_action(state, Action::SetVolume { volume: v }));
        }
        Action::QueueNext { index } => {
            let song_id = state
                .status
                .as_ref()
                .and_then(|s| s.song.as_ref().map(|s| s.pos));
            // let queue_page_scroll = state.queue_page.as_ref().and_then(|tl| {
            //     let f = tl.scroll.vadjustment().value();
            //     Some(f)
            // });
            match song_id {
                None => {
                    do_action(state, Action::SetSingleMode { single: false });
                }
                Some(current_index) => {
                    with_client_and_result(
                        state,
                        move |c| {
                            futures_lite::future::block_on(c.command(
                                format!("move {} {}\n", index, current_index + 1).as_bytes(),
                            ))
                        },
                        move |_sender, _| {
                            // match queue_page_scroll {
                            //     Some(scroll) => {
                            //         let cmd2 = Action::ScrollQueue_PageTo {value: scroll};
                            //         sender.send(cmd2).unwrap();
                            //     }
                            //     None => ()
                            // }
                        },
                    );
                }
            };
        }
        Action::Play { index } => {
            with_client(state, move |c| {
                futures_lite::future::block_on(c.play(index))
            });
        }
        Action::RemoveTrack { index } => {
            with_client(state, move |c| {
                futures_lite::future::block_on(c.command(format!("delete {}\n", index).as_bytes()))
            });
        }
        Action::SetPause { pause } => {
            let playing = state.status.as_ref().map(|s| s.state.clone());
            with_client(state, move |c| {
                if pause {
                    futures_lite::future::block_on(c.command(b"pause 1\n"))
                } else {
                    if playing == Some(Playback::Stopped) {
                        futures_lite::future::block_on(c.play(0))
                    } else {
                        futures_lite::future::block_on(c.command(b"pause 0\n"))
                    }
                }
            });
        }
        Action::TogglePause => {
            if let Some(status) = state.status.as_ref() {
                let value = match status.state {
                    Playback::Playing => true,
                    Playback::Paused => false,
                    Playback::Stopped => false,
                };
                do_action(state, Action::SetPause { pause: value });
            }
        }
        Action::SetEndless { endless } => {
            with_client(state, move |c| {
                if endless {
                    futures_lite::future::block_on(c.command(b"repeat 1\n"))
                } else {
                    futures_lite::future::block_on(c.command(b"repeat 0\n"))
                }
            });
        }
        Action::ToggleEndless => {
            if let Some(status) = state.status.as_ref() {
                let value = !status.repeat;
                do_action(state, Action::SetEndless { endless: value });
            }
        }
        Action::SetSingleMode { single } => {
            with_client(state, move |c| {
                if single {
                    futures_lite::future::block_on(c.command(b"single 1\n"))
                } else {
                    futures_lite::future::block_on(c.command(b"single 0\n"))
                }
            });
        }
        Action::ToggleSingleMode => {
            match state.status.as_ref().map(|s| &s.single) {
                Some(mpd::SingleMode::On) => {
                    do_action(state, Action::SetSingleMode { single: false })
                }
                Some(mpd::SingleMode::Off) | Some(mpd::SingleMode::Oneshot) | _ => {
                    do_action(state, Action::SetSingleMode { single: true })
                }
            };
        }
        Action::SetConsumeMode { consume } => {
            with_client(state, move |c| {
                if consume {
                    futures_lite::future::block_on(c.command(b"consume 1\n"))
                } else {
                    futures_lite::future::block_on(c.command(b"consume 0\n"))
                }
            });
        }
        Action::ToggleConsumeMode => {
            match state.status.as_ref().map(|s| &s.consume) {
                Some(true) => do_action(state, Action::SetConsumeMode { consume: false }),
                _ => do_action(state, Action::SetConsumeMode { consume: true }),
            };
        }
        Action::NextTrack => {
            with_client(state, move |c| {
                futures_lite::future::block_on(c.command(b"next\n"))
            });
        }
        Action::PreviousTrack => {
            with_client(state, move |c| {
                futures_lite::future::block_on(c.command(b"previous\n"))
            });
        }
        Action::SetRandomize { randomize } => {
            with_client(state, move |c| {
                if randomize {
                    futures_lite::future::block_on(c.command(b"random 1\n"))
                } else {
                    futures_lite::future::block_on(c.command(b"random 0\n"))
                }
            });
        }
        Action::ToggleRandomize => {
            if let Some(status) = state.status.as_ref() {
                let value = !status.random;
                do_action(state, Action::SetRandomize { randomize: value });
            }
        }
        Action::Seek { position } => {
            let song_id = state
                .status
                .as_ref()
                .and_then(|s| s.song.as_ref().map(|s| s.pos));
            with_client(state, move |c| {
                if MPD_VERSION > 0.16 {
                    futures_lite::future::block_on(
                        c.command(format!("seekcur {}\n", position.round()).as_bytes()),
                    )
                } else {
                    match song_id {
                        Some(sid) => futures_lite::future::block_on(
                            c.command(format!("seek {} {}\n", sid, position.round()).as_bytes()),
                        ),
                        None => Err(anyhow::Error::msg("Cannot seek, no song is playing.")),
                    }
                }
            });
        }
        Action::SeekRelative { offset } => {
            let song_id = state
                .status
                .as_ref()
                .and_then(|s| s.song.as_ref().map(|s| s.pos));
            let current_time = state
                .controls
                .as_ref()
                .map(|c| c.progress_time())
                .unwrap_or(0.0);
            let position = current_time + offset;
            with_client(state, move |c| {
                if MPD_VERSION > 0.16 {
                    let sign = if offset >= 0.0 { '+' } else { '-' };
                    futures_lite::future::block_on(
                        c.command(format!("seekcur {}{}\n", sign, offset.round()).as_bytes()),
                    )
                } else {
                    match song_id {
                        Some(sid) => futures_lite::future::block_on(
                            c.command(format!("seek {} {}\n", sid, position.round()).as_bytes()),
                        ),
                        None => Err(anyhow::Error::msg("Cannot seek, no song is playing.")),
                    }
                }
            });
        }
        Action::List { tag } => {
            with_client_and_result(
                state,
                move |c| {
                    // TODO if > 0.16? allow:
                    // group tag2
                    futures_lite::future::block_on(c.list(&tag))
                },
                move |sender, mut l| {
                    let start = std::time::Instant::now();
                    l.string_sort_unstable(natural_lexical_cmp);
                    let duration = start.elapsed();
                    println!("Time elapsed in expensive_function() is: {:?}", duration);
                    let cmd2 = Action::GotList { tag: tag, items: l };
                    sender.send(cmd2).unwrap();
                },
            );
        }
        Action::FindListPage { page } => {
            let search = page.value.clone();
            let tag = page.tag.clone();
            with_client_and_result(
                state,
                move |c| {
                    futures_lite::future::block_on(c.find(
                        tag,
                        search,
                        &mpd::SearchFields {
                            file: true,
                            title: true,
                            artist: true,
                            album: true,
                        },
                    ))
                },
                move |sender, (tracks, _)| {
                    let cmd = Action::GotListPage {
                        page: page,
                        results: tracks,
                    };
                    sender.send(cmd).unwrap();
                },
            );
        }
        Action::Search { search, tag } => {
            with_client_and_result(
                state,
                move |c| {
                    futures_lite::future::block_on(c.search(
                        tag,
                        search,
                        &SearchFields {
                            file: true,
                            title: true,
                            artist: true,
                            album: true,
                        },
                    ))
                },
                move |sender, (tracks, _)| {
                    let cmd = Action::GotSearch { items: tracks };
                    sender.send(cmd).unwrap();
                },
            );
        }
        Action::GotSearch { mut items } => {
            let sender = &state.sender;
            // println!("{:#?}", items);
            let items: Vec<ListPageItem> = items
                .drain(..)
                .map(|t| ListPageItem::ListPageTrack { track: t })
                .collect();
            let lp = ListPage {
                tag: mpd::Tag::Track,
                value: "".to_owned(),
                top_levels: vec![],
                sub_pages: vec![mpd::Tag::Track],
            };
            let items = lp.create_items(sender, &items.iter().collect());
            state.filter_page.set_search_items(items);
        }
        Action::AddFilesToQueue { files } => {
            let queue_size = state.status.as_ref().map(|s| s.queue_len);
            let playing = state.status.as_ref().map(|s| s.state.clone());
            with_client(state, move |c| {
                let added_any = files.is_empty();
                for file in files {
                    futures_lite::future::block_on(
                        c.command(format!("add \"{}\"\n", file).as_bytes()),
                    )?
                }
                match queue_size {
                    Some(id) => {
                        if playing != Some(Playback::Playing) && !added_any {
                            futures_lite::future::block_on(
                                c.command(format!("play \"{}\"\n", id).as_bytes()),
                            )?
                        };
                    }
                    _ => (),
                }
                Ok(())
            });
        }
        Action::UpdateStatus => {
            with_client_and_result(
                state,
                |c| futures_lite::future::block_on(c.status()),
                |sender, s| {
                    let cmd = Action::GotStatus { status: s };
                    sender.send(cmd).unwrap();
                },
            );
        }
        Action::UpdateQueue => {
            with_client_and_result(
                state,
                |c| {
                    futures_lite::future::block_on(c.queue(
                        0,
                        &SearchFields {
                            file: true,
                            title: true,
                            artist: true,
                            album: true,
                        },
                    ))
                },
                |sender, (tracks, _queue_files)| {
                    let cmd = Action::GotQueue { tracks: tracks };
                    sender.send(cmd).unwrap();
                },
            );
        }
        Action::GotList { tag, items } => {
            state.filter_page.set_list(&state.sender, tag, items);
        }
        // TODO FIXME WTF IS THE DIFFERENCE TO GotList? GET BETTER NAMES
        Action::GotListPage { page, results } => {
            state
                .filter_page
                .set_list_page(&state.sender, page, results);
            let transition_r = gtk::StackTransitionType::SlideRight;
            state.stack.set_visible_child_full("filters", transition_r);
        }
        Action::ClearQueue => {
            with_client(state, move |c| {
                futures_lite::future::block_on(c.command(b"clear\n"))
            });
        }
        Action::ScrollTracklistTo { value } => {
            // state.tracklist.as_ref().and_then(|tl| {
            //     let adjustment = tl.scroll.vadjustment();
            //     adjustment.set_value(value);
            //     Some(value)
            // });
        }
        Action::OpenConfig => {
            state
                .stack
                .child_by_name("config")
                .map(|s| state.stack.remove(&s));
            let config_page = make_config_page(&state);
            state.stack.add_named(&config_page.root, Some("config"));
            state.stack.set_visible_child_name("config");
            state.config_page = Some(config_page);
        }
        Action::ConfigRemoveServer { index } => {
            if let Some(config_page) = &mut state.config_page {
                let mut config = config_page.wip_config.clone();
                config.remove_server(index);
                let first_server = config.get_servers().first().map(|c| c.clone());
                config_page.wip_config = config;
                config_page.remove_server(&state.sender.clone());
                if let Some(server) = first_server {
                    config_page.server_inputs_page.set_active(Some(0), &server);
                } else {
                    config_page
                        .server_inputs_page
                        .set_active(None, &pmpdc_config::Server::empty());
                }
            }
        }
        Action::ConfigAddServer => {
            if let Some(config_page) = &mut state.config_page {
                let mut config = config_page.wip_config.clone();
                // let mut server_inputs_page = &config_page.server_inputs_page;
                let server = pmpdc_config::Server::empty();
                let index = config.server_count();
                config.add_server(server.clone());
                config_page.add_server(&server, &state.sender.clone(), index);
                config_page.wip_config = config;
                config_page
                    .server_inputs_page
                    .set_active(Some(index), &server);
            }
        }
        Action::ConfigUpdateServerInput => {
            // todo chain?
            if let Some(config_page) = &mut state.config_page {
                let server_inputs_page = &mut config_page.server_inputs_page;
                let mut config = config_page.wip_config.clone();

                println!("aaa {}", server_inputs_page.ignore_change);
                if server_inputs_page.ignore_change <= 0 {
                    if let Some(index) = server_inputs_page.active {
                        let address = server_inputs_page.address.text().to_string();
                        let password = server_inputs_page.password.text().to_string();
                        let name = server_inputs_page.name.text().to_string();

                        let server = pmpdc_config::Server {
                            address: address,
                            password: password,
                            name: Some(name),
                        };
                        config.update_server(index, server.clone());
                        config_page.update_server(index as i32, &server, &state.sender.clone());
                    }
                    config_page.wip_config = config;
                } else {
                    server_inputs_page.ignore_change -= 1;
                }
            }
        }
        Action::ConfigSetActiveServer { index } => {
            if let Some(config_page) = &mut state.config_page {
                let server_inputs_page = &mut config_page.server_inputs_page;
                let config = &config_page.wip_config;

                if let Some(server) = config.get_servers().get(index) {
                    server_inputs_page.set_active(Some(index), &server);
                }
            }
        }
        Action::SaveWipConfig => {
            if let Some(config_page) = &mut state.config_page {
                let config = config_page.wip_config.clone();
                state.initialized = false;
                config.write();
                state.config = Some(config);
            }
        }
        Action::RaiseWindow => {
            state.window.present();
        }
        Action::ClientError { error } => {
            println!("recovering from error {}", error);
            // TODO remove copy pasta from init
            let sender = &state.sender;
            let (server, password) = state
                .config
                .as_ref()
                .and_then(|c| c.active_server())
                .map_or(("".to_owned(), "".to_owned()), |s| {
                    (s.address.clone(), s.password.clone())
                });
            state
                .pool
                .push(clone!(@strong sender => move || {
                    match futures_lite::future::block_on(init_client(server,password)) {
                        Ok((client, status, tracks, idle_client)) => {
                            send!(sender, Action::GotQueue {tracks: tracks});
                            send!(sender, Action::GotClient {client: client});
                            send!(sender, Action::GotStatus {status: status});
                            send!(sender, Action::GotIdleClient {idle_client: idle_client});
                        }
                        e => println!("failed to init client {:#?}", e)
                    }
                }))
                .expect("failed push to thread pool (Init)");
        }
    }
    glib::Continue(true)
}

// TODO make this a macro so the Send can be avoided
fn with_client_and_result_err<F, R, F2>(state: &mut State, client_fn: F, result_fn: F2)
where
    F: FnOnce(&mut Client) -> R + 'static + std::marker::Send,
    F2: FnOnce(&glib::Sender<Action>, R) -> () + 'static + std::marker::Send,
{
    let sender = &state.sender;
    let mut c2 = None;
    std::mem::swap(&mut state.client, &mut c2);
    match c2 {
        Some(mut c) => {
            let _ = state.pool.push(clone!(@strong sender => move || {
                let result = client_fn(&mut c);
                send!(sender, Action::GotClient {client: c});
                result_fn(&sender, result);
            }));
        }
        None => println!("no connection in pool available, refresh if persists."),
    }
}
// TODO make this a macro so the Send can be avoided
fn with_client_and_result<F, R, F2>(state: &mut State, client_fn: F, result_fn: F2)
where
    F: FnOnce(&mut Client) -> Result<R> + 'static + std::marker::Send,
    F2: FnOnce(&glib::Sender<Action>, R) -> () + 'static + std::marker::Send,
{
    with_client_and_result_err(state, client_fn, |sender, result| match result {
        Ok(r) => result_fn(&sender, r),
        Err(e) => {
            println!("error result {:?}", e);
            send!(sender, Action::ClientError { error: e });
        }
    })
}
fn with_client<F, R>(state: &mut State, client_fn: F)
where
    F: FnOnce(&mut Client) -> Result<R> + 'static + std::marker::Send,
{
    with_client_and_result(state, client_fn, |_, _| ())
}

fn do_idle(sender: &glib::Sender<Action>) -> glib::Continue {
    let cmd = Action::Idle;
    sender.send(cmd).unwrap();
    glib::Continue(true)
}
fn do_second(sender: &glib::Sender<Action>) -> glib::Continue {
    let cmd = Action::SecondPassed;
    sender.send(cmd).unwrap();
    glib::Continue(true)
}

async fn init_client(
    server: String,
    password: String,
) -> Result<(Client, Status, Vec<Track>, Client)> {
    let port = parse_port(&server);
    // make sure default port 6600 is appeneded
    let addr = if port.is_some() {
        server.clone()
    } else {
        format!("{}:{}", server, 6600)
    };
    // println!("server {} {:#?} {}", addr, port, password);
    let mut client = Client::init(&addr, &password).await?;
    let idle_client = Client::init(&addr, &password);
    let status = client.status().await?;
    let (tracks, _queue_files) = client
        .queue(
            0,
            &SearchFields {
                file: true,
                title: true,
                artist: true,
                album: true,
            },
        )
        .await?;
    Ok((client, status, tracks, idle_client.await?))
}

fn parse_port(string: &str) -> Option<&str> {
    string.rsplitn(1, ':').nth(1)
}

fn init_mpris(
    sender: &glib::Sender<Action>,
    status: Option<&Status>,
    track: &Option<&Track>,
) -> Arc<MprisPlayer> {
    use mpris_player::{LoopStatus, OrgMprisMediaPlayer2Player};

    let mpris = MprisPlayer::new(
        config::APP_ID.to_string(),
        "pmpdc".to_string(),
        format!("{}.desktop", config::APP_ID),
    );
    mpris.set_can_raise(true);
    mpris.set_can_play(true);
    mpris.set_can_seek(true);
    mpris.set_can_set_fullscreen(false);

    mpris.set_can_go_previous(true);
    mpris.set_can_go_next(true);
    mpris.set_can_pause(true);
    mpris.set_can_control(true);

    mpris.connect_play_pause(clone!(@strong sender, @weak mpris => move || {

        if let Ok(status) = mpris.get_playback_status() {
            match status.as_ref() {
                "Paused" => send!(sender, Action::SetPause{pause: false}),
                "Stopped" => send!(sender, Action::SetPause{pause: false}),
                _ => send!(sender, Action::SetPause{pause: true}),
            };
        }
    }));

    mpris.connect_play(clone!(@strong sender => move || {
        println!("mpris play");
        send!(sender, Action::SetPause{pause: false});
    }));

    mpris.connect_pause(clone!(@strong sender => move || {
        println!("mpris pause");
        send!(sender, Action::SetPause{pause: true});
    }));

    mpris.connect_next(clone!(@strong sender => move || {
        println!("mpris next");
        send!(sender, Action::NextTrack);
    }));

    mpris.connect_previous(clone!(@strong sender => move || {
        println!("mpris previous");
        send!(sender, Action::PreviousTrack);
    }));

    mpris.connect_raise(clone!(@strong sender => move || {
        println!("mpris raise");
        send!(sender, Action::RaiseWindow);
    }));

    mpris.connect_seek(clone!(@strong sender => move |s| {
        println!("mpris seek");
        send!(sender, Action::SeekRelative {offset: (s as f64) / 1000000.0});
    }));

    mpris.connect_loop_status(clone!(@strong sender => move |l| {
        println!("mpris loop_status");
        match l {
            LoopStatus::None => send!(sender, Action::SetEndless {endless: false}),
            LoopStatus::Playlist => {
                send!(sender, Action::SetEndless {endless: true });
                send!(sender, Action::SetSingleMode { single: false });
            }
            LoopStatus::Track => {
                send!(sender, Action::SetEndless {endless: true });
                send!(sender, Action::SetSingleMode { single: true });
            }
        }
    }));

    mpris.connect_shuffle(clone!(@strong sender => move |s| {
        println!("mpris shuffle");
        send!(sender, Action::SetRandomize {randomize: s});
    }));

    mpris.connect_volume(clone!(@strong sender => move |v| {
        println!("mpris volume");
        send!(sender, Action::SetVolume {volume: v as u16});
    }));

    sync_mpris_with_status(&mpris, status, track);
    mpris
}

fn sync_mpris_with_status(
    mpris: &Arc<MprisPlayer>,
    status: Option<&Status>,
    track: &Option<&Track>,
) {
    use mpris_player::Metadata;
    if let Some(status) = status {
        mpris.set_playback_status(match status.state {
            Playback::Playing => mpris_player::PlaybackStatus::Playing,
            Playback::Paused => mpris_player::PlaybackStatus::Paused,
            Playback::Stopped => mpris_player::PlaybackStatus::Stopped,
        });
        // mpris.set_shuffle(status.random);
        // mpris.set_loop_status(match (status.repeat, status.single) {
        //     (true, mpd::SingleMode::On) => LoopStatus::Track,
        //     (true, mpd::SingleMode::Oneshot) => LoopStatus::Track,
        //     (true, mpd::SingleMode::Off) => LoopStatus::Playlist,
        //     (false, _) => LoopStatus::None,
        // });
        // mpris.set_volume(((status.volume as f64) / 100.0).clamp(0.0, 1.0));
    }

    let mut metadata = Metadata::new();
    if let Some(track) = track {
        metadata.artist = track.artist.as_ref().map(|a| vec![a.clone()]);
        metadata.title = track.title.clone();
        metadata.length = Some(track.time as i64);
        metadata.album = track.album.clone();
        // metadata.album_artist =  Option<Vec<String>>,
        // metadata.genre =  Option<Vec<String>>,
        // metadata.url =  Option<String>,
    }
    let mut path = std::env::temp_dir().to_path_buf();
    path.push("mpris-cover");
    if path.exists() {
        let url = Url::from_file_path(path).ok();
        metadata.art_url = url.map(|u| u.to_string());
    }

    mpris.set_metadata(metadata);
}

fn write_mpris_tmp_cover(bs: &bytes::Bytes) {
    let mut path = std::env::temp_dir().to_path_buf();
    path.push("mpris-cover");

    if let Err(err) = std::fs::write(path, bs) {
        eprintln!("failed to write mpris cover {err}");
    }
}

fn delete_mpris_cover() {
    let mut path = std::env::temp_dir().to_path_buf();
    path.push("mpris-cover");
    std::fs::remove_file(path);
}

fn setup_gactions(state: &State) {
    add_action!(state, "quit", Action::Quit);
    add_action!(state, "toggle-single-mode", Action::ToggleSingleMode);
    add_action!(state, "toggle-consume-mode", Action::ToggleConsumeMode);
    add_action!(state, "toggle-endless-mode", Action::ToggleEndless);
    add_action!(state, "toggle-shuffle-mode", Action::ToggleRandomize);

    add_action!(state, "clear-queue", Action::ClearQueue);
    add_action!(state, "toggle-pause", Action::TogglePause);
    add_action!(state, "play-next", Action::NextTrack);
    add_action!(state, "play-previous", Action::PreviousTrack);

    add_action!(state, "open-filter-page", Action::OpenFilterPage);
    add_action!(state, "open-queue-page", Action::OpenQueuePage);
    add_action!(state, "preferences", Action::OpenConfig);

    add_action!(state, "refresh", Action::InitClient);
}

fn setup_accels(app: &adw::Application) {
    app.set_accels_for_action("app.quit", &["<primary>q"]);
    app.set_accels_for_action("app.toggle-single-mode", &["<primary>y"]);
    app.set_accels_for_action("app.toggle-consume-mode", &["<primary>c"]);
    app.set_accels_for_action("app.toggle-endless-mode", &["<primary>r"]);
    app.set_accels_for_action("app.toggle-shuffle-mode", &["<primary>z"]);

    app.set_accels_for_action("app.clear-queue", &["<primary><Alt>c"]);
    app.set_accels_for_action("app.toggle-pause", &["<primary>space"]);
    app.set_accels_for_action("app.play-next", &["<primary>Right"]);
    app.set_accels_for_action("app.play-previous", &["<primary>Left"]);

    app.set_accels_for_action("app.open-filter-page", &["<Alt>Left"]);
    app.set_accels_for_action("app.open-queue-page", &["<Alt>Right"]);
    app.set_accels_for_action("app.preferences", &["<Alt>p"]);
    // Bind the hamburger menu button to `F10`
    app.set_accels_for_action("app.menu", &["F10"]);
    app.set_accels_for_action("app.refresh", &["<primary>r"]);
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Have the tmp dir shareable, so mpris can read the cover path and it will be something like:
    // /run/flatpak/1000/pmpdc/tmp/mpris-cover
    // and not the unreachable:
    // /tmp/mpris-cover
    if let (Ok(rtd), Ok(fpid)) = (
        std::env::var("XDG_RUNTIME_DIR"),
        std::env::var("FLATPAK_ID"),
    ) {
        std::env::set_var("TMPDIR", vec![rtd, "/app/".to_string(), fpid].join(""));
    }

    // Prepare i18n
    gettextrs::setlocale(LocaleCategory::LcAll, "");
    gettextrs::bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR).expect("Unable to bind the text domain");
    gettextrs::textdomain(GETTEXT_PACKAGE).expect("Unable to switch to the text domain");

    glib::set_application_name(&gettext("pmpdc"));
    let res = gio::Resource::load(RESOURCES_FILE).expect("Could not load gresource file");
    gio::resources_register(&res);

    gtk::init()?;
    let application = adw::Application::new(Some(crate::config::APP_ID), Default::default());
    application.connect_activate(move |app| {
        let (sender, r) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
        let init_sender = sender.clone();
        let receiver = RefCell::new(Some(r));
        let pool = glib::ThreadPool::shared(Some(10)).expect("Failed to build pool");

        setup_accels(app);
        setup_css();

        let root = gtk::Stack::new();
        let app_window = ApplicationWindow::new(app);
        app_window.set_title(Some("pmpdc"));
        app_window.set_default_size(320, 720);
        app_window.set_child(Some(&root));
        app_window.set_decorated(false);
        app_window.show();

        let media_keys = gtk::EventControllerKey::builder()
            .propagation_limit(gtk::PropagationLimit::None)
            .propagation_phase(gtk::PropagationPhase::Capture)
            .build();

        let keys_sender = init_sender.clone();
        media_keys.connect_key_pressed(move |_, key, code, _modifier| {
            // let code = event.get_keyval();
            println!("code pressed {:#?} {:#?}", key, code);
            if code == gtk::gdk::ffi::GDK_KEY_AudioRaiseVolume as u32 {
                keys_sender.send(Action::RaiseVolume).unwrap();
                gtk::Inhibit(true)
            } else if code == gtk::gdk::ffi::GDK_KEY_AudioLowerVolume as u32 {
                keys_sender.send(Action::LowerVolume).unwrap();
                gtk::Inhibit(true)
            } else {
                gtk::Inhibit(false)
            }
        });
        app_window.add_controller(media_keys);

        let filter_page = FilterPage::new(&sender);
        root.add_named(&filter_page, Some("filters"));

        let mut state = State {
            stack: root,
            sender: sender,
            pool: Rc::new(pool),
            client: None,
            idle_client: None,
            mpris: None,
            status: None,
            controls: None,
            queue_page: None,
            tracks: vec![],
            filter_page: filter_page,
            initialized: false,
            config_page: None,
            config: None,
            window: app_window,
            app: app.clone(),
        };

        setup_gactions(&state);

        let receiver2 = receiver.borrow_mut().take().unwrap();
        receiver2.attach(None, move |action| do_action(&mut state, action));

        let idle_sender = init_sender.clone();
        let second_sender = init_sender.clone();
        glib::timeout_add_local(std::time::Duration::from_secs(1), move || {
            do_idle(&idle_sender)
        });
        glib::timeout_add_local(std::time::Duration::from_secs(1), move || {
            do_second(&second_sender)
        });

        let cmd = Action::Init;
        init_sender.send(cmd).unwrap();
    });

    fn setup_css() {
        let provider = gtk::CssProvider::new();
        provider.load_from_resource("/blue/hidamari/pmpdc/style.css");
        if let Some(display) = gtk::gdk::Display::default() {
            gtk::style_context_add_provider_for_display(
                &display,
                &provider,
                gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
            );
        }
    }

    println!("running!");
    application.run();

    Ok(())
}