//! Produce text!!!

use std::sync::Arc;

use assets_manager::{
    loader::{BytesLoader, LoadFrom, RonLoader},
    Asset, AssetCache,
};
use cosmic_text::fontdb::FaceInfo;
use glam::Vec3Swizzles;
use glyphon::{
    fontdb::Source, Attrs, Buffer, Color, FontSystem, Metrics, Shaping, SwashCache, TextArea,
    TextBounds,
};
use sys_locale::get_locale;

use crate::screen::WORLD_HALF_EXTENTS;

/// Contains the font system, swash cache, and is generally permanent
pub struct TextSystem {
    pub fonts: FontSystem,
    pub swash: SwashCache,
    glyphon_to_world: glam::Affine3A,
}

impl TextSystem {
    pub fn new<S: assets_manager::source::Source>(
        assets: &AssetCache<S>,
    ) -> color_eyre::Result<Self> {
        let fonts_handle = assets.load_rec_dir::<FontData>("fonts")?;
        let fonts: Vec<_> = fonts_handle
            .read()
            .iter(assets)
            .filter_map(|maybe_font| match maybe_font {
                Ok(font_handle) => {
                    let font_data: Arc<Vec<u8>> = Arc::new(font_handle.read().0.clone());
                    let id = font_handle.id();
                    Some((id, Source::Binary(font_data)))
                }
                Err(e) => {
                    log::info!("failed to recursively load a font: {e}");
                    None
                }
            })
            .collect();

        // Until we can assume otherwise, hardcode this
        let locale = get_locale().unwrap_or_else(|| "en-US".to_string());
        let mut db = glyphon::fontdb::Database::new();
        #[cfg(not(target_arch = "wasm32"))]
        {
            db.load_system_fonts();
        }
        for (asset_id, source) in fonts.into_iter() {
            let ids = db.load_font_source(source);
            log::warn!("loaded {asset_id} into {ids}");
            let font_info = ids.iter().find_map(|id| db.face(*id));
            if font_info
                .expect("fontdb failed to load faces from {asset_id}")
                .monospaced
            {
                log::warn!("{asset_id} is monospaced");
            }
            // Change emoji to monospaced?
            for face_info in ids
                .into_iter()
                .filter_map(|id| db.face(id).cloned())
                .collect::<Vec<_>>()
            {
                let FaceInfo {
                    ref families,
                    ref post_script_name,
                    ref style,
                    ref monospaced,
                    ref weight,
                    ref stretch,
                    ..
                } = face_info;
                log::warn!(
                    "{families:?} {post_script_name} {style:?} {monospaced} {weight:?} {stretch:?}"
                );
                // face_info.monospaced = true;
                // face_info.id = ID::dummy();
                // db.push_face_info(face_info);
            }
        }
        let fonts = FontSystem::new_with_locale_and_db(locale, db);

        Ok(Self {
            fonts,
            swash: SwashCache::new(),
            glyphon_to_world: (glam::Affine3A::from_translation(glam::Vec3::new(
                -(WORLD_HALF_EXTENTS.x as f32),
                -(WORLD_HALF_EXTENTS.y as f32),
                0.0,
            )) * glam::Affine3A::from_scale(glam::Vec3::new(
                2.0 * WORLD_HALF_EXTENTS.x as f32,
                2.0 * WORLD_HALF_EXTENTS.y as f32,
                1.0,
            )))
            .inverse(),
        })
    }
}

struct FontData(Vec<u8>);

impl From<Vec<u8>> for FontData {
    fn from(value: Vec<u8>) -> Self {
        Self(value)
    }
}

impl Asset for FontData {
    const EXTENSIONS: &'static [&'static str] = &["ttf"];

    type Loader = LoadFrom<Vec<u8>, BytesLoader>;
}

#[derive(serde::Deserialize, Debug, Clone, Copy)]
struct TextColor(
    #[serde(deserialize_with = "palette::serde::deserialize_with_optional_alpha")] palette::Srgba,
);

