use std::ops::AddAssign;

use crate::{load::ToFromBytes, server::Action};

use super::{database::Database, SongId};

#[derive(Clone, Debug, PartialEq)]
pub struct Queue {
    enabled: bool,
    content: QueueContent,
}
#[derive(Clone, Debug, PartialEq)]
pub enum QueueContent {
    Song(SongId),
    Folder(QueueFolder),
    Loop(usize, usize, Box<Queue>),
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct QueueFolder {
    pub index: usize,
    pub content: Vec<Queue>,
    pub name: String,
    pub order: Option<Vec<usize>>,
}

impl Queue {
    pub fn enabled(&self) -> bool {
        self.enabled
    }
    pub fn content(&self) -> &QueueContent {
        &self.content
    }
    pub fn content_mut(&mut self) -> &mut QueueContent {
        &mut self.content
    }

    pub fn add_to_end(&mut self, v: Vec<Self>, skip_init: bool) -> Option<usize> {
        match &mut self.content {
            QueueContent::Song(_) => None,
            QueueContent::Folder(folder) => folder.add_to_end(v, skip_init),
            QueueContent::Loop(..) => None,
        }
    }
    pub fn is_current(&self, index: &[usize]) -> bool {
        if index.is_empty() {
            return true;
        }
        match self.content() {
            QueueContent::Song(_) => true,
            QueueContent::Folder(folder) => {
                folder.index == index[0]
                    && folder
                        .get_current_immut()
                        .is_some_and(|c| c.is_current(&index[1..]))
            }
            QueueContent::Loop(_, _, inner) => index[0] == 0 && inner.is_current(&index[1..]),
        }
    }
    pub fn insert(&mut self, v: Vec<Self>, index: usize, skip_init: bool) -> bool {
        match &mut self.content {
            QueueContent::Song(_) => false,
            QueueContent::Folder(folder) => folder.insert(v, index, skip_init),
            QueueContent::Loop(..) => false,
        }
    }

    pub fn is_empty(&self) -> bool {
        if !self.enabled {
            return true;
        }
        match &self.content {
            QueueContent::Song(_) => false,
            QueueContent::Folder(folder) => folder.content.iter().all(|v| v.is_empty()),
            QueueContent::Loop(_total, _done, inner) => inner.is_empty(),
        }
    }
    /// returns true if there is at most one song in the queue
    pub fn is_almost_empty(&self) -> bool {
        self.is_almost_empty_int() < 2
    }
    fn is_almost_empty_int(&self) -> u8 {
        if !self.enabled {
            return 0;
        }
        match &self.content {
            QueueContent::Song(_) => 1,
            QueueContent::Folder(folder) => {
                let mut o = 0;
                for v in folder.content.iter() {
                    o += v.is_almost_empty_int();
                    if o >= 2 {
                        return 2;
                    }
                }
                o
            }
            QueueContent::Loop(_total, _done, inner) => inner.is_almost_empty_int(),
        }
    }
    pub fn len(&self) -> usize {
        if !self.enabled {
            return 0;
        }
        match &self.content {
            QueueContent::Song(_) => 1,
            QueueContent::Folder(folder) => folder.len(),
            QueueContent::Loop(total, _done, inner) => {
                if *total == 0 {
                    inner.len()
                } else {
                    total.saturating_mul(inner.len())
                }
            }
        }
    }
    pub fn duration_total(&self, db: &Database) -> QueueDuration {
        let mut dur = QueueDuration::new_total();
        self.add_duration(&mut dur, db);
        dur
    }
    // remaining time, including current song
    pub fn duration_remaining(&self, db: &Database) -> QueueDuration {
        let mut dur = QueueDuration::new_remaining();
        self.add_duration(&mut dur, db);
        dur
    }
    pub fn add_duration(&self, dur: &mut QueueDuration, db: &Database) {
        if self.enabled {
            match &self.content {
                QueueContent::Song(v) => {
                    dur.millis += db.get_song(v).map(|s| s.duration_millis).unwrap_or(0)
                }
                QueueContent::Folder(QueueFolder {
                    index,
                    content,
                    name: _,
                    order: _,
                }) => {
                    for (i, inner) in content.iter().enumerate() {
                        if dur.include_past || i >= *index {
                            inner.add_duration(dur, db);
                        }
                    }
                }
                QueueContent::Loop(total, done, inner) => {
                    if *total == 0 {
                        dur.infinite = true;
                    } else if dur.include_past {
                        // <total duration> * <total iterations>
                        let dt = inner.duration_total(db);
                        for _ in 0..*total {
                            *dur += dt;
                        }
                    } else {
                        // <remaining duration> + <total duration> * <remaining iterations>
                        inner.add_duration(dur, db);
                        let dt = inner.duration_total(db);
                        for _ in 0..(total.saturating_sub(*done + 1)) {
                            *dur += dt;
                        }
                    }
                }
            }
        }
    }

