use std::time::Duration;

use async_trait::async_trait;
use bytesize::ByteSize;
use hex_color::HexColor;
use serde_derive::{Deserialize, Serialize};
use strum::EnumIter;
use sysinfo::SystemExt;

use crate::context::{BarEvent, BarItem, Context, StopAction};
use crate::error::Result;
use crate::i3::{I3Button, I3Item, I3Markup};
use crate::theme::Theme;
use crate::util::format::{float, FloatFormat};
use crate::util::EnumCycle;

#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, PartialEq, EnumIter)]
#[serde(rename_all = "snake_case")]
pub enum MemDisplay {
    #[default]
    Bytes,
    Percentage,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct Mem {
    #[serde(with = "crate::human_time")]
    interval: Duration,
    #[serde(flatten)]
    float_fmt: FloatFormat,
    #[serde(default)]
    display: MemDisplay,
}

impl Mem {
    fn get_color(theme: &Theme, used_pct: f64) -> Option<HexColor> {
        match used_pct as u64 {
            80..=100 => Some(theme.red),
            60..=79 => Some(theme.orange),
            40..=59 => Some(theme.yellow),
            _ => None,
        }
    }
}

#[async_trait(?Send)]
impl BarItem for Mem {
    async fn start(&self, mut ctx: Context) -> Result<StopAction> {
        let mut total = None;
        let mut display = EnumCycle::new_at(self.display)?;
        loop {
            let (available, total) = {
                ctx.state.sys.refresh_memory();
                (
                    ctx.state.sys.available_memory(),
                    *total.get_or_insert_with(|| ctx.state.sys.total_memory()),
                )
            };

            let used_pct = ((total - available) as f64 / total as f64) * 100.0;
            let s = match *display.current() {
                MemDisplay::Bytes => ByteSize(available).to_string_as(true),
                MemDisplay::Percentage => format!("{}%", float(used_pct, &self.float_fmt)),
            };

            let mut item = I3Item::new(format!(" {}", s)).markup(I3Markup::Pango);
            if let Some(fg) = Self::get_color(&ctx.config.theme, used_pct) {
                item = item.color(fg);
            }

            ctx.update_item(item).await?;
            ctx.delay_with_event_handler(self.interval, |ev| {
                if let BarEvent::Click(c) = ev {
                    if let I3Button::Left = c.button {
                        display.next();
                    }
                }

                async {}
            })
            .await;
        }
    }
}