#[derive(serde::Deserialize, Debug, Clone)]
struct Text {
    font_size: f32,
    #[serde(default)]
    text_dims: Option<glam::Vec2>,
    text: String,
    #[serde(default)]
    position: Option<glam::Vec2>,
    #[serde(default)]
    scale: Option<f32>,
    #[serde(default)]
    color: Option<TextColor>,
    #[serde(default)]
    lines: Option<usize>,
}

#[derive(serde::Deserialize)]
struct TextList(Vec<Text>);

impl Asset for TextList {
    const EXTENSION: &'static str = "textl";

    type Loader = RonLoader;
}

impl TextSystem {
    /// Loading requested data here
    pub fn prepare<S: assets_manager::source::Source>(
        &mut self,
        _data: &TextSystemData,
        _assets: &AssetCache<S>,
    ) {
    }

    /// Convert a 2d point as the world describes it, to a 2d point that
    /// the world glyphon can understand
    fn convert_world_to_glyphon_world(&self, point: glam::Vec2) -> glam::Vec2 {
        // Converts it to a representation of [0, 1] from the world domain of [-WHE, +WHE]
        let normalized = self.glyphon_to_world.transform_point3(point.extend(0.0));
        // Maps the normalized coords to the glyphon scale of 2 * WHE
        normalized.xy() * 2.0 * WORLD_HALF_EXTENTS.as_vec2()
    }

    /// Produce world space text.
    ///
    /// Moving the camera *should* move this text.
    pub fn produce_world<S: assets_manager::source::Source>(
        &mut self,
        _data: &TextSystemData,
        assets: &AssetCache<S>,
    ) -> impl IntoIterator<Item = (Buffer, BufferMapper)> {
        let Ok(world_text) = assets.load::<TextList>("text_data.world") else {
            let metrics = Metrics::new(32.0, 20.0);
            let mut buffer = Buffer::new(&mut self.fonts, metrics);
            {
                let mut buffer = buffer.borrow_with(&mut self.fonts);
                buffer.set_size(250.0, 25.0);
                let attrs = Attrs::new().family(glyphon::Family::Name("Poiret One"));
                buffer.set_text("Hello, game! 🦀\n", attrs, Shaping::Advanced);
                buffer.shape_until_scroll(true);
            }
            let loc = glam::Vec2::new(32.0, 32.0);
            let mut buffer2 = Buffer::new(&mut self.fonts, metrics);
            {
                let mut buffer2 = buffer2.borrow_with(&mut self.fonts);
                buffer2.set_size(250.0, 25.0);
                let attrs = Attrs::new()
                    .family(glyphon::Family::Name("Poiret One"))
                    .color(glyphon::Color::rgb(255, 255, 255));
                buffer2.set_text("Y tho? 🦀\n", attrs, Shaping::Advanced);
                buffer2.shape_until_scroll(true);
            }
            let loc2 = glam::Vec2::new(32.0, 64.0);
            return vec![
                (
                    buffer,
                    BufferMapper::new(self.convert_world_to_glyphon_world(loc), 1.0),
                ),
                (
                    buffer2,
                    BufferMapper::new(self.convert_world_to_glyphon_world(loc2), 1.0),
                ),
            ];
        };

        world_text
            .read()
            .0
            .iter()
            .map(|text| {
                let metrics = Metrics::new(text.font_size, 20.0);
                let mut buffer = Buffer::new(&mut self.fonts, metrics);
                {
                    let mut buffer = buffer.borrow_with(&mut self.fonts);
                    if let Some(text_dims) = text.text_dims {
                        buffer.set_size(text_dims.x, text_dims.y);
                    }
                    // let attrs = Attrs::new().family(glyphon::Family::Name("Poiret One"));
                    let mut attrs = Attrs::new();
                    if let Some(color) = text.color {
                        let color: palette::Srgba<u8> = color.0.into_format();
                        attrs = attrs.color(glyphon::Color::rgba(
                            color.red,
                            color.green,
                            color.blue,
                            color.alpha,
                        ));
                    }
                    buffer.set_rich_text(
                        [(
                            text.text.as_str(),
                            // attrs.family(glyphon::Family::Name("Noto Color Emoji")),
                            attrs.family(glyphon::Family::Name("Poiret One")),
                        )],
                        attrs,
                        Shaping::Advanced,
                    );

                    for line_idx in 0..text.lines.unwrap_or(1) {
                        buffer.line_shape(line_idx);
                        buffer.line_layout(line_idx);
                    }
                }
                (
                    buffer,
                    BufferMapper::new(
                        self.convert_world_to_glyphon_world(text.position.unwrap_or_default()),
                        text.scale.unwrap_or(1.0),
                    ),
                )
            })
            .collect()
    }