    /// recursively descends the queue until the current active element is found, then returns it.
    pub fn get_current(&self) -> Option<&Self> {
        match &self.content {
            QueueContent::Song(_) => Some(self),
            QueueContent::Folder(folder) => folder.get_current_immut()?.get_current(),
            QueueContent::Loop(_, _, inner) => inner.get_current(),
        }
    }
    pub fn get_current_song(&self) -> Option<&SongId> {
        if let QueueContent::Song(id) = self.get_current()?.content() {
            Some(id)
        } else {
            None
        }
    }
    pub fn get_next_song(&self) -> Option<&SongId> {
        if let QueueContent::Song(id) = self.get_next()?.content() {
            Some(id)
        } else {
            None
        }
    }
    pub fn get_next(&self) -> Option<&Self> {
        match &self.content {
            QueueContent::Song(_) => None,
            QueueContent::Folder(folder) => folder.get_next(),
            QueueContent::Loop(total, current, inner) => {
                if let Some(v) = inner.get_next() {
                    Some(v)
                } else if *total == 0 || current < total {
                    inner.get_first()
                } else {
                    None
                }
            }
        }
    }
    pub fn get_first(&self) -> Option<&Self> {
        match &self.content {
            QueueContent::Song(..) => Some(self),
            QueueContent::Folder(folder) => folder.get_first(),
            QueueContent::Loop(_, _, q) => q.get_first(),
        }
    }

    pub fn advance_index_db(db: &mut Database) -> bool {
        let mut actions = Vec::new();
        let o = db.queue.advance_index_inner(&mut Vec::new(), &mut actions);
        for action in actions {
            db.apply_action_unchecked_seq(action, None);
        }
        o
    }
    pub fn init(&mut self) {
        match &mut self.content {
            QueueContent::Song(..) => {}
            QueueContent::Folder(folder) => {
                folder.index = 0;
                for v in &mut folder.content {
                    v.init();
                }
            }
            QueueContent::Loop(_, _, inner) => inner.init(),
        }
    }
    pub fn done(&mut self, path: &Vec<usize>, actions: &mut Vec<Action>) {
        match &mut self.content {
            QueueContent::Song(..) => {}
            QueueContent::Folder(folder) => {
                if folder.order.is_some() {
                    actions.push(Action::QueueShuffle(path.clone(), 0));
                }
            }
            QueueContent::Loop(_, _, _) => {}
        }
    }
    pub fn advance_index_inner(
        &mut self,
        path: &mut Vec<usize>,
        actions: &mut Vec<Action>,
    ) -> bool {
        match &mut self.content {
            QueueContent::Song(_) => {
                self.done(path, actions);
                false
            }
            QueueContent::Folder(folder) => {
                if folder.advance_index_inner(path, actions) {
                    true
                } else {
                    self.done(path, actions);
                    false
                }
            }
            QueueContent::Loop(total, current, inner) => {
                path.push(0);
                if inner.advance_index_inner(path, actions) {
                    path.pop();
                    true
                } else {
                    inner.done(path, actions);
                    path.pop();
                    *current += 1;
                    if *total == 0 || *current < *total {
                        inner.init();
                        true
                    } else {
                        *current = 0;
                        self.done(path, actions);
                        false
                    }
                }
            }
        }
    }

