use std::borrow::Cow;
use std::fmt;
use std::pin::Pin;

use anyhow::Result;
use tokio::io::{AsyncWrite, AsyncWriteExt, BufWriter};

use crate::bilibili::danmaku::canvas::CanvasConfig;
use crate::bilibili::danmaku::{DanmakuOption, DrawEffect, Drawable};

struct TimePoint {
    t: f64,
}
impl fmt::Display for TimePoint {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let secs = self.t.floor() as u32;
        let hour = secs / 3600;
        let minutes = (secs % 3600) / 60;

        let left = self.t - (hour * 3600) as f64 - (minutes * 60) as f64;

        write!(f, "{hour}:{minutes:02}:{left:05.2}")
    }
}

struct AssEffect {
    effect: DrawEffect,
}
impl fmt::Display for AssEffect {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self.effect {
            DrawEffect::Move { start, end } => {
                let (x0, y0) = start;
                let (x1, y1) = end;
                write!(f, "\\move({x0}, {y0}, {x1}, {y1})")
            }
        }
    }
}

impl DanmakuOption {
    pub fn ass_styles(&self) -> Vec<String> {
        vec![
            // Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, \
            // Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, \
            // Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
            format!(
                "Style: Float,{font},{font_size},&H{a:02x}FFFFFF,&H00FFFFFF,&H{a:02x}000000,&H00000000,\
                {bold}, 0, 0, 0, 100, 100, 0.00, 0.00, 1, \
                {outline}, 0, 7, 0, 0, 0, 1",
                a = self.opacity,
                font = self.font,
                font_size = self.font_size,
                bold = self.bold as u8,
                outline = self.outline,
            ),
            format!(
                "Style: Bottom,{font},{font_size},&H{a:02x}FFFFFF,&H00FFFFFF,&H{a:02x}000000,&H00000000,\
                {bold}, 0, 0, 0, 100, 100, 0.00, 0.00, 1, \
                {outline}, 0, 7, 0, 0, 0, 1",
                a = self.opacity,
                font = self.font,
                font_size = self.font_size,
                bold = self.bold as u8,
                outline = self.outline,
            ),
            format!(
                "Style: Top,{font},{font_size},&H{a:02x}FFFFFF,&H00FFFFFF,&H{a:02x}000000,&H00000000,\
                {bold}, 0, 0, 0, 100, 100, 0.00, 0.00, 1, \
                {outline}, 0, 7, 0, 0, 0, 1",
                a = self.opacity,
                font = self.font,
                font_size = self.font_size,
                bold = self.bold as u8,
                outline = self.outline,
            ),
        ]
    }
}

struct CanvasStyles(Vec<String>);
impl fmt::Display for CanvasStyles {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        for style in &self.0 {
            writeln!(f, "{}", style)?;
        }
        Ok(())
    }
}

pub struct AssWriter<'a, W: AsyncWrite> {
    f: Pin<Box<BufWriter<W>>>,
    title: String,
    canvas_config: CanvasConfig<'a>,
}

impl<'a, W: AsyncWrite> AssWriter<'a, W> {
    pub fn new(f: W, title: String, canvas_config: CanvasConfig<'a>) -> Self {
        AssWriter {
            // 对于 HDD、docker 之类的场景，磁盘 IO 是非常大的瓶颈。使用大缓存
            f: Box::pin(BufWriter::with_capacity(10 << 20, f)),
            title,
            canvas_config,
        }
    }

    pub async fn construct(f: W, title: String, canvas_config: CanvasConfig<'a>) -> Result<Self> {
        let mut res = Self::new(f, title, canvas_config);
        res.init().await?;
        Ok(res)
    }

    pub async fn init(&mut self) -> Result<()> {
        self.f
            .write_all(
                format!(
                    "\
            [Script Info]\n\
            ; Script generated by danmu2ass\n\
            Title: {title}\n\
            Script Updated By: danmu2ass (https://github.com/gwy15/danmu2ass)\n\
            ScriptType: v4.00+\n\
            PlayResX: {width}\n\
            PlayResY: {height}\n\
            Aspect Ratio: {width}:{height}\n\
            Collisions: Normal\n\
            WrapStyle: 2\n\
            ScaledBorderAndShadow: yes\n\
            YCbCr Matrix: TV.601\n\
            \n\
            \n\
            [V4+ Styles]\n\
            Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, \
                    Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, \
                    Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\n\
            {styles}\
            \n\
            [Events]\n\
            Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n\
            ",
                    title = self.title,
                    width = self.canvas_config.width,
                    height = self.canvas_config.height,
                    styles = CanvasStyles(self.canvas_config.danmaku_option.ass_styles()),
                )
                .into_bytes()
                .as_slice(),
            )
            .await?;
        Ok(())
    }

    pub async fn write(&mut self, drawable: Drawable) -> Result<()> {
        self.f
            .write_all(
                format!(
                    // Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
                    "Dialogue: 2,{start},{end},{style},,0,0,0,,{{{effect}\\c&H{b:02x}{g:02x}{r:02x}&}}{text}\n",
                    start = TimePoint {
                        t: drawable.danmu.timeline_s
                    },
                    end = TimePoint {
                        t: drawable.danmu.timeline_s + drawable.duration
                    },
                    style = drawable.style_name,
                    effect = AssEffect {
                        effect: drawable.effect
                    },
                    b = drawable.danmu.rgb.2,
                    g = drawable.danmu.rgb.1,
                    r = drawable.danmu.rgb.0,
                    text = escape_text(&drawable.danmu.content),
                    // text = (0..drawable.danmu.content.chars().count()).map(|_| '晚').collect::<String>(),
                )
                .into_bytes()
                .as_slice(),
            )
            .await?;
        Ok(())
    }

    pub async fn flush(&mut self) -> Result<()> {
        Ok(self.f.flush().await?)
    }
}

fn escape_text(text: &'_ str) -> Cow<'_, str> {
    let text = text.trim();
    if memchr::memchr(b'\n', text.as_bytes()).is_some() {
        Cow::from(text.replace('\n', "\\N"))
    } else {
        Cow::from(text)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn time_point_fmt() {
        assert_eq!(format!("{}", TimePoint { t: 0.0 }), "0:00:00.00");
        assert_eq!(format!("{}", TimePoint { t: 1.0 }), "0:00:01.00");
        assert_eq!(format!("{}", TimePoint { t: 60.0 }), "0:01:00.00");
        assert_eq!(format!("{}", TimePoint { t: 3600.0 }), "1:00:00.00");
        assert_eq!(format!("{}", TimePoint { t: 3600.0 + 60.0 }), "1:01:00.00");
        assert_eq!(format!("{}", TimePoint { t: 3600.0 + 60.0 + 1.0 }), "1:01:01.00");
        assert_eq!(
            format!(
                "{}",
                TimePoint {
                    t: 3600.0 + 60.0 + 1.0 + 0.5
                }
            ),
            "1:01:01.50"
        );
        assert_eq!(
            format!(
                "{}",
                TimePoint {
                    t: 3600.0 + 1.0 + 0.01234
                }
            ),
            "1:00:01.01"
        );
    }

    #[test]
    fn test_escape_text() {
        assert_eq!(
            escape_text("呵\n呵\n比\n你\n们\n更\n喜\n欢\n晚\n晚").as_ref(),
            r"呵\N呵\N比\N你\N们\N更\N喜\N欢\N晚\N晚"
        );
    }
}
