root/src/main.rs

// main.rs
// Copyright 2023 nee <nee-git@patchouli.garden>
// SPDX-License-Identifier: AGPL-3.0-or-later
#![cfg_attr(debug_assertions, allow(dead_code, unused_imports))]

// use std::io::prelude::*;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::fmt::Display;
use std::path::PathBuf;
use std::sync::{Arc, RwLock};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio::sync::broadcast;
use tokio::sync::mpsc;

mod configfile;
use configfile::ConfigFile;

use rodio::{
    Decoder, // OutputStream, source::Source,
    Sink,
};

const PROTOCOL_VERSION: &str = "0.22.4";

#[allow(dead_code, non_camel_case_types)]
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Hash)]
enum TagType {
    Artist,
    ArtistSort,
    Album,
    AlbumSort,
    AlbumArtist,
    AlbumArtistSort,
    Title,
    Track,
    Name,
    Genre,
    Date,
    OriginalDate,
    Composer,
    Performer,
    Conductor,
    Work,
    Grouping,
    Disc,
    Label,
    MUSICBRAINZ_ARTISTID,
    MUSICBRAINZ_ALBUMID,
    MUSICBRAINZ_ALBUMARTISTID,
    MUSICBRAINZ_TRACKID,
    MUSICBRAINZ_RELEASETRACKID,
    MUSICBRAINZ_WORKID,
}

impl Display for TagType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match &self {
            TagType::Artist => write!(f, "Artist"),
            TagType::ArtistSort => write!(f, "ArtistSort"),
            TagType::Album => write!(f, "Album"),
            TagType::AlbumSort => write!(f, "AlbumSort"),
            TagType::AlbumArtist => write!(f, "AlbumArtist"),
            TagType::AlbumArtistSort => write!(f, "AlbumArtistSort"),
            TagType::Title => write!(f, "Title"),
            TagType::Track => write!(f, "Track"),
            TagType::Name => write!(f, "Name"),
            TagType::Genre => write!(f, "Genre"),
            TagType::Date => write!(f, "Date"),
            TagType::OriginalDate => write!(f, "OriginalDate"),
            TagType::Composer => write!(f, "Composer"),
            TagType::Performer => write!(f, "Performer"),
            TagType::Conductor => write!(f, "Conductor"),
            TagType::Work => write!(f, "Work"),
            TagType::Grouping => write!(f, "Grouping"),
            TagType::Disc => write!(f, "Disc"),
            TagType::Label => write!(f, "Label"),
            TagType::MUSICBRAINZ_ARTISTID => write!(f, "MUSICBRAINZ_ARTISTID"),
            TagType::MUSICBRAINZ_ALBUMID => write!(f, "MUSICBRAINZ_ALBUMID"),
            TagType::MUSICBRAINZ_ALBUMARTISTID => write!(f, "MUSICBRAINZ_ALBUMARTISTID"),
            TagType::MUSICBRAINZ_TRACKID => write!(f, "MUSICBRAINZ_TRACKID"),
            TagType::MUSICBRAINZ_RELEASETRACKID => write!(f, "MUSICBRAINZ_RELEASETRACKID"),
            TagType::MUSICBRAINZ_WORKID => write!(f, "MUSICBRAINZ_WORKID"),
        }
    }
}

impl TryFrom<&str> for TagType {
    type Error = &'static str;

    fn try_from(s: &str) -> Result<Self, Self::Error> {
        match s {
            "Artist" => Ok(TagType::Artist),
            "ArtistSort" => Ok(TagType::ArtistSort),
            "Album" => Ok(TagType::Album),
            "AlbumSort" => Ok(TagType::AlbumSort),
            "AlbumArtist" => Ok(TagType::AlbumArtist),
            "AlbumArtistSort" => Ok(TagType::AlbumArtistSort),
            "Title" => Ok(TagType::Title),
            "Track" => Ok(TagType::Track),
            "Name" => Ok(TagType::Name),
            "Genre" => Ok(TagType::Genre),
            "Date" => Ok(TagType::Date),
            "OriginalDate" => Ok(TagType::OriginalDate),
            "Composer" => Ok(TagType::Composer),
            "Performer" => Ok(TagType::Performer),
            "Conductor" => Ok(TagType::Conductor),
            "Work" => Ok(TagType::Work),
            "Grouping" => Ok(TagType::Grouping),
            "Disc" => Ok(TagType::Disc),
            "Label" => Ok(TagType::Label),
            "MUSICBRAINZ_ARTISTID" => Ok(TagType::MUSICBRAINZ_ARTISTID),
            "MUSICBRAINZ_ALBUMID" => Ok(TagType::MUSICBRAINZ_ALBUMID),
            "MUSICBRAINZ_ALBUMARTISTID" => Ok(TagType::MUSICBRAINZ_ALBUMARTISTID),
            "MUSICBRAINZ_TRACKID" => Ok(TagType::MUSICBRAINZ_TRACKID),
            "MUSICBRAINZ_RELEASETRACKID" => Ok(TagType::MUSICBRAINZ_RELEASETRACKID),
            "MUSICBRAINZ_WORKID" => Ok(TagType::MUSICBRAINZ_WORKID),
            _ => Err("invalid value"),
        }
    }
}

#[derive(Debug, Clone)]
enum Action {
    PlayAtList(usize),
    PlayForId(u64),
    Pause(bool),
    TogglePause,
    Stop,
    Previous,
    Next,
    Volume(f32),
    BroadcastIdle(IdleAction),

    TrackEnded,
}

#[derive(Debug, Clone, PartialEq)]
enum IdleAction {
    Database,
    Update,
    StoredPlaylist,
    Playlist,
    Player,
    Mixer,
    Output,
    Options,
    Partition,
    Sticker,
    Subscription,
    Message,
    Neighbor,
    Mount,
}

impl Display for IdleAction {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match &self {
            IdleAction::Database => write!(f, "database"),
            IdleAction::Update => write!(f, "update"),
            IdleAction::StoredPlaylist => write!(f, "stored_playlist"),
            IdleAction::Playlist => write!(f, "playlist"),
            IdleAction::Player => write!(f, "player"),
            IdleAction::Mixer => write!(f, "mixer"),
            IdleAction::Output => write!(f, "output"),
            IdleAction::Options => write!(f, "options"),
            IdleAction::Partition => write!(f, "partition"),
            IdleAction::Sticker => write!(f, "sticker"),
            IdleAction::Subscription => write!(f, "subscription"),
            IdleAction::Message => write!(f, "message"),
            IdleAction::Neighbor => write!(f, "neighbor"),
            IdleAction::Mount => write!(f, "mount"),
        }
    }
}

// TODO get rid of redundant fields, don't impl Display, have a custom fn on State instead
#[derive(Debug, Default, Clone)]
#[allow(dead_code)]
struct Status {
    // pub partition: Partition
    pub repeat: bool,
    pub random: bool,
    pub single: SingleMode, // None: oneshot
    pub consume: SingleMode,
    pub playlistlength: usize,
    pub playlist_update_counter: u64, // DIVERSION protocol says u32
    pub state: Playback,
    pub volume: u16,
    pub song: Option<Song>,
    pub next_song: Option<Song>,
    pub song_playlist_pos: u32,
    pub elapsed: u32,
    pub last_play_time: Option<std::time::Instant>,
    pub duration: u32,
    // bitrate: instantaneous bitrate in kbps
    // xfade: crossfade in seconds (see Cross-Fading)
    // mixrampdb: mixramp threshold in dB
    // mixrampdelay: mixrampdelay in seconds
    // audio: The format emitted by the decoder plugin during playback, format: samplerate:bits:channels. See Global Audio Format for a detailed explanation.
    // updating_db: job id
    // error: if there is an error, returns message here
}

impl Status {
    fn update_elapsed(&mut self) {
        if let Some(last_play_time) = self.last_play_time {
            self.elapsed = std::time::Instant::now()
                .duration_since(last_play_time)
                .as_secs() as u32;
        }
    }
}