    pub fn set_index_db(db: &mut Database, index: &[usize]) {
        db.queue.reset_index();
        db.queue.set_index_inner(index, 0, vec![], false);
    }
    pub fn set_index_inner(
        &mut self,
        index: &[usize],
        depth: usize,
        mut build_index: Vec<usize>,
        keep_child_indices: bool,
    ) {
        let i = if let Some(i) = index.get(depth) {
            *i
        } else {
            return;
        };
        build_index.push(i);
        match &mut self.content {
            QueueContent::Song(_) => {}
            QueueContent::Folder(folder) => {
                folder.index = i;
                if let Some(c) = folder.get_current_mut() {
                    if !keep_child_indices {
                        c.init();
                    }
                    c.set_index_inner(index, depth + 1, build_index, keep_child_indices);
                }
            }
            QueueContent::Loop(_, _, inner) => {
                if !keep_child_indices {
                    inner.init();
                }
                inner.set_index_inner(index, depth + 1, build_index, keep_child_indices)
            }
        }
    }
    pub fn reset_index(&mut self) {
        match self.content_mut() {
            QueueContent::Song(_) => {}
            QueueContent::Folder(folder) => {
                folder.index = 0;
                for v in &mut folder.content {
                    v.reset_index();
                }
            }
            QueueContent::Loop(_, done, i) => {
                *done = 0;
                i.reset_index();
            }
        }
    }

    pub fn get_item_at_index(&self, index: &[usize], depth: usize) -> Option<&Self> {
        if let Some(i) = index.get(depth) {
            match &self.content {
                QueueContent::Song(_) => None,
                QueueContent::Folder(folder) => {
                    if let Some(v) = folder.get_at(*i) {
                        v.get_item_at_index(index, depth + 1)
                    } else {
                        None
                    }
                }
                QueueContent::Loop(_, _, inner) => inner.get_item_at_index(index, depth + 1),
            }
        } else {
            Some(self)
        }
    }
    pub fn get_item_at_index_mut(&mut self, index: &[usize], depth: usize) -> Option<&mut Self> {
        if let Some(i) = index.get(depth) {
            match &mut self.content {
                QueueContent::Song(_) => None,
                QueueContent::Folder(folder) => {
                    if let Some(v) = folder.get_mut_at(*i) {
                        v.get_item_at_index_mut(index, depth + 1)
                    } else {
                        None
                    }
                }
                QueueContent::Loop(_, _, inner) => inner.get_item_at_index_mut(index, depth + 1),
            }
        } else {
            Some(self)
        }
    }

