#![allow(dead_code)]

use std::cell::RefCell;
use std::cmp::PartialEq;
use std::collections::HashMap;
use std::fmt::Display;
use std::hash::{Hash, Hasher};
use std::mem::size_of;
use std::ops::Deref;
use std::ptr::null;
use std::rc::{Rc, Weak};

use glc::{
    array_buffer::{ArrayBuffer, Target, Usage},
    error::Error as GlcError,
    misc::BindGuard,
    shader::{Program, Type},
    texture::{FilterMag, FilterMin, Target as TextureTarget, Texture, Wrap},
    vector::{Vector2f, Vector4f},
    vertex_array::{DataType, VertexArray},
};
use glyph_brush::{
    ab_glyph::{FontArc, FontVec},
    BrushAction, BrushError, FontId, GlyphBrush, GlyphBrushBuilder, GlyphVertex, OwnedSection,
    OwnedText, Rectangle,
};
use ordered_float::OrderedFloat;
use space_crush_common::misc::{LogResult, Vfs, WorldHelper as _};
use specs::World;

use crate::{misc::WorldHelper, Error};

/* TextManager */

#[derive(Clone)]
pub struct TextManager(Rc<RefCell<TextManagerInner>>);

struct TextManagerInner {
    vfs: Vfs,
    program: Rc<Program>,
    fonts: HashMap<String, FontArc>,
}

impl TextManager {
    pub fn new(world: &World) -> Result<Self, Error> {
        let vfs = world.resource::<Vfs>()?.deref().clone();
        let program = world.load_program(vec![
            (Type::Vertex, "resources/shader/text/vert.glsl"),
            (Type::Fragment, "resources/shader/text/frag.glsl"),
        ])?;
        let program = Rc::new(program);

        Ok(Self(Rc::new(RefCell::new(TextManagerInner {
            vfs,
            program,
            fonts: HashMap::new(),
        }))))
    }

    pub fn create_cache(&self) -> Result<TextCache, Error> {
        TextCache::new(self)
    }

    pub fn font(&self, path: &str) -> Result<FontArc, Error> {
        if let Some(font) = self.0.borrow().fonts.get(path) {
            return Ok(font.clone());
        }

        let path = self.0.borrow().vfs.join(path)?;

        let mut data = Vec::new();
        path.open_file()?.read_to_end(&mut data)?;

        let font = FontVec::try_from_vec(data)?;
        let font = FontArc::new(font);

        {
            let mut this = self.0.borrow_mut();
            this.fonts.insert(path.filename(), font.clone());
            this.fonts.insert(path.as_str().to_owned(), font.clone());
        }

        Ok(font)
    }
}

/* TextCache */

#[derive(Clone)]
pub struct TextCache(Rc<RefCell<TextCacheInner>>);

pub struct TextCacheInner {
    manager: TextManager,
    fonts: HashMap<String, FontId>,
    glyphs: Option<GlyphBrush<Vertex, Extra>>,
    texture: Rc<RefCell<Texture>>,
    texts: HashMap<usize, Weak<RefCell<TextInner>>>,
    text_ids: Vec<usize>,
    next_text_id: usize,
    update_counter: usize,
    update_requested: bool,
}

pub struct UpdateGuard(TextCache);

impl TextCache {
    fn new(manager: &TextManager) -> Result<Self, Error> {
        let mut texture = Texture::new(TextureTarget::Texture2D)?;
        texture.set_filter(FilterMin::Linear, FilterMag::Linear)?;
        texture.set_wrap(Wrap::ClampToEdge, Wrap::ClampToEdge, Wrap::ClampToEdge)?;

        Ok(Self(Rc::new(RefCell::new(TextCacheInner {
            manager: manager.clone(),
            fonts: HashMap::new(),
            glyphs: None,
            texture: Rc::new(RefCell::new(texture)),
            texts: HashMap::new(),
            text_ids: Vec::new(),
            next_text_id: 0,
            update_counter: 0,
            update_requested: false,
        }))))
    }

    pub fn new_text(&self) -> TextBuilder {
        TextBuilder::new(self)
    }

    pub fn begin_update(&self) -> UpdateGuard {
        self.0.borrow_mut().update_counter += 1;

        UpdateGuard(self.clone())
    }

    fn end_update(&self) {
        let mut inner = self.0.borrow_mut();

        if inner.update_counter > 0 {
            inner.update_counter -= 1;
        }

        if inner.update_counter > 0 {
            return;
        }

        let update_requested = inner.update_requested;

        drop(inner);

        if update_requested {
            self.update().error("Unable to update text cache");
        }
    }

