//! Defines the [PdfFontGlyphs] struct, a collection of all the `PdfFontGlyph` objects in a
//! `PdfFont`.

use crate::bindgen::FPDF_FONT;
use crate::bindings::PdfiumLibraryBindings;
use crate::error::PdfiumError;
use crate::font_glyph::PdfFontGlyph;
use std::cell::Cell;
use std::ops::{Range, RangeInclusive};
use std::os::raw::c_uint;

/// The zero-based index of a single [PdfFontGlyph] inside its containing [PdfFontGlyphs] collection.
pub type PdfFontGlyphIndex = u16;

pub struct PdfFontGlyphs<'a> {
    handle: FPDF_FONT,
    len: Cell<Option<PdfFontGlyphIndex>>,
    bindings: &'a dyn PdfiumLibraryBindings,
}

impl<'a> PdfFontGlyphs<'a> {
    #[inline]
    pub(crate) fn from_pdfium(handle: FPDF_FONT, bindings: &'a dyn PdfiumLibraryBindings) -> Self {
        Self {
            handle,
            len: Cell::new(None),
            bindings,
        }
    }

    /// Initializes the length of this [PdfFontGlyphs] collection.
    ///
    /// We avoid doing this on instantiation as it may not take constant time.
    /// We only incur the cost of initializing this value if the user actually requests the
    /// [PdfFontGlyphs] collection by calling the [PdfFont::glyphs()] function.
    #[inline]
    pub(crate) fn initialize_len(&self) {
        if self.len.get().is_none() {
            // Pdfium does not provide a function that returns the number of glyphs in a font.
            // We use a binary search algorithm to determine the number of glyphs as efficiently
            // as possible.

            let len = self
                .find_maximum_valid_glyph_index(u16::MIN, u16::MAX)
                .unwrap_or(0);

            self.len.replace(Some(len));
        }
    }

    /// Returns the highest index position of an extant glyph within the given index range.
    fn find_maximum_valid_glyph_index(&self, min: u16, max: u16) -> Option<u16> {
        // Exit immediately if the maximum valid glyph index lies outside the given index boundaries.

        if !self
            .bindings
            .FPDFFont_GetGlyphPath(self.handle, max as c_uint, 1.0)
            .is_null()
        {
            return Some(max);
        }

        if self
            .bindings
            .FPDFFont_GetGlyphPath(self.handle, min as c_uint, 1.0)
            .is_null()
        {
            return None;
        }

        // Partition the given index boundaries and recursively search.

        let mid = min + (max - min) / 2;

        if self
            .bindings
            .FPDFFont_GetGlyphPath(self.handle, mid as c_uint, 1.0)
            .is_null()
        {
            // The maximum valid glyph index must lie before the partition mid point.

            if mid > min {
                self.find_maximum_valid_glyph_index(min, mid - 1)
            } else {
                None
            }
        } else {
            // The maximum valid glyph index must lie after the partition mid point.

            if mid < max {
                self.find_maximum_valid_glyph_index(mid + 1, max)
            } else {
                Some(max)
            }
        }
    }

    /// Returns the [PdfiumLibraryBindings] used by this [PdfFontGlyphs] collection.
    #[inline]
    pub fn bindings(&self) -> &'a dyn PdfiumLibraryBindings {
        self.bindings
    }

    /// Returns the number of glyphs in this [PdfFontGlyphs] collection.
    #[inline]
    pub fn len(&self) -> PdfFontGlyphIndex {
        self.len.get().unwrap_or(0)
    }

    /// Returns `true` if this [PdfFontGlyphs] collection is empty.
    #[inline]
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }

    /// Returns a Range from `0..(number of glyphs)` for this [PdfFontGlyphs] collection.
    #[inline]
    pub fn as_range(&self) -> Range<PdfFontGlyphIndex> {
        0..self.len()
    }

    /// Returns an inclusive Range from `0..=(number of glyphs - 1)` for this [PdfFontGlyphs] collection.
    #[inline]
    pub fn as_range_inclusive(&self) -> RangeInclusive<PdfFontGlyphIndex> {
        if self.is_empty() {
            0..=0
        } else {
            0..=(self.len() - 1)
        }
    }

    /// Returns a single [PdfFontGlyph] from this [PdfFontGlyphs] collection.
    pub fn get(&self, index: PdfFontGlyphIndex) -> Result<PdfFontGlyph<'a>, PdfiumError> {
        if index >= self.len() {
            return Err(PdfiumError::FontGlyphIndexOutOfBounds);
        }

        Ok(PdfFontGlyph::from_pdfium(self.handle, index, self.bindings))
    }

    /// Returns an iterator over all the glyphs in this [PdfFontGlyphs] collection.
    #[inline]
    pub fn iter(&self) -> PdfFontGlyphsIterator {
        PdfFontGlyphsIterator::new(self)
    }
}

/// An iterator over all the [PdfFontGlyph] objects in a [PdfFontGlyphs] collection.
pub struct PdfFontGlyphsIterator<'a> {
    glyphs: &'a PdfFontGlyphs<'a>,
    next_index: PdfFontGlyphIndex,
}

impl<'a> PdfFontGlyphsIterator<'a> {
    #[inline]
    pub(crate) fn new(glyphs: &'a PdfFontGlyphs<'a>) -> Self {
        PdfFontGlyphsIterator {
            glyphs,
            next_index: 0,
        }
    }
}

impl<'a> Iterator for PdfFontGlyphsIterator<'a> {
    type Item = PdfFontGlyph<'a>;

    fn next(&mut self) -> Option<Self::Item> {
        let next = self.glyphs.get(self.next_index);

        self.next_index += 1;

        next.ok()
    }
}