    pub fn remove_by_index(&mut self, index: &[usize], depth: usize) -> Option<Self> {
        if let Some(i) = index.get(depth) {
            match &mut self.content {
                QueueContent::Song(_) => None,
                QueueContent::Folder(folder) => {
                    if depth + 1 < index.len() {
                        if let Some(v) = folder.get_mut_at(*i) {
                            v.remove_by_index(index, depth + 1)
                        } else {
                            None
                        }
                    } else {
                        if *i < folder.content.len() {
                            // if current playback is past this point,
                            // reduce the index by 1 so that it still points to the same element
                            if folder.index > *i {
                                folder.index -= 1;
                            }
                            let idx = if let Some(order) = &mut folder.order {
                                let idx = order.remove(*i);
                                // compensate for removal of element from .content
                                for o in order {
                                    if *o > idx {
                                        *o -= 1;
                                    }
                                }
                                idx
                            } else {
                                *i
                            };
                            Some(folder.content.remove(idx))
                        } else {
                            None
                        }
                    }
                }
                QueueContent::Loop(_, _, inner) => {
                    if depth + 1 < index.len() {
                        inner.remove_by_index(index, depth + 1)
                    } else {
                        None
                    }
                }
            }
        } else {
            None
        }
    }
}

impl QueueFolder {
    pub fn iter(&self) -> QueueFolderIter<'_> {
        QueueFolderIter {
            folder: self,
            index: 0,
        }
    }
    pub fn add_to_end(&mut self, v: Vec<Queue>, skip_init: bool) -> Option<usize> {
        let add_len = v.len();
        let len = self.content.len();
        for mut v in v.into_iter() {
            if !skip_init {
                v.init();
            }
            self.content.push(v);
        }
        if let Some(order) = &mut self.order {
            for i in 0..add_len {
                order.push(len + i);
            }
        }
        Some(len)
    }
    pub fn insert(&mut self, v: Vec<Queue>, index: usize, skip_init: bool) -> bool {
        if index <= self.content.len() {
            if self.index >= index {
                self.index += v.len();
            }
            fn insert_multiple<T>(index: usize, vec: &mut Vec<T>, v: impl IntoIterator<Item = T>) {
                // remove the elements starting at the insertion point
                let end = vec.split_off(index);
                // insert new elements
                for v in v {
                    vec.push(v);
                }
                // re-add previously removed elements
                vec.extend(end);
            }
            let mapfunc = |mut v: Queue| {
                if !skip_init {
                    v.init();
                }
                v
            };
            if let Some(order) = &mut self.order {
                insert_multiple(index, order, (0..v.len()).map(|i| self.content.len() + i));
                self.content.extend(v.into_iter().map(mapfunc));
            } else {
                insert_multiple(index, &mut self.content, v.into_iter().map(mapfunc));
            }
            true
        } else {
            false
        }
    }
    pub fn len(&self) -> usize {
        self.content.iter().map(|v| v.len()).sum()
    }
    pub fn get_at(&self, mut i: usize) -> Option<&Queue> {
        if let Some(order) = &self.order {
            i = *order.get(i)?;
        }
        self.content.get(i)
    }
    pub fn get_mut_at(&mut self, mut i: usize) -> Option<&mut Queue> {
        if let Some(order) = &self.order {
            i = *order.get(i)?;
        }
        self.content.get_mut(i)
    }
    pub fn get_current_immut(&self) -> Option<&Queue> {
        self.get_at(self.index)
    }
    pub fn get_current_mut(&mut self) -> Option<&mut Queue> {
        self.get_mut_at(self.index)
    }
    pub fn get_next(&self) -> Option<&Queue> {
        if let Some(v) = self.get_current_immut() {
            if let Some(v) = v.get_next() {
                Some(v)
            } else {
                if let Some(v) = self.get_at(self.index + 1) {
                    v.get_current()
                } else {
                    None
                }
            }
        } else {
            None
        }
    }
    pub fn get_first(&self) -> Option<&Queue> {
        if let Some(order) = &self.order {
            self.content.get(*order.first()?)
        } else {
            self.content.first()
        }
    }
    pub fn advance_index_inner(
        &mut self,
        path: &mut Vec<usize>,
        actions: &mut Vec<Action>,
    ) -> bool {
        let index = self.index;
        if let Some(c) = self.get_current_mut() {
            path.push(index);
            if c.advance_index_inner(path, actions) {
                path.pop();
                // inner value could advance index, do nothing.
                true
            } else {
                path.pop();
                loop {
                    if self.index + 1 < self.content.len() {
                        // can advance
                        self.index += 1;
                        if self.content[self.index].enabled {
                            self.content[self.index].init();
                            break true;
                        }
                    } else {
                        // can't advance: index would be out of bounds
                        self.index = 0;
                        break false;
                    }
                }
            }
        } else {
            self.index = 0;
            false
        }
    }

    pub fn move_elem(&mut self, index_from: usize, index_to: usize) -> bool {
        fn vec_move<T>(vec: &mut Vec<T>, from: usize, to: usize) -> bool {
            if from < vec.len() && to < vec.len() {
                if from == to {
                    return true;
                }
                let elem = vec.remove(from);
                vec.insert(to, elem);
                true
            } else {
                false
            }
        }
        if self.index == index_from {
            self.index = index_to;
        } else if index_from < self.index && self.index <= index_to {
            self.index -= 1;
        } else if index_to <= self.index && self.index < index_from {
            self.index += 1;
        }
        if let Some(order) = &mut self.order {
            vec_move(order, index_from, index_to)
        } else {
            vec_move(&mut self.content, index_from, index_to)
        }
    }
}
pub struct QueueFolderIter<'a> {
    folder: &'a QueueFolder,
    index: usize,
}
impl<'a> Iterator for QueueFolderIter<'a> {
    type Item = &'a Queue;
    fn next(&mut self) -> Option<Self::Item> {
        if let Some(v) = self.folder.get_at(self.index) {
            self.index += 1;
            Some(v)
        } else {
            None
        }
    }
}

impl From<QueueContent> for Queue {
    fn from(value: QueueContent) -> Self {
        Self {
            enabled: true,
            content: value,
        }
    }
}