impl Display for Status {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "partition: default
repeat: {}
random: {}
single: {}
consume: {}
state: {}
playlist: {}
playlistlength: {}
volume: {}",
            if self.repeat { "1" } else { "0" },
            if self.random { "1" } else { "0" },
            self.single,
            self.consume,
            self.state,
            self.playlist_update_counter,
            self.playlistlength,
            self.volume
        )?;
        if let Some(song) = &self.song {
            write!(
                f,
                "\nsong: {}
elapsed: {}
duration: {}",
                self.song_playlist_pos, self.elapsed, song.duration
            )?;

            if let Some(playlist_id) = song.playlist_id {
                write!(f, "\nsongid: {}", playlist_id)?
            }
        }
        Ok(())
    }
}

#[derive(Debug, Default)]
struct State {
    status: Status,
    playlist: Vec<Song>,
    config: ConfigFile,
    // TODO move to connection specific settings
    tagtypes: HashSet<TagType>,
    binarylimit: u64,
    db: Db,
}

impl State {}

#[derive(Deserialize, Serialize, Debug, Default)]
struct Db {
    songs: Vec<Song>,
    stats: Stats,
}
impl Db {
    fn update_stats(&mut self) {
        let unknown = "Unknown".to_string();
        let artists_set: HashSet<&String> = self
            .songs
            .iter()
            .map(|s| s.tags.get(&TagType::Artist).unwrap_or(&unknown))
            .collect();
        let albums_set: HashSet<&String> = self
            .songs
            .iter()
            .map(|s| s.tags.get(&TagType::Album).unwrap_or(&unknown))
            .collect();

        self.stats = Stats {
            artists: artists_set.len() as u64,
            albums: albums_set.len() as u64,
            songs: self.songs.len() as u64,
            uptime: 0,
            db_playtime: self
                .songs
                .iter()
                .fold(0, |secs, s| secs + (s.duration as u64)),
            db_update: 0, //last db update in UNIX time (seconds since 1970-01-01 UTC)
            playtime: 0,  //time length of music played
        }
    }

    fn apply_update(&mut self, songs: Vec<Song>) {
        for new_song in songs.into_iter() {
            let mut updated = false;
            for s in self.songs.iter_mut() {
                if s.song_path == new_song.song_path {
                    *s = new_song.clone();
                    updated = true;
                    break;
                }
            }
            if !updated {
                self.songs.push(new_song);
            }
        }
        self.update_stats();
    }

    fn write(&self) -> Option<()> {
        let mut path = dirs::data_local_dir()?;
        path.push("mpdr");
        std::fs::create_dir_all(&path).ok();
        path.push("db.toml");
        let data_str = toml::to_string(&self).ok()?;
        fs::write(path, data_str).ok()?;
        Some(())
        // TODO stream this https://github.com/serde-rs/json/issues/345#issuecomment-323592752
    }

    pub fn read() -> Option<Db> {
        let mut path = dirs::data_local_dir()?;
        path.push("mpdr/db.toml");
        let config: Db = toml::from_str(&fs::read_to_string(path).ok()?).ok()?;
        Some(config)
    }
}

#[allow(dead_code)]
#[derive(Debug, PartialEq, Clone, Copy, Default)]
pub enum Playback {
    Playing,
    Paused,
    #[default]
    Stopped,
}

impl Display for Playback {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match &self {
            Playback::Playing => write!(f, "play"),
            Playback::Paused => write!(f, "pause"),
            Playback::Stopped => write!(f, "stop"),
        }
    }
}

#[allow(dead_code)]
#[derive(Debug, PartialEq, Clone, Copy, Default)]
pub enum SingleMode {
    On,
    #[default]
    Off,
    Oneshot,
}

impl Display for SingleMode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match &self {
            SingleMode::On => write!(f, "1"),
            SingleMode::Off => write!(f, "0"),
            SingleMode::Oneshot => write!(f, "oneshot"),
        }
    }
}

impl TryFrom<&str> for SingleMode {
    type Error = &'static str;

    fn try_from(s: &str) -> Result<SingleMode, Self::Error> {
        match s {
            "1" => Ok(SingleMode::On),
            "0" => Ok(SingleMode::Off),
            "oneshot" => Ok(SingleMode::Oneshot),
            _ => Err("invalid value"),
        }
    }
}

#[derive(Debug, PartialEq, Clone)]
enum Command {
    SingleCommand(Vec<String>),
    CommandList(Vec<Vec<String>>, bool), // bool: return ok for every command?
}

fn all_tagtypes() -> HashSet<TagType> {
    HashSet::from([
        TagType::Artist,
        TagType::ArtistSort,
        TagType::Album,
        TagType::AlbumSort,
        TagType::AlbumArtist,
        TagType::AlbumArtistSort,
        TagType::Title,
        TagType::Track,
        TagType::Name,
        TagType::Genre,
        TagType::Date,
        TagType::OriginalDate,
        TagType::Composer,
        TagType::Performer,
        TagType::Conductor,
        TagType::Work,
        TagType::Grouping,
        TagType::Disc,
        TagType::Label,
        TagType::MUSICBRAINZ_ARTISTID,
        TagType::MUSICBRAINZ_ALBUMID,
        TagType::MUSICBRAINZ_ALBUMARTISTID,
        TagType::MUSICBRAINZ_TRACKID,
        TagType::MUSICBRAINZ_RELEASETRACKID,
        TagType::MUSICBRAINZ_WORKID,
    ])
}

fn parse_bool_arg(input: &str) -> Option<bool> {
    match input {
        "1" => Some(true),
        "0" => Some(false),
        _ => None,
    }
}

fn parse_command(input: &str) -> Command {
    let mut in_str = false;
    let mut last_char: Option<char> = None;
    let mut args: Vec<String> = vec![];
    let mut buffer: String = "".to_string();
    let mut in_list = false;
    let mut in_list_ok = false;
    let mut list_results: Vec<Vec<String>> = vec![];
    input.chars().for_each(|c| {
        if c == '"' && last_char != Some('\\') {
            in_str = !in_str;
        } else if in_list && c == '\n' {
            if buffer == "command_list_end" {
                // pass
            } else {
                args.push(buffer.clone());
                list_results.push(args.clone());
            }
            buffer = "".to_string();
            args = vec![];
        } else if !in_str && c == ' ' || (!in_list && c == '\n') {
            if buffer == "command_list_begin" {
                in_list = true;
                in_list_ok = false;
            } else if buffer == "command_list_ok_begin" {
                in_list = true;
                in_list_ok = true;
            } else {
                args.push(buffer.clone());
            }
            buffer = "".to_string();
        } else if c == '\\' {
            if last_char == Some('\\') {
                buffer.push(c);
            }
        } else {
            buffer.push(c);
        }

        last_char = Some(c);
    });
    if in_list {
        Command::CommandList(list_results, in_list_ok)
    } else {
        Command::SingleCommand(args)
    }
}

