use super::{MathRendererExt, SvgUtil};
use crate::elements::{Math, MathLang};
use async_trait::async_trait;
use gtk4::glib::Bytes;
use gtk4::{ContentFit, Picture, Svg, Widget, prelude::*};
use std::error::Error;
use std::io::{Error as IoError, ErrorKind};
use std::sync::Arc;
use tokio::runtime::Runtime;

#[derive(Debug, Clone)]
pub struct RatexRenderer {
    runtime: Arc<Runtime>,
}

impl RatexRenderer {
    const DEFAULT_FONT_SIZE: f64 = 10.0;
    const DEFAULT_PADDING: f64 = 1.0;
    const DEFAULT_STROKE_WIDTH: f64 = 1.5;

    pub fn new(runtime: &Arc<Runtime>) -> Self {
        Self {
            runtime: runtime.clone(),
        }
    }

    async fn generate_svg(
        math: Math,
        font_size: f64,
        stroke_width: f64,
        padding: f64,
    ) -> Result<String, IoError> {
        if math.lang == MathLang::MathML {
            return Err(IoError::new(
                ErrorKind::Unsupported,
                "MathML is not supported",
            ));
        }

        let svg_opts = ratex_svg::SvgOptions {
            font_size,
            padding,
            stroke_width,
            embed_glyphs: true,
            font_dir: String::new(),
        };
        let layout_opts = ratex_layout::LayoutOptions::default();

        let ast = ratex_parser::parse(&math.expression)
            .map_err(|e| IoError::new(ErrorKind::InvalidData, format!("ParseError: {e}")))?;
        let lbox = ratex_layout::layout(&ast, &layout_opts);
        let dl = ratex_layout::to_display_list(&lbox);
        let svg = ratex_svg::render_to_svg(&dl, &svg_opts);
        let svg = SvgUtil::make_symbolic(&svg)?;
        //std::fs::write("test.svg", &svg).unwrap();

        Ok(svg)
    }
}

#[async_trait]
impl MathRendererExt for RatexRenderer {
    async fn render(
        &self,
        math: Math,
        font_size: i32,
        scale: i32,
    ) -> Result<Widget, Box<dyn Error>> {
        let font_size_factor = font_size as f64 / Self::DEFAULT_FONT_SIZE;
        let stroke_width = font_size_factor * Self::DEFAULT_STROKE_WIDTH;
        let padding = font_size_factor * Self::DEFAULT_PADDING;

        let svg_future = Self::generate_svg(math, font_size as f64, stroke_width, padding);
        let svg_data = self.runtime.spawn(svg_future).await.unwrap()?;
        let bytes = Bytes::from_owned(svg_data);
        let paintable = Svg::from_bytes(&bytes);

        let widget_width = (paintable.intrinsic_width() * scale) as f32 * 0.8;
        let widget_height = (paintable.intrinsic_height() * scale) as f32 * 0.8;

        let margin = (2.0 * font_size_factor) as i32;

        Ok(Picture::builder()
            .paintable(&paintable)
            .height_request(widget_height as i32)
            .width_request(widget_width as i32)
            .margin_start(margin)
            .margin_end(margin)
            .vexpand(true)
            .content_fit(ContentFit::Contain)
            .build()
            .upcast())
    }
}