impl ToFromBytes for Queue {
    fn to_bytes<T>(&self, s: &mut T) -> Result<(), std::io::Error>
    where
        T: std::io::Write,
    {
        s.write_all(&[if self.enabled { 0b11111111 } else { 0b00000000 }])?;
        self.content.to_bytes(s)?;
        Ok(())
    }
    fn from_bytes<T>(s: &mut T) -> Result<Self, std::io::Error>
    where
        T: std::io::Read,
    {
        let mut enabled = [0];
        s.read_exact(&mut enabled)?;
        Ok(Self {
            enabled: enabled[0].count_ones() >= 4,
            content: ToFromBytes::from_bytes(s)?,
        })
    }
}

impl ToFromBytes for QueueContent {
    fn to_bytes<T>(&self, s: &mut T) -> Result<(), std::io::Error>
    where
        T: std::io::Write,
    {
        match self {
            Self::Song(id) => {
                s.write_all(&[0b11111111])?;
                id.to_bytes(s)?;
            }
            Self::Folder(folder) => {
                s.write_all(&[0b00000000])?;
                ToFromBytes::to_bytes(folder, s)?;
            }
            Self::Loop(total, current, inner) => {
                s.write_all(&[0b11000000])?;
                total.to_bytes(s)?;
                current.to_bytes(s)?;
                inner.to_bytes(s)?;
            }
        }
        Ok(())
    }
    fn from_bytes<T>(s: &mut T) -> Result<Self, std::io::Error>
    where
        T: std::io::Read,
    {
        let mut switch_on = [0];
        s.read_exact(&mut switch_on)?;
        Ok(match switch_on[0] {
            0b11111111 => Self::Song(ToFromBytes::from_bytes(s)?),
            0b00000000 => Self::Folder(ToFromBytes::from_bytes(s)?),
            0b11000000 => Self::Loop(
                ToFromBytes::from_bytes(s)?,
                ToFromBytes::from_bytes(s)?,
                Box::new(ToFromBytes::from_bytes(s)?),
            ),
            _ => Self::Folder(QueueFolder {
                index: 0,
                content: vec![],
                name: "<invalid byte received>".to_string(),
                order: None,
            }),
        })
    }
}
impl ToFromBytes for QueueFolder {
    fn to_bytes<T>(&self, s: &mut T) -> Result<(), std::io::Error>
    where
        T: std::io::prelude::Write,
    {
        ToFromBytes::to_bytes(&self.index, s)?;
        ToFromBytes::to_bytes(&self.content, s)?;
        ToFromBytes::to_bytes(&self.name, s)?;
        ToFromBytes::to_bytes(&self.order, s)?;
        Ok(())
    }
    fn from_bytes<T>(s: &mut T) -> Result<Self, std::io::Error>
    where
        T: std::io::prelude::Read,
    {
        let v = Self {
            index: ToFromBytes::from_bytes(s)?,
            content: ToFromBytes::from_bytes(s)?,
            name: ToFromBytes::from_bytes(s)?,
            order: ToFromBytes::from_bytes(s)?,
        };
        Ok(v)
    }
}

#[derive(Clone, Copy)]
pub struct QueueDuration {
    pub include_past: bool,
    pub infinite: bool,
    /// number of milliseconds (that we know of)
    pub millis: u64,
    /// number of milliseconds from the <random> element - only accurate the first time it is reached in queue.
    pub random_known_millis: u64,
    /// number of <random> elements, which could have pretty much any duration.
    pub random_counter: u64,
}
impl QueueDuration {
    fn new_total() -> Self {
        Self::new(true)
    }
    fn new_remaining() -> Self {
        Self::new(false)
    }
    fn new(include_past: bool) -> Self {
        QueueDuration {
            include_past,
            infinite: false,
            millis: 0,
            random_known_millis: 0,
            random_counter: 0,
        }
    }
}
impl AddAssign<QueueDuration> for QueueDuration {
    fn add_assign(&mut self, rhs: QueueDuration) {
        if rhs.infinite {
            self.infinite = true;
        }
        self.millis += rhs.millis;
        self.random_known_millis += rhs.random_known_millis;
        self.random_counter += rhs.random_counter;
    }
}