async fn handle_client(
    sender: mpsc::UnboundedSender<Action>,
    mut idle_receiver: broadcast::Receiver<IdleAction>,
    mut stream: TcpStream,
    state: Arc<RwLock<State>>,
) -> std::io::Result<()> {
    stream
        .write_all(format!("OK MPD {}\n", PROTOCOL_VERSION).as_bytes())
        .await?;

    let mut idling = false;
    loop {
        let mut buf = vec![0; 4096];
        let mut did_not_read = false;

        if let Ok(length) = stream.try_read(&mut buf) {
            if length == 0 {
                break;
            }

            let zero_pos = buf
                .iter()
                .enumerate()
                .find_map(|(i, v)| if *v == b'\0' { Some(i) } else { None })
                .unwrap_or(buf.len());
            let full_command =
                String::from_utf8(buf[0..zero_pos].to_vec()).unwrap_or("".to_string());
            println!("handling {full_command}");
            let args = parse_command(&full_command);
            // println!("handling {args:#?}");
            match args {
                Command::SingleCommand(args) => {
                    let args_ref: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
                    match handle_command(&sender, &state, args_ref) {
                        CommandResult::Ok(mut s) => {
                            s.push_str("\nOK\n");
                            println!("resp2: {s}");
                            stream.write_all(s.as_bytes()).await?;
                        }
                        CommandResult::OkEmpty => {
                            println!("resp1: OK");
                            stream.write_all(b"OK\n").await?;
                        }
                        CommandResult::Idle => {
                            idling = true;
                        }
                        CommandResult::NoIdle => {
                            idling = false;
                            stream.write_all(b"OK\n").await?;
                        }
                        CommandResult::Ack(msg) => {
                            eprintln!("ACK {msg}");
                            stream
                                .write_all(format!("ACK {}\n", msg).as_bytes())
                                .await?;
                        }
                        CommandResult::Binary(_) => (),
                    }
                }
                Command::CommandList(lst, true) => {
                    let mut results: Vec<String> = vec![];
                    let mut error = false;
                    // println!("GOT LIST {lst:#?}");
                    for args in lst {
                        let args_ref: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
                        match handle_command(&sender, &state, args_ref) {
                            CommandResult::Ok(s) => {
                                results.push(s);
                                results.push("list_OK".to_string());
                            }
                            CommandResult::OkEmpty => {
                                results.push("list_OK".to_string());
                            }
                            CommandResult::Idle => {
                                idling = true;
                            }
                            CommandResult::NoIdle => {
                                idling = false;
                                results.push("list_OK".to_string());
                            }
                            CommandResult::Ack(msg) => {
                                eprintln!("ACK {msg}");
                                stream
                                    .write_all(format!("ACK {}\n", msg).as_bytes())
                                    .await?;
                                error = true;
                                break;
                            }
                            CommandResult::Binary(_) => (),
                        }
                    }
                    if !error {
                        results.push("OK\n".to_string());
                        println!("CMD {}", results.join("\n"));
                        stream.write_all(results.join("\n").as_bytes()).await?;
                    }
                }
                Command::CommandList(lst, false) => {
                    let mut results: Vec<String> = vec![];
                    let mut error = false;
                    for args in lst {
                        let args_ref: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
                        match handle_command(&sender, &state, args_ref) {
                            CommandResult::Ack(msg) => {
                                eprintln!("ACK {msg}");
                                // TODO print: ACK [error@command_listNum] {current_command} message_text
                                stream.write_all(format!("ACK {msg}\n").as_bytes()).await?;
                                error = true;
                                break;
                            }
                            CommandResult::Ok(s) => results.push(s),
                            _ => (),
                        }
                    }
                    if !error {
                        results.push("OK\n".to_string());
                        println!("CMD {}", results.join("\n"));
                        stream.write_all(results.join("\n").as_bytes()).await?;
                    }
                }
            }
        } else {
            did_not_read = true;
        }

        if idling {
            let mut idle_actions = vec![];
            while let Ok(idle_action) = idle_receiver.try_recv() {
                idle_actions.push(idle_action);
            }
            if !idle_actions.is_empty() {
                idle_actions.dedup();
                let strs: Vec<String> = idle_actions
                    .into_iter()
                    .map(|i| format!("changed: {i}"))
                    .collect();
                println!("IDLE changed: {}", strs.join("\n"));
                stream
                    .write_all(format!("{}\nOK\n", strs.join("\n")).as_bytes())
                    .await?;
                // let strs: Vec<String> = idle_actions.into_iter().map(|i| i.to_string()).collect();
                // println!("IDLE changed: {}", strs.join(" "));
                // stream.write_all(format!("changed: {}\n", strs.join(" ")).as_bytes()).await?;
            } else {
                // don't hog the CPU if nothing is going on
                tokio::time::sleep(std::time::Duration::from_millis(30)).await;
            }
        } else if did_not_read {
            // don't hog the CPU if nothing is going on
            tokio::time::sleep(std::time::Duration::from_millis(30)).await;
        }
    }
    std::io::Result::Ok(())
}
#[derive(Debug, PartialEq, Clone)]
#[allow(dead_code)]
enum CommandResult {
    Ok(String),
    OkEmpty,
    Idle,
    NoIdle,
    Ack(String),
    Binary(Vec<u8>),
}

fn handle_command(
    sender: &mpsc::UnboundedSender<Action>,
    state: &Arc<RwLock<State>>,
    args: Vec<&str>,
) -> CommandResult {
    println!("COMMAND {:#?}", args);
    match args[..] {
        ["password", pw] => {
            let s = state.read().unwrap();
            let real_pw = s.config.password.clone();
            drop(s);
            if !check_password(&real_pw, pw) {
                eprintln!("WRONG PASSWORD");
                CommandResult::Ack("ACK wrong password\n".to_string())
            } else {
                CommandResult::OkEmpty
            }
        }
        ["status"] => {
            let s = state.read().unwrap();
            let mut status2 = s.status.clone();
            status2.update_elapsed();
            // TODO send elapsed updates???
            // if status2.elapsed != s.status.elapsed {
            //     sender.send(Action::BroadcastIdle(IdleAction::Player)).unwrap();
            // }
            let lines: String = format!("{}", status2);
            CommandResult::Ok(lines)
        }
        ["playlistinfo"] => {
            let s = state.read().unwrap();
            let lines: Vec<String> = s.playlist.iter().map(|s| format!("{}", s)).collect();
            let lines_str = lines.join("\n");
            println!("{lines_str}");
            CommandResult::Ok(lines_str)
        }
        ["playlistinfo", index] => {
            if let Ok(index) = index.parse::<usize>() {
                let s = state.read().unwrap();
                if let Some(song) = s.playlist.get(index) {
                    CommandResult::Ok(format!("{song}"))
                } else {
                    CommandResult::Ack("no song at index".to_string())
                }
            } else {
                CommandResult::Ack("invalid index".to_string())
            }
        }

        ["tagtypes", "enable", ..] => {
            let tags = args[2..].to_vec();
            let mut s = state.write().unwrap();
            for t in tags {
                if let Ok(t) = TagType::try_from(t) {
                    s.tagtypes.insert(t);
                }
            }
            CommandResult::OkEmpty
        }
        ["tagtypes", "disable", ..] => {
            let tags = args[2..].to_vec();
            let mut s = state.write().unwrap();
            for t in tags {
                if let Ok(t) = TagType::try_from(t) {
                    s.tagtypes.remove(&t);
                }
            }
            CommandResult::OkEmpty
        }
        ["tagtypes", "clear"] => {
            let mut s = state.write().unwrap();
            s.tagtypes.clear();
            CommandResult::OkEmpty
        }
        ["tagtypes", "all"] => {
            let mut s = state.write().unwrap();
            s.tagtypes = all_tagtypes();
            CommandResult::OkEmpty
        }
        ["tagtypes"] => {
            let s = state.read().unwrap();
            let tags: Vec<String> = s.tagtypes.iter().map(|t| format!("tagtype: {t}")).collect();
            CommandResult::Ok(tags.join("\n"))
        }
        ["idle"] | ["idle", ..] => CommandResult::Idle,
        ["noidle"] => CommandResult::NoIdle,
        ["list", _tag, _filter] => CommandResult::OkEmpty,
        ["list", _tag, "group", _group_tag] => CommandResult::OkEmpty,
        ["list", _tag, _filter, "group", _group_tag] => CommandResult::OkEmpty,
        ["list", tag] => {
            if let Ok(tag) = TagType::try_from(tag) {
                let unknown = "Unknown".to_string();
                let s = state.read().unwrap();
                let result_set: HashSet<&String> =
                    s.db.songs
                        .iter()
                        .map(|s| s.tags.get(&tag).unwrap_or(&unknown))
                        .collect();
                let lines: Vec<String> = result_set
                    .iter()
                    .map(|r| format!("{}: {}", tag, r))
                    .collect();
                CommandResult::Ok(lines.join("\n"))
            } else {
                CommandResult::Ack("Invalid tag".to_string())
            }
        }
        // deprecated pre 0.21
        ["list", tag, filter_tag, filter_value] => {
            if let (Ok(tag), Ok(filter_tag)) =
                (TagType::try_from(tag), TagType::try_from(filter_tag))
            {
                let unknown = "Unknown".to_string();
                let s = state.read().unwrap();
                let result_set: HashSet<&String> = s
                    .db
                    .songs
                    .iter()
                    .filter(|s| s.tags.get(&filter_tag).map(|s| s.as_str()) == Some(filter_value))
                    .map(|s| s.tags.get(&tag).unwrap_or(&unknown))
                    .collect();
                let lines: Vec<String> = result_set
                    .iter()
                    .map(|r| format!("{}: {}", tag, r))
                    .collect();
                CommandResult::Ok(lines.join("\n"))
            } else {
                CommandResult::Ack("Invalid tag".to_string())
            }
        }
        ["lsinfo", dir] => {
            let dir = sanitize_path_input(dir);
            let s = state.read().unwrap();
            let music_dir = s.config.music_dir.clone();
            println!("dir input {:#?}", dir);
            let mut response = lsinfo_db(&s.db, &music_dir, Path::new(&dir));
            response.sort_by_key(|a| {
                if let LsInfo::File(s) = a {
                    Some(s.song_path.to_string())
                } else {
                    None
                }
            });
            response.sort_by_key(|a| {
                if let LsInfo::File(s) = a {
                    s.track_number()
                } else {
                    None
                }
            });
            let lines: Vec<String> = response.iter().map(|l| format!("{}", l)).collect();
            println!("LINES {}", lines.join("\n"));
            CommandResult::Ok(lines.join("\n"))
        }
        ["addid", uri] => {
            let uri = sanitize_path_input(uri);
            let s = state.read().unwrap();
            let song = s.db.songs.iter().find(|s| s.song_path == uri).cloned();
            drop(s);
            if let Some(song) = song {
                let new_songs = add_to_playlist(state, vec![song.clone()]);
                let song = new_songs.first().unwrap();
                sender
                    .send(Action::BroadcastIdle(IdleAction::Playlist))
                    .unwrap();
                if let Some(playlist_id) = song.playlist_id {
                    CommandResult::Ok(format!("Id: {}", playlist_id))
                } else {
                    CommandResult::Ack("could not add song".to_string())
                }
            } else {
                CommandResult::Ack("song not found".to_string())
            }
        }
        ["pause", arg] => {
            let arg = sanitize_path_input(arg);
            command_send_empty(
                sender,
                match arg.as_str() {
                    "1" => Action::Pause(true),
                    "0" => Action::Pause(false),
                    _ => Action::TogglePause,
                },
            )
        }
        ["clearerror"] => {
            println!("TODO impl currenterror");
            CommandResult::OkEmpty
        }
        ["currentsong"] => {
            let s = state.read().unwrap();
            if let Some(song) = s.status.song.as_ref() {
                CommandResult::Ok(format!("{song}"))
            } else {
                CommandResult::OkEmpty
            }
        }
        ["stats"] => {
            let s = state.read().unwrap();
            CommandResult::Ok(format!("{}", s.db.stats))
        }
        ["consume", value] => {
            let parsed = SingleMode::try_from(value);
            if let Ok(value) = parsed {
                let mut s = state.write().unwrap();
                s.status.consume = value;
                sender
                    .send(Action::BroadcastIdle(IdleAction::Player))
                    .unwrap();
                CommandResult::OkEmpty
            } else {
                println!("invalid value for consume {value}");
                CommandResult::Ack("invalid value".to_string())
            }
        }
        ["crossfade", _seconds] => {
            println!("TODO impl crossfade");
            CommandResult::OkEmpty
        }
        ["mixrampdb", _deci_bels] => {
            println!("TODO impl mixrampdb");
            CommandResult::OkEmpty
        }
        ["mixrampdelay", _seconds] => {
            println!("TODO impl mixrampdelay");
            CommandResult::OkEmpty
        }
        ["random", value] => {
            let parsed = parse_bool_arg(value);
            if let Some(value) = parsed {
                let mut s = state.write().unwrap();
                s.status.random = value;
                sender
                    .send(Action::BroadcastIdle(IdleAction::Player))
                    .unwrap();
                CommandResult::OkEmpty
            } else {
                println!("invalid value for random {value}");
                CommandResult::Ack("invalid value".to_string())
            }
        }
        ["repeat", value] => {
            let parsed = parse_bool_arg(value);
            if let Some(value) = parsed {
                let mut s = state.write().unwrap();
                s.status.repeat = value;
                sender
                    .send(Action::BroadcastIdle(IdleAction::Player))
                    .unwrap();
                CommandResult::OkEmpty
            } else {
                println!("invalid value for repeat {value}");
                CommandResult::Ack("invalid value".to_string())
            }
        }
        ["setvol", value] => {
            let parse: Result<i16, _> = value.parse();
            if let Ok(value) = parse {
                if let Err(e) = sender.send(Action::Volume(f32::from(value) / 100.0)) {
                    CommandResult::Ack(e.to_string())
                } else {
                    CommandResult::OkEmpty
                    // CommandResult::Ok(format!("volume: {}", value))
                }
            } else {
                CommandResult::OkEmpty
            }
        }
        ["getvol"] => {
            let s = state.read().unwrap();
            CommandResult::Ok(format!("volume: {}", s.status.volume))
        }
        ["single", value] => {
            let parsed = SingleMode::try_from(value);
            if let Ok(value) = parsed {
                let mut s = state.write().unwrap();
                s.status.single = value;
                sender
                    .send(Action::BroadcastIdle(IdleAction::Player))
                    .unwrap();
                CommandResult::OkEmpty
            } else {
                println!("invalid value for consume {value}");
                CommandResult::Ack("invalid value".to_string())
            }
        }
        ["replay_gain_mode"] => {
            println!("TODO impl replay_gain_mode");
            CommandResult::OkEmpty
        }
        ["replay_gain_status"] => {
            println!("TODO impl replay_gain_status");
            CommandResult::OkEmpty
        }
        ["volume"] => CommandResult::Ack("deprecated, use setvol".to_string()),
        ["next"] => command_send_empty(sender, Action::Next),
        ["play", song_pos] => {
            if let Ok(song_pos) = song_pos.parse() {
                command_send_empty(sender, Action::PlayAtList(song_pos))
            } else {
                CommandResult::Ack("invalid song_pos, not number".to_string())
            }
        }
        ["playid", id] => {
            if let Ok(id) = id.parse() {
                command_send_empty(sender, Action::PlayForId(id))
            } else {
                CommandResult::Ack("invalid id".to_string())
            }
        }
        ["previous"] => command_send_empty(sender, Action::Previous),
        ["seek"] => {
            println!("TODO impl seek");
            CommandResult::OkEmpty
        }
        ["seekid"] => {
            println!("TODO impl seekid");
            CommandResult::OkEmpty
        }
        ["seekcur"] => {
            println!("TODO impl seekcur");
            CommandResult::OkEmpty
        }
        ["stop"] => command_send_empty(sender, Action::Stop),
        ["add", uri] => {
            let uri = sanitize_path_input(uri);
            let s = state.read().unwrap();
            let song = s.db.songs.iter().find(|s| s.song_path == uri).cloned();
            drop(s);
            if let Some(song) = song {
                add_to_playlist(state, vec![song]);
                sender
                    .send(Action::BroadcastIdle(IdleAction::Playlist))
                    .unwrap();
                CommandResult::OkEmpty
            } else {
                CommandResult::Ack("song not found".to_string())
            }
        }
        ["clear"] => {
            let mut s = state.write().unwrap();
            s.playlist = vec![];
            s.status.playlistlength = 0;
            s.status.song = None;
            s.status.next_song = None;
            s.status.song_playlist_pos = 0;
            s.status.playlist_update_counter = 0;
            sender
                .send(Action::BroadcastIdle(IdleAction::Playlist))
                .unwrap();
            command_send_empty(sender, Action::Stop)
        }
        ["delete", pos] => {
            if let Ok(pos) = pos.parse() {
                let mut s = state.write().unwrap();
                if s.playlist.len() > pos {
                    remove_from_playlist(&mut s, pos);
                    sender
                        .send(Action::BroadcastIdle(IdleAction::Playlist))
                        .unwrap();
                    CommandResult::OkEmpty
                } else {
                    CommandResult::Ack("invalid pos".to_string())
                }
                // command_send_empty(sender, Action::DeleteForId(id))
            } else {
                CommandResult::Ack("invalid pos".to_string())
            }
        }
        ["deleteid", id] => {
            if let Ok(id) = id.parse() {
                let mut s = state.write().unwrap();
                if let Some(index) = s.playlist.iter().position(|s| s.playlist_id == Some(id)) {
                    remove_from_playlist(&mut s, index);
                    sender
                        .send(Action::BroadcastIdle(IdleAction::Playlist))
                        .unwrap();
                    CommandResult::OkEmpty
                } else {
                    CommandResult::Ack("invalid id".to_string())
                }
                // command_send_empty(sender, Action::DeleteForId(id))
            } else {
                CommandResult::Ack("invalid id".to_string())
            }
        }
        ["move"] => {
            println!("TODO impl move");
            CommandResult::OkEmpty
        }
        ["moveid"] => {
            println!("TODO impl moveid");
            CommandResult::OkEmpty
        }
        ["playlist"] => CommandResult::Ack("Deprecated, use palylistinfo".to_string()),
        ["playlistfind"] => {
            println!("TODO impl playlistfind");
            CommandResult::OkEmpty
        }
        ["playlistid"] => {
            println!("TODO impl playlistid");
            CommandResult::OkEmpty
        }
        ["playlistsearch"] => {
            println!("TODO impl playlistsearch");
            CommandResult::OkEmpty
        }
        ["plchanges", since] => {
            if let Ok(since) = since.parse() {
                let s = state.read().unwrap();
                let songs: Vec<(usize, &Song)> = s
                    .playlist
                    .iter()
                    .enumerate()
                    .filter(|(_, s)| s.playlist_id.unwrap_or(0) >= since)
                    .collect();
                println!("{:#?} VS {:#?}", s.playlist, songs);
                if !songs.is_empty() {
                    let strs: Vec<String> = songs
                        .into_iter()
                        .map(|(i, song)| format!("{}\nPos: {i}", song))
                        .collect();
                    drop(s);
                    CommandResult::Ok(strs.join("\n"))
                } else {
                    CommandResult::OkEmpty
                }
            } else {
                CommandResult::Ack("invalid since_parameter".to_string())
            }
        }
        ["plchangesposid"] => {
            println!("TODO impl plchangesposid");
            CommandResult::OkEmpty
        }
        ["prio"] => {
            println!("TODO impl prio");
            CommandResult::OkEmpty
        }
        ["prioid"] => {
            println!("TODO impl prioid");
            CommandResult::OkEmpty
        }
        ["rangeid"] => {
            println!("TODO impl rangeid");
            CommandResult::OkEmpty
        }
        ["shuffle"] => {
            println!("TODO impl shuffle");
            CommandResult::OkEmpty
        }
        ["swap"] => {
            println!("TODO impl swap");
            CommandResult::OkEmpty
        }
        ["swapid"] => {
            println!("TODO impl swapid");
            CommandResult::OkEmpty
        }
        ["addtagid"] => CommandResult::Ack("volatile tag ids, not supported".to_string()),
        ["cleartagid"] => CommandResult::Ack("volatile tag ids, not supported".to_string()),
        // PLAYLIST BLOCK
        // --------------------------------------------------
        ["listplaylist"] => {
            println!("TODO impl listplaylist");
            CommandResult::OkEmpty
        }
        ["listplaylistinfo"] => {
            println!("TODO impl listplaylistinfo");
            CommandResult::OkEmpty
        }
        ["listplaylists"] => {
            println!("TODO impl listplaylists");
            CommandResult::OkEmpty
        }
        ["load"] => {
            println!("TODO impl load");
            CommandResult::OkEmpty
        }
        ["playlistadd"] => {
            println!("TODO impl playlistadd");
            CommandResult::OkEmpty
        }
        ["playlistclear"] => {
            println!("TODO impl playlistclear");
            CommandResult::OkEmpty
        }
        ["playlistdelete"] => {
            println!("TODO impl playlistdelete");
            CommandResult::OkEmpty
        }
        ["playlistmove"] => {
            println!("TODO impl playlistmove");
            CommandResult::OkEmpty
        }
        ["rename"] => {
            println!("TODO impl rename");
            CommandResult::OkEmpty
        }
        ["rm"] => {
            println!("TODO impl rm");
            CommandResult::OkEmpty
        }
        ["save"] => {
            println!("TODO impl save");
            CommandResult::OkEmpty
        }
        // --------------------------------------------------
        // PLAYLIST BLOCK END
        ["albumart"] => {
            println!("TODO impl albumart");
            CommandResult::OkEmpty
        }
        ["count", ..] => {
            let filters = args[1..].to_vec();
            let songs = find_command(state, filters, true);
            CommandResult::Ok(format!("{}", songs.len()))
        }
        ["getfingerprint"] => {
            println!("TODO impl getfingerprint");
            CommandResult::OkEmpty
        }
        ["find", ..] => {
            let filters = args[1..].to_vec();
            let songs = find_command(state, filters, true);
            let lines: Vec<String> = songs.iter().map(|song| format!("{song}")).collect();
            CommandResult::Ok(lines.join("\n"))

            // CommandResult::Ack("Invalid tag".to_string())
        }
        ["findadd", ..] => {
            let filters = args[1..].to_vec();
            let songs = find_command(state, filters, true);
            add_to_playlist(state, songs);
            CommandResult::OkEmpty
        }
        ["listall"] => {
            // doc says this is bad
            CommandResult::Ack("listall not implemented by choice, use lsfinfo".to_string())
        }
        ["listallinfo"] => {
            // doc says this is bad
            CommandResult::Ack("listallinfo not implemented by choice, use lsinfo".to_string())
        }
        ["listfiles", dir] => {
            // not implementing listing unreleated files, wtf is this for.
            handle_command(sender, state, vec!["lsinfo", dir])
        }
        ["readcomments"] => {
            println!("TODO impl readcomments");
            CommandResult::OkEmpty
        }
        ["readpicture"] => {
            println!("TODO impl readpicture");
            CommandResult::OkEmpty
        }
        ["search", ..] => {
            // same as find, but case insensitive
            let filters = args[1..].to_vec();
            let songs = find_command(state, filters, false);
            let lines: Vec<String> = songs.iter().map(|song| format!("{song}")).collect();
            CommandResult::Ok(lines.join("\n"))
        }
        ["searchadd", ..] => {
            // same as find, but case insensitive
            let filters = args[1..].to_vec();
            let songs = find_command(state, filters, false);
            add_to_playlist(state, songs);
            CommandResult::OkEmpty
        }
        ["searchaddpl"] => {
            println!("TODO impl searchaddpl");
            CommandResult::OkEmpty
        }
        ["searchcount", ..] => {
            // same as find, but case insensitive
            let filters = args[1..].to_vec();
            let songs = find_command(state, filters, false);
            CommandResult::Ok(format!("{}", songs.len()))
        }
        ["update", dir] => {
            sender
                .send(Action::BroadcastIdle(IdleAction::Update))
                .unwrap();
            let dir = sanitize_path_input(dir);
            let music_dir = {
                let s = state.read().unwrap();
                s.config.music_dir.clone()
            };
            println!("UPDATE {:#?}", dir);
            let updates = scan_db_update(&music_dir, Path::new(&dir));
            {
                let mut s = state.write().unwrap();
                s.db.apply_update(updates);
            };
            sender
                .send(Action::BroadcastIdle(IdleAction::Database))
                .unwrap();

            // TODO track job id
            CommandResult::Ok(format!("updating_db: {}", 1))
        }
        // Same as update, but also rescans unmodified files.
        ["rescan", dir] => {
            // TODO pass some flag
            handle_command(sender, state, vec!["update", dir])
        }
        ["mount"] => {
            println!("TODO impl mount");
            CommandResult::OkEmpty
        }
        ["unmount"] => {
            println!("TODO impl unmount");
            CommandResult::OkEmpty
        }
        ["listmounts"] => {
            println!("TODO impl listmounts");
            CommandResult::OkEmpty
        }
        ["listneighbors"] => {
            println!("TODO impl listneighbors");
            CommandResult::OkEmpty
        }
        // STICKER BLOCK
        // --------------------------------------------------
        ["sticker", "get"] => {
            println!("TODO impl sticker");
            CommandResult::OkEmpty
        }
        ["sticker", "set"] => {
            println!("TODO impl sticker");
            CommandResult::OkEmpty
        }
        ["sticker", "delete"] => {
            println!("TODO impl sticker");
            CommandResult::OkEmpty
        }
        ["sticker", "list"] => {
            println!("TODO impl sticker");
            CommandResult::OkEmpty
        }
        ["sticker", "find"] => {
            println!("TODO impl sticker");
            CommandResult::OkEmpty
        }
        // --------------------------------------------------
        // STICKER BLOCK END
        ["close"] => {
            // clinet should just close
            CommandResult::OkEmpty
        }
        ["kill"] => {
            // WHY is this a command.
            // maybe make it available from config?
            CommandResult::Ack("kill not implemented by choice, use systemd ( ͡° ͜ʖ ͡°) ".to_string())
        }
        ["ping"] => {
            CommandResult::OkEmpty // just say Ok
        }
        ["binarylimit", size] => {
            let size = size.parse();
            if let Ok(size) = size {
                let mut s = state.write().unwrap();
                s.binarylimit = size;
                sender
                    .send(Action::BroadcastIdle(IdleAction::Options))
                    .unwrap();
                CommandResult::OkEmpty
            } else {
                CommandResult::Ack("invalid value".to_string())
            }
        }
        ["partition"] => {
            println!("TODO impl partition");
            CommandResult::OkEmpty
        }
        ["listpartitions"] => {
            println!("TODO impl listpartitions");
            CommandResult::Ack("Not implemented yet".to_string())
        }
        ["newpartition"] => {
            println!("TODO impl newpartition");
            CommandResult::OkEmpty
        }
        ["delpartition"] => {
            println!("TODO impl delpartition");
            CommandResult::OkEmpty
        }
        ["moveoutput"] => {
            println!("TODO impl moveoutput");
            CommandResult::OkEmpty
        }
        ["disableoutput", _id] => {
            println!("TODO impl disableoutput");
            CommandResult::OkEmpty
        }
        ["enableoutput", _id] => {
            println!("TODO impl enableoutput");
            CommandResult::OkEmpty
        }
        ["toggleoutput", _id] => {
            println!("TODO impl toggleoutput");
            CommandResult::OkEmpty
        }
        ["outputs"] => CommandResult::Ok(format!("{}", Output::default())),
        ["outputset"] | ["outputset", ..] => CommandResult::Ack(
            "Remote output config, not implemented in mpdr. Edit the config file locally."
                .to_string(),
        ),
        ["config"] => {
            // maybe implement for socket connections?
            CommandResult::Ack("config not implemented by choice, use emacs".to_string())
        }
        ["commands"] => {
            println!("TODO impl commands");
            CommandResult::OkEmpty
        }
        ["notcommands"] => {
            println!("TODO impl notcommands");
            CommandResult::OkEmpty
        }
        ["urlhandlers"] => {
            println!("TODO impl urlhandlers");
            CommandResult::OkEmpty
        }
        ["decoders"] => {
            println!("TODO impl decoders");
            CommandResult::OkEmpty
        }
        ["subscribe"] => {
            println!("TODO impl subscribe");
            CommandResult::OkEmpty
        }
        ["unsubscribe"] => {
            println!("TODO impl unsubscribe");
            CommandResult::OkEmpty
        }
        ["channels"] => {
            println!("TODO impl channels");
            CommandResult::OkEmpty
        }
        ["readmessages"] => {
            println!("TODO impl readmessages");
            CommandResult::OkEmpty
        }
        ["sendmessage"] => {
            println!("TODO impl sendmessage");
            CommandResult::OkEmpty
        }
        _ => {
            println!("UNKNOWN CMD");
            CommandResult::Ack("Unknown command".to_string())
        }
    }
}

fn command_send_empty(sender: &mpsc::UnboundedSender<Action>, cmd: Action) -> CommandResult {
    if let Err(e) = sender.send(cmd) {
        CommandResult::Ack(e.to_string())
    } else {
        CommandResult::OkEmpty
    }
}

fn find_command(state: &Arc<RwLock<State>>, filters: Vec<&str>, exact_match: bool) -> Vec<Song> {
    let mut filters_result = vec![];
    let mut i = 0;
    while i < filters.len() {
        if let (Some(filter_tag), Some(filter_value)) = (
            filters.get(i).and_then(|s| TagType::try_from(*s).ok()),
            filters.get(i + 1),
        ) {
            filters_result.push((filter_tag, filter_value));
        }
        i += 2;
    }

    let s = state.read().unwrap();

    let mut songs: Vec<Song> = vec![];
    for (filter_tag, filter_value) in filters_result {
        let filter_value = if exact_match {
            filter_value.to_string()
        } else {
            filter_value.to_uppercase()
        };
        if songs.is_empty() {
            songs =
                s.db.songs
                    .iter()
                    .filter(|s| {
                        if exact_match {
                            s.tags.get(&filter_tag).map(|s| s.as_str()) == Some(&filter_value)
                        } else {
                            let tag_value = s.tags.get(&filter_tag).map(|s| s.to_uppercase());
                            tag_value
                                .map(|s| s.as_str().contains(&filter_value))
                                .unwrap_or(false)
                        }
                    })
                    .cloned()
                    .collect();
        } else {
            songs.retain(|s| {
                if exact_match {
                    s.tags.get(&filter_tag).map(|s| s.as_str()) == Some(&filter_value)
                } else {
                    let tag_value = s.tags.get(&filter_tag).map(|s| s.to_uppercase());
                    tag_value
                        .map(|s| s.as_str().contains(&filter_value))
                        .unwrap_or(false)
                }
            });
        }
    }
    songs.sort_by_key(|s| s.track_number().unwrap_or(0));
    songs
}