    fn font(&self, path: &str) -> Result<FontId, Error> {
        let mut inner = self.0.borrow_mut();

        if let Some(id) = inner.fonts.get(path) {
            return Ok(*id);
        }

        let font = inner.manager.font(path)?;

        inner.add_font(font)
    }

    fn next_text_id(&self) -> usize {
        let mut inner = self.0.borrow_mut();

        if let Some(id) = inner.text_ids.pop() {
            return id;
        }

        let ret = inner.next_text_id;
        inner.next_text_id += 1;

        ret
    }

    fn add_text(&self, text: Weak<RefCell<TextInner>>) -> Result<(), Error> {
        let text_id = self.next_text_id();
        for section in &mut text.upgrade().unwrap().borrow_mut().sections {
            for t in &mut section.text {
                t.extra.text_id = text_id;
            }
        }

        self.0.borrow_mut().texts.insert(text_id, text);

        self.update()
    }

    fn update(&self) -> Result<(), Error> {
        let mut inner = self.0.borrow_mut();
        let inner = &mut *inner;

        if inner.update_counter > 0 {
            inner.update_requested = true;

            return Ok(());
        }

        /* remove droped texts */
        let texts = &mut inner.texts;
        let text_ids = &mut inner.text_ids;
        texts.retain(|id, t| {
            if t.strong_count() > 0 {
                true
            } else {
                text_ids.push(*id);

                false
            }
        });

        /* add sections to glyph queue */
        let glyphs = inner.glyphs.as_mut().unwrap();
        for text in texts.values_mut() {
            for section in text.upgrade().unwrap().borrow_mut().sections.iter_mut() {
                glyphs.queue(section.to_borrowed());
            }
        }

        /* process glyph queue */
        let action = loop {
            let glyphs = inner.glyphs.as_mut().unwrap();
            let texture = inner.texture.borrow();
            let ret = glyphs.process_queued(
                move |rect, data| update_texture(&*texture, rect, data),
                create_vertex,
            );

            match ret {
                Ok(action) => break action,
                Err(BrushError::TextureTooSmall { suggested, .. }) => {
                    let new_size = glyphs.texture_dimensions();
                    let new_size = resize_texture(suggested, Some(new_size))?;
                    glyphs.resize_texture(new_size.0, new_size.1);
                }
            }
        };

        // upate vertex data
        if let BrushAction::Draw(vertices) = action {
            let mut map = Vec::<Vec<VertexData>>::new();
            map.resize_with(inner.next_text_id, Default::default);

            for vertex in vertices {
                map[vertex.text_id].push(vertex.data);
            }

            for (text_id, data) in map.into_iter().enumerate() {
                if data.is_empty() {
                    continue;
                }

                inner
                    .texts
                    .get(&text_id)
                    .unwrap()
                    .upgrade()
                    .unwrap()
                    .borrow_mut()
                    .update(data)?;
            }
        }

        inner.update_requested = false;

        Ok(())
    }
}

impl TextCacheInner {
    fn add_font(&mut self, font: FontArc) -> Result<FontId, Error> {
        let (id, size) = match self.glyphs.as_mut() {
            Some(glyphs) => (glyphs.add_font(font), None),
            None => {
                let glyphs = GlyphBrushBuilder::using_font(font)
                    .draw_cache_position_tolerance(2.0)
                    .build();
                let size = glyphs.texture_dimensions();

                self.glyphs = Some(glyphs);

                (FontId(0), Some(size))
            }
        };

        if let Some(size) = size {
            let texture = self.texture.borrow();
            let _guard = BindGuard::new(&*texture);

            let new_size = resize_texture(size, None)?;

            self.glyphs
                .as_mut()
                .unwrap()
                .resize_texture(new_size.0, new_size.1);
        }

        Ok(id)
    }
}

impl Drop for UpdateGuard {
    fn drop(&mut self) {
        self.0.end_update()
    }
}

fn resize_texture(new_size: (u32, u32), cur_size: Option<(u32, u32)>) -> Result<(u32, u32), Error> {
    let mut max_size = 0;
    gl::get_integer_v(gl::MAX_TEXTURE_SIZE, &mut max_size);

    let max_size = max_size as u32;
    let (w, h) = if let Some(cur_size) = cur_size {
        if (new_size.0 > max_size || new_size.1 > max_size)
            && (cur_size.0 < max_size || cur_size.1 < max_size)
        {
            (max_size, max_size)
        } else {
            new_size
        }
    } else {
        new_size
    };

    GlcError::checked(|| {
        gl::tex_image_2d(
            gl::TEXTURE_2D,
            0,
            gl::RED as _,
            w as _,
            h as _,
            0,
            gl::RED,
            gl::UNSIGNED_BYTE,
            null(),
        )
    })?;

    Ok(new_size)
}