    /// Produce camera space text.
    ///
    /// Moving the game camera should *not* move this text.
    pub fn produce_camera<S: assets_manager::source::Source>(
        &mut self,
        _data: &TextSystemData,
        assets: &AssetCache<S>,
    ) -> impl IntoIterator<Item = (Buffer, BufferMapper)> {
        let Ok(cam_text) = assets.load::<TextList>("text_data.camera") else {
            let metrics = Metrics::new(32.0, 20.0);
            let mut buffer = Buffer::new(&mut self.fonts, metrics);
            {
                let mut buffer = buffer.borrow_with(&mut self.fonts);
                buffer.set_size(200.0, 25.0);
                let attrs = Attrs::new().family(glyphon::Family::Name("Poiret One"));
                buffer.set_text("Hello, Rust! 🦀\n", attrs, Shaping::Advanced);
                buffer.shape_until_scroll(true);
            }
            return vec![(buffer, BufferMapper::new((32.0, 32.0).into(), 1.0))];
        };

        cam_text
            .read()
            .0
            .iter()
            .map(|text| {
                let metrics = Metrics::new(text.font_size, 20.0);
                let mut buffer = Buffer::new(&mut self.fonts, metrics);
                {
                    let mut buffer = buffer.borrow_with(&mut self.fonts);
                    if let Some(text_dims) = text.text_dims {
                        buffer.set_size(text_dims.x, text_dims.y);
                    }
                    let attrs = Attrs::new().family(glyphon::Family::Name("Poiret One"));
                    buffer.set_text(
                        &text.text,
                        if let Some(color) = text.color {
                            let color: palette::Srgba<u8> = color.0.into_format();
                            attrs.color(glyphon::Color::rgba(
                                color.red,
                                color.green,
                                color.blue,
                                color.alpha,
                            ))
                        } else {
                            attrs
                        },
                        Shaping::Advanced,
                    );
                    buffer.shape_until_scroll(true);
                }
                (
                    buffer,
                    BufferMapper::new(text.position.unwrap_or_default(), text.scale.unwrap_or(1.0)),
                )
            })
            .collect()
    }
}

/// Resposible for configuring the TextArea of a given buffer
pub struct BufferMapper {
    position: glam::Vec2,
    scale: f32,
}

impl BufferMapper {
    fn new(position: glam::Vec2, scale: f32) -> Self {
        Self { position, scale }
    }

    pub(crate) fn produce<'a>(&self, buffer: &'a Buffer) -> TextArea<'a> {
        TextArea {
            buffer,
            left: self.position.x,
            top: self.position.y,
            scale: self.scale,
            bounds: TextBounds {
                ..Default::default()
            },
            default_color: Color::rgba(255, 0, 0, 200),
        }
    }
}

/// Contains ephemural data about what the game wants to display with text
pub struct TextSystemData {}

impl TextSystemData {
    /// Extract the data necessary to produce text
    pub fn extract(_world: &mut hecs::World) -> Self {
        Self {}
    }
}