fn check_password(real_pw: &str, pw: &str) -> bool {
    real_pw == pw
}

fn sanitize_path_input(path: &str) -> String {
    let path = path.trim();
    let path = path.strip_suffix('\n').unwrap_or(path);
    let path = path
        .strip_prefix('"')
        .and_then(|s| s.strip_suffix('"'))
        .unwrap_or(path);
    let path = path.strip_prefix("..").unwrap_or(path);
    path.replace('\\', "/").replace("/..", "")
}

#[derive(Deserialize, Serialize, Debug, Clone)]
struct Song {
    song_path: String,
    // song_id: u64,
    playlist_id: Option<u64>,
    duration: f32,
    tags: HashMap<TagType, String>,
    last_modified: u64,
}

impl Display for Song {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "file: {}
duration: {}",
            self.song_path, self.duration,
        )?;
        if let Some(playlist_id) = self.playlist_id {
            write!(f, "\nId: {}", playlist_id)?;
        }
        let tags: Vec<String> = self.tags.iter().map(|(t, v)| format!("{t}: {v}")).collect();
        if !tags.is_empty() {
            write!(f, "\n{}", tags.join("\n"))?;
        }
        Ok(())
    }
}

impl Song {
    fn track_number(&self) -> Option<i32> {
        self.tags.get(&TagType::Track).and_then(|n| n.parse().ok())
    }
}

#[allow(dead_code)]
enum LsInfo {
    File(Song),
    Directory(String),
    Playlist(String),
}

impl Display for LsInfo {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match &self {
            LsInfo::File(s) => write!(f, "{}", s),
            LsInfo::Directory(p) => write!(f, "directory: {}", p),
            LsInfo::Playlist(p) => write!(f, "playlist: {}", p),
        }
    }
}

fn lsinfo_db(_db: &Db, root: &PathBuf, dir: &Path) -> Vec<LsInfo> {
    // just restore the song ids

    lsinfo_local(root, dir)
    // .into_iter()
    // .map(|mut l| {
    //     if let LsInfo::File(ref mut s) = l {
    //         s.song_id = db
    //             .songs
    //             .iter()
    //             .find_map(|s2| {
    //                 if s.song_path == s2.song_path {
    //                     Some(s2.song_id)
    //                 } else {
    //                     None
    //                 }
    //             })
    //             .unwrap_or(0);
    //     }
    //     l
    // })
    // .collect()
}

fn lsinfo_local(root: &PathBuf, dir: &Path) -> Vec<LsInfo> {
    let path = root.join(dir);
    // println!("DIR {:#?}", path);

    let mut result = vec![];
    if path.is_dir() {
        let _ = (|| {
            for entry in fs::read_dir(path)? {
                let entry = entry?;
                let path = entry.path();
                let relative_path = path.strip_prefix(root).unwrap();
                // println!("LSINFO {path:#?}");
                if path.is_dir() {
                    result.push(LsInfo::Directory(relative_path.to_str().unwrap().into()));
                } else {
                    // println!("PATH FILE {:#?}", relative_path);
                    if let Some(song) = scan_file(&path, relative_path.to_str().unwrap().into()) {
                        result.push(LsInfo::File(song));
                    } else {
                        // println!("Nope, no tags in this 😔");
                    }
                }
            }
            std::io::Result::Ok(())
        })();
    }
    result
}

fn scan_db_init(db: &mut Db, root: &PathBuf, path: &Path) {
    use std::mem::swap;
    // println!("path {path:#?}");
    let results = lsinfo_local(root, path);
    for r in results {
        match r {
            LsInfo::File(song) => {
                // let id = db.songs.len();
                // song.song_id = id as u64;
                db.songs.push(song)
            }
            LsInfo::Directory(dir) => {
                println!("dir {}", dir);
                let buf = Path::new(&dir);
                let mut db2 = Db::default();
                swap(db, &mut db2);
                scan_db_init(&mut db2, root, buf);
                swap(&mut db2, db);
            }
            _ => (),
        }
    }
}

fn scan_db_update(root: &PathBuf, path: &Path) -> Vec<Song> {
    use std::mem::swap;
    // println!("path {path:#?}");
    let mut songs = vec![];
    let results = lsinfo_local(root, path);
    for r in results {
        match r {
            LsInfo::File(song) => {
                // let id = db.songs.len();
                // song.song_id = id as u64;
                songs.push(song)
            }
            LsInfo::Directory(dir) => {
                println!("dir {}", dir);
                let buf = Path::new(&dir);
                songs.append(&mut scan_db_update(root, buf));
            }
            _ => (),
        }
    }
    songs
}

// use std::io;
use std::fs;
use std::path::Path;

//use symphonia::core::codecs::{DecoderOptions, CODEC_TYPE_NULL};
//use symphonia::core::errors::Error;
use symphonia::core::formats::FormatOptions;
use symphonia::core::io::MediaSourceStream;
use symphonia::core::meta::{MetadataOptions, StandardTagKey};
use symphonia::core::probe::Hint;

impl TryFrom<symphonia::core::meta::StandardTagKey> for TagType {
    type Error = &'static str;

    fn try_from(tag: symphonia::core::meta::StandardTagKey) -> Result<TagType, Self::Error> {
        match tag {
            // https://docs.rs/symphonia-core/0.5.3/symphonia_core/meta/enum.StandardTagKey.html#variant.TrackTitle
            StandardTagKey::Album => Ok(TagType::Album),
            StandardTagKey::Artist => Ok(TagType::Artist),
            StandardTagKey::AlbumArtist => Ok(TagType::AlbumArtist),
            StandardTagKey::TrackTitle => Ok(TagType::Title),
            StandardTagKey::TrackNumber => Ok(TagType::Track),
            // StandardTagKey::Name => Ok(TagType::Name),
            StandardTagKey::Genre => Ok(TagType::Genre),
            StandardTagKey::Date => Ok(TagType::Date),
            StandardTagKey::OriginalDate => Ok(TagType::OriginalDate),
            StandardTagKey::Composer => Ok(TagType::Composer),
            StandardTagKey::Performer => Ok(TagType::Performer),
            StandardTagKey::Conductor => Ok(TagType::Conductor),
            // StandardTagKey::Work => Ok(TagType::Work),
            StandardTagKey::ContentGroup => Ok(TagType::Grouping),
            StandardTagKey::DiscNumber => Ok(TagType::Disc),
            StandardTagKey::Label => Ok(TagType::Label),
            StandardTagKey::MusicBrainzArtistId => Ok(TagType::MUSICBRAINZ_ARTISTID),
            StandardTagKey::MusicBrainzAlbumId => Ok(TagType::MUSICBRAINZ_ALBUMID),
            StandardTagKey::MusicBrainzAlbumArtistId => Ok(TagType::MUSICBRAINZ_ALBUMARTISTID),
            StandardTagKey::MusicBrainzTrackId => Ok(TagType::MUSICBRAINZ_TRACKID),
            StandardTagKey::MusicBrainzReleaseTrackId => Ok(TagType::MUSICBRAINZ_RELEASETRACKID),
            StandardTagKey::MusicBrainzWorkId => Ok(TagType::MUSICBRAINZ_WORKID),
            _ => Err("No matching MPD tag for this symphonia standard Tag"),
        }
    }
}

struct Output {
    pub outputid: u32,
    pub outputname: String,
    pub plugin: String,
    pub outputenabled: bool,
    pub attributes: Vec<(String, String)>,
}

impl Display for Output {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let res = write!(
            f,
            "outputid: {}
outputname: {}
plugin: {}
outputenabled: {}",
            self.outputid,
            self.outputname,
            self.plugin,
            if self.outputenabled { "1" } else { "0" }
        );
        self.attributes
            .iter()
            .fold(res, |_, (k, v)| write!(f, "\nattribute: {k}={v}"))
    }
}

impl Default for Output {
    fn default() -> Self {
        Self {
            outputid: 0,
            outputname: "default".to_string(),
            plugin: "default".to_string(),
            outputenabled: true,
            attributes: vec![],
        }
    }
}

#[derive(Deserialize, Serialize, Debug, Clone, Default)]
struct Stats {
    artists: u64,     // number of artists
    albums: u64,      // number of albums
    songs: u64,       // number of songs
    uptime: u64,      // daemon uptime in seconds
    db_playtime: u64, //sum of all song times in the database in seconds
    db_update: u64,   //last db update in UNIX time (seconds since 1970-01-01 UTC)
    playtime: u64,    //time length of music played
}

impl Display for Stats {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "artists: {}
albums: {}
songs: {}
uptime: {}
db_playtime: {}
db_update: {}
playtime: {}",
            self.artists,
            self.albums,
            self.songs,
            self.uptime,
            self.db_playtime,
            self.db_update,
            self.playtime
        )
    }
}

fn meta_to_tags() {}

// TODO add flag to skip, based on modified date
fn scan_file(path: &Path, song_path: String) -> Option<Song> {
    let metadata = std::fs::metadata(path).ok()?;
    let last_modified: u64 = metadata
        .modified()
        .ok()
        .and_then(|s| {
            s.duration_since(std::time::UNIX_EPOCH)
                .map(|d| d.as_secs())
                .ok()
        })
        .unwrap_or(0);

    let src = std::fs::File::open(path).expect("failed to open media");
    let mss = MediaSourceStream::new(Box::new(src), Default::default());
    let mut hint = Hint::new();
    if let Some(ext) = path.extension() {
        hint.with_extension(ext.to_str().unwrap());
    }
    let meta_opts: MetadataOptions = Default::default();
    let fmt_opts: FormatOptions = Default::default();
    let mut probed = symphonia::default::get_probe()
        .format(&hint, mss, &fmt_opts, &meta_opts)
        .ok()?;

    if let Some(meta) = probed.metadata.get().or(Some(probed.format.metadata())) {
        // println!("{:#?}", meta.current()?.tags());
        let tags: HashMap<TagType, String> = meta
            .current()?
            .tags()
            .iter()
            .filter_map(|t| Some((TagType::try_from(t.std_key?).ok()?, format!("{}", t.value))))
            .collect();
        // println!("{:#?} {:#?}", path, tags);

        let duration = probed
            .format
            .default_track()
            .and_then(|t| {
                Some(
                    t.codec_params
                        .time_base?
                        .calc_time(t.codec_params.n_frames?),
                )
            })
            .map(|tb| tb.seconds)
            .unwrap_or(0) as f32;

        Some(Song {
            // song_id: 1,
            last_modified,
            playlist_id: None,
            song_path,
            duration,
            tags,
        })
    } else {
        None
    }
}

fn remove_from_playlist(s: &mut State, pos: usize) {
    s.playlist.remove(pos);
    s.status.playlist_update_counter += 1;
    s.status.playlistlength -= 1;
}

fn add_to_playlist(state: &Arc<RwLock<State>>, mut songs: Vec<Song>) -> Vec<Song> {
    let mut s = state.write().unwrap();
    let mut i = s.status.playlist_update_counter;
    for song in &mut songs {
        i += 1;
        song.playlist_id = Some(i);
        s.playlist.push(song.clone());
    }
    s.status.playlist_update_counter = i;
    s.status.playlistlength += songs.len();
    songs
}

async fn playback_loop(
    audio_handle: rodio::OutputStreamHandle,
    sender: mpsc::UnboundedSender<Action>,
    mut receiver: mpsc::UnboundedReceiver<Action>,
    broadcaster: broadcast::Sender<IdleAction>,
    state: Arc<RwLock<State>>,
) {
    let sink = Sink::try_new(&audio_handle).unwrap();

    while let Some(msg) = receiver.recv().await {
        println!("got ACTION {:#?}", msg);

        match msg {
            Action::PlayAtList(list_pos) => {
                let mut s = state.write().unwrap();
                if let Some(song) = s.playlist.get(list_pos) {
                    let song = song.clone();
                    if !sink.empty() {
                        sink.stop();
                    }
                    append_to_sink(&mut s, &sink, sender.clone(), list_pos, song);
                    let _ = broadcaster.send(IdleAction::Player);
                }
            }
            Action::PlayForId(id) => {
                let mut s = state.write().unwrap();
                if let Some(song) = s.playlist.iter().find(|s| s.playlist_id == Some(id)) {
                    let song = song.clone();
                    let playlist_id = s
                        .playlist
                        .iter()
                        .enumerate()
                        .position(|(_, s)| s.playlist_id == Some(id))
                        .unwrap_or(0);

                    if !sink.empty() {
                        sink.stop();
                    }
                    append_to_sink(&mut s, &sink, sender.clone(), playlist_id, song);
                    let _ = broadcaster.send(IdleAction::Player);
                }
            }
            Action::Stop => {
                sink.stop();
                let _ = broadcaster.send(IdleAction::Player);
            }
            Action::Next => {
                sink.skip_one();
                let _ = broadcaster.send(IdleAction::Player);
            }
            Action::Previous => {}
            Action::Volume(volume) => {
                sink.set_volume(volume);
                state.write().unwrap().status.volume = (volume * 100.0) as u16;
                let _ = broadcaster.send(IdleAction::Mixer);
            }
            // TODO reuse pause
            Action::TogglePause => {
                if sink.is_paused() {
                    sink.play();
                    let mut s = state.write().unwrap();
                    s.status.state = Playback::Playing;
                    s.status.last_play_time = Some(std::time::Instant::now());
                    // state.get_mut().unwrap().status.state = Playback::Playing;
                } else {
                    sink.pause();
                    let mut s = state.write().unwrap();
                    s.status.state = Playback::Paused;
                    s.status.update_elapsed();
                    s.status.last_play_time = None;
                    // state.get_mut().unwrap().status.state = Playback::Paused;
                }
                let _ = broadcaster.send(IdleAction::Player);
            }
            Action::Pause(p) => {
                if p {
                    sink.pause();
                    let mut s = state.write().unwrap();
                    s.status.state = Playback::Paused;
                    s.status.update_elapsed();
                    s.status.last_play_time = None;
                } else {
                    sink.play();
                    let mut s = state.write().unwrap();
                    s.status.state = Playback::Playing;
                    s.status.last_play_time = Some(std::time::Instant::now());
                }
                let _ = broadcaster.send(IdleAction::Player);
            }
            Action::TrackEnded => {
                let mut s = state.write().unwrap();

                // TODO make sure this adjusts when songs get removed
                if ((s.status.song_playlist_pos + 1) as usize) >= s.playlist.len() {
                    if !s.playlist.is_empty() && s.status.repeat {
                        let next_track = if s.status.single == SingleMode::Off {
                            0
                        } else {
                            s.status.song_playlist_pos as usize
                        };
                        sender.send(Action::PlayAtList(next_track)).unwrap();
                    } else {
                        s.status.song_playlist_pos = 0;
                        s.status.state = Playback::Stopped;
                        s.status.last_play_time = None;
                        s.status.song = None;
                    }
                } else {
                    sender
                        .send(Action::PlayAtList(s.status.song_playlist_pos as usize + 1))
                        .unwrap();
                }
                let _ = broadcaster.send(IdleAction::Player);
            }

            Action::BroadcastIdle(idle_action) => {
                let _ = broadcaster.send(idle_action);
            }
        }
    }
}

fn append_to_sink(
    s: &mut State,
    sink: &Sink,
    sender: mpsc::UnboundedSender<Action>,
    playlist_pos: usize,
    song: Song,
) {
    use std::fs::File;
    use std::io::BufReader;

    let root = s.config.music_dir.clone();
    let path = root.join(&song.song_path);
    let file = BufReader::new(File::open(path.clone()).unwrap());
    let source = Decoder::new(file).unwrap();

    let (queue_in, queue_out) = rodio::queue::queue(false);
    let receiver = queue_in.append_with_signal(source);

    tokio::task::spawn_blocking(move || {
        if receiver.recv().is_ok() {
            sender.send(Action::TrackEnded).unwrap();
            println!("TRACK OVER, APPEND NEXT");
        } else {
            eprintln!("TRACK OVER, WITH ERROR");
        }
    });
    sink.append(queue_out);
    if sink.is_paused() {
        sink.play();
    }

    s.status.song_playlist_pos = playlist_pos as u32;
    s.status.state = Playback::Playing;
    s.status.last_play_time = Some(std::time::Instant::now());
    s.status.song = Some(song.clone());
}

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let config = ConfigFile::read().unwrap_or_default();
    let db = Db::read().unwrap_or_else(|| {
        let mut db = Db::default();
        scan_db_init(&mut db, &config.music_dir, Path::new(""));
        db.update_stats();
        db.write();
        db
    });

    let s = State {
        config,
        db,
        tagtypes: all_tagtypes(),
        ..Default::default()
    };
    let listener = TcpListener::bind(&s.config.net_address).await?;
    let state = Arc::new(RwLock::new(s));
    let state2 = state.clone();

    // This thread setup is a mess.
    let (sender, receiver) = mpsc::unbounded_channel::<Action>();
    let (_stream, stream_handle) = rodio::OutputStream::try_default().unwrap();
    let (broadcaster, _) = broadcast::channel(16);

    let sender2 = sender.clone();
    let broadcaster2 = broadcaster.clone();
    tokio::spawn(async move {
        playback_loop(stream_handle, sender2, receiver, broadcaster2, state).await;
    });

    if let Err(e) = sender.send(Action::Volume(0.05)) {
        eprintln!("Failed to send volume init {e}");
    }

    while let Ok((stream, _)) = listener.accept().await {
        let sender = sender.clone();
        let state2 = state2.clone();
        let idle2 = broadcaster.subscribe();
        tokio::spawn(async {
            if let Err(e) = handle_client(sender, idle2, stream, state2).await {
                eprintln!("ERROR {e}");
            }
        });
    }

    Ok(())
}