fn update_texture(texture: &Texture, rect: Rectangle<u32>, data: &[u8]) {
    GlcError::checked(|| {
        gl::texture_sub_image_2d(
            texture.id(),
            0,
            rect.min[0] as _,
            rect.min[1] as _,
            rect.width() as _,
            rect.height() as _,
            gl::RED,
            gl::UNSIGNED_BYTE,
            data.as_ptr() as _,
        )
    })
    .error("Unable to update text texture");
}

fn create_vertex(data: GlyphVertex<Extra>) -> Vertex {
    let pos_min = Vector2f::new(data.pixel_coords.min.x, data.pixel_coords.min.y);
    let pos_max = Vector2f::new(data.pixel_coords.max.x, data.pixel_coords.max.y);
    let tex_min = Vector2f::new(data.tex_coords.min.x, data.tex_coords.min.y);
    let tex_max = Vector2f::new(data.tex_coords.max.x, data.tex_coords.max.y);

    Vertex {
        text_id: data.extra.text_id,
        data: VertexData {
            pos_min,
            pos_max,
            tex_min,
            tex_max,
            color: data.extra.color,
        },
    }
}

/* TextBuilder */

pub struct TextBuilder {
    cache: TextCache,
    items: Vec<BuilderItem>,
}

enum BuilderItem {
    Position(f32, f32),
    Color(Vector4f),
    Font(String),
    Text(String),
    Scale(f32),
}

impl TextBuilder {
    fn new(cache: &TextCache) -> Self {
        Self {
            cache: cache.clone(),
            items: Vec::new(),
        }
    }

    pub fn build(self) -> Result<Text, Error> {
        let mut sections = Vec::<OwnedSection<Extra>>::new();

        let mut scale = 20.0;
        let mut font_id = None;
        let mut color = Vector4f::new(0.0, 0.0, 0.0, 1.0);
        let mut position = None;

        for item in self.items {
            match item {
                BuilderItem::Text(text) => {
                    let font_id = font_id.ok_or(Error::FontNotSet)?;
                    let text = OwnedText::new(text)
                        .with_extra(Extra { text_id: 0, color })
                        .with_scale(scale)
                        .with_font_id(font_id);

                    match (sections.pop(), position.take()) {
                        (Some(section), Some(pos)) => {
                            sections.push(section);
                            sections.push(
                                OwnedSection::default()
                                    .with_screen_position(pos)
                                    .add_text(text),
                            );
                        }
                        (Some(section), None) => {
                            sections.push(section.add_text(text));
                        }
                        (None, Some(pos)) => sections.push(
                            OwnedSection::<Extra>::default()
                                .with_screen_position(pos)
                                .add_text(text),
                        ),
                        (None, None) => {
                            sections.push(OwnedSection::<Extra>::default().add_text(text))
                        }
                    };
                }
                BuilderItem::Position(x, y) => position = Some((x, y)),
                BuilderItem::Color(c) => color = c,
                BuilderItem::Scale(s) => scale = s,
                BuilderItem::Font(path) => font_id = Some(self.cache.font(&path)?),
            }
        }

        let text = Text::new(&self.cache, sections)?;

        let weak = Rc::downgrade(&text.inner);
        self.cache.add_text(weak)?;

        Ok(text)
    }

    pub fn position(mut self, x: f32, y: f32) -> Self {
        self.items.push(BuilderItem::Position(x, y));

        self
    }

    pub fn font<S>(mut self, path: S) -> Self
    where
        S: Display,
    {
        self.items.push(BuilderItem::Font(path.to_string()));

        self
    }

    pub fn color(self, r: f32, g: f32, b: f32, a: f32) -> Self {
        self.color_vec(Vector4f::new(r, g, b, a))
    }

    pub fn color_vec(mut self, color: Vector4f) -> Self {
        self.items.push(BuilderItem::Color(color));

        self
    }

    pub fn text<S>(mut self, text: S) -> Self
    where
        S: Display,
    {
        self.items.push(BuilderItem::Text(text.to_string()));

        self
    }

    pub fn scale(mut self, scale: f32) -> Self {
        self.items.push(BuilderItem::Scale(scale));

        self
    }
}

/* Text */

pub struct Text {
    cache: TextCache,
    inner: Rc<RefCell<TextInner>>,
}

struct TextInner {
    array: VertexArray,
    program: Rc<Program>,
    texture: Rc<RefCell<Texture>>,
    sections: Vec<OwnedSection<Extra>>,
    vertex_count: usize,
}

#[derive(Default, Debug, Clone)]
struct Extra {
    text_id: usize,
    color: Vector4f,
}

#[derive(Default, Clone)]
struct Vertex {
    text_id: usize,
    data: VertexData,
}

#[repr(C, packed)]
#[derive(Default, Debug, Clone)]
struct VertexData {
    pos_min: Vector2f,
    pos_max: Vector2f,
    tex_min: Vector2f,
    tex_max: Vector2f,
    color: Vector4f,
}

impl Text {
    fn new(cache: &TextCache, sections: Vec<OwnedSection<Extra>>) -> Result<Self, Error> {
        const STRIDE: gl::GLsizei = size_of::<VertexData>() as _;

        const SIZE_VEC2: gl::GLsizei = size_of::<Vector2f>() as _;

        const OFFSET_POS_MIN: gl::GLsizei = 0;
        const OFFSET_POS_MAX: gl::GLsizei = OFFSET_POS_MIN + SIZE_VEC2;
        const OFFSET_TEX_MIN: gl::GLsizei = OFFSET_POS_MAX + SIZE_VEC2;
        const OFFSET_TEX_MAX: gl::GLsizei = OFFSET_TEX_MIN + SIZE_VEC2;
        const OFFSET_COLOR: gl::GLsizei = OFFSET_TEX_MAX + SIZE_VEC2;

        let buffer = ArrayBuffer::new(Target::ArrayBuffer)?;
        let array = VertexArray::builder()
            .bind_buffer(buffer)
            .vertex_attrib_pointer(0, 2, DataType::Float, false, STRIDE, OFFSET_POS_MIN)?
            .vertex_attrib_divisor(1)?
            .vertex_attrib_pointer(1, 2, DataType::Float, false, STRIDE, OFFSET_POS_MAX)?
            .vertex_attrib_divisor(1)?
            .vertex_attrib_pointer(2, 2, DataType::Float, false, STRIDE, OFFSET_TEX_MIN)?
            .vertex_attrib_divisor(1)?
            .vertex_attrib_pointer(3, 2, DataType::Float, false, STRIDE, OFFSET_TEX_MAX)?
            .vertex_attrib_divisor(1)?
            .vertex_attrib_pointer(4, 4, DataType::Float, false, STRIDE, OFFSET_COLOR)?
            .vertex_attrib_divisor(1)?
            .build()?;

        let cache = cache.clone();
        let program = cache.0.borrow().manager.0.borrow().program.clone();
        let texture = cache.0.borrow().texture.clone();

        let inner = TextInner {
            array,
            program,
            texture,
            sections,
            vertex_count: 0,
        };
        let text = Text {
            cache,
            inner: Rc::new(RefCell::new(inner)),
        };

        Ok(text)
    }

    pub fn render(&self, enable_blending: bool) {
        let inner = self.inner.borrow();
        let texture = inner.texture.borrow();

        if enable_blending {
            gl::enable(gl::BLEND);
            gl::blend_func(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
        }

        let _guard = BindGuard::new(&*texture);
        let _guard = BindGuard::new(&inner.array);
        let _guard = BindGuard::new(&*inner.program);
        gl::draw_arrays_instanced(gl::TRIANGLE_STRIP, 0, 4, inner.vertex_count as _);

        if enable_blending {
            gl::disable(gl::BLEND);
        }
    }

    pub fn update<S>(&mut self, mut index: usize, text: S) -> Result<(), Error>
    where
        S: Display,
    {
        let mut inner = self.inner.borrow_mut();

        for section in &mut inner.sections {
            if index < section.text.len() {
                section.text[index].text = text.to_string();

                break;
            } else {
                index -= section.text.len();
            }
        }

        drop(inner);

        self.cache.update()
    }
}

impl TextInner {
    fn update(&mut self, data: Vec<VertexData>) -> Result<(), Error> {
        let buffers = self.array.buffers_mut();
        buffers[0].buffer_data(Usage::StaticDraw, &data)?;

        self.vertex_count = data.len();

        Ok(())
    }
}

impl Hash for Extra {
    fn hash<H>(&self, state: &mut H)
    where
        H: Hasher,
    {
        self.text_id.hash(state);
        OrderedFloat::from(self.color[0]).hash(state);
        OrderedFloat::from(self.color[1]).hash(state);
        OrderedFloat::from(self.color[2]).hash(state);
        OrderedFloat::from(self.color[3]).hash(state);
    }
}

impl PartialEq for Extra {
    fn eq(&self, other: &Self) -> bool {
        self.text_id.eq(&other.text_id) && self.color.eq(&other.color)
    }
}
