//! Reading Type1 fonts.

mod charstring;
mod charstring_parser;
mod decrypt;
mod operator;
mod standard;
pub(crate) mod stream;

use crate::type1::charstring::parse_char_string;
use crate::type1::decrypt::{decrypt, decrypt_byte};
use crate::type1::standard::STANDARD;
use crate::type1::stream::Stream;
use crate::{Arc, Map, Matrix, OutlineBuilder};
use alloc::borrow::Cow;
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
use core::iter::Copied;
use core::slice::Iter;
use core::str::FromStr;
use log::error;
// Many parts of the parser code are adapted from
// https://github.com/janpe2/CFFDump/blob/master/cff/type1/Type1Dump.java

#[derive(Debug)]
pub(crate) struct Parameters {
    font_matrix: Matrix,
    encoding_type: EncodingType,
    subroutines: Map<u32, Vec<u8>>,
    charstrings: Map<String, Vec<u8>>,
    charstring_names: Vec<String>,
    charstring_indices: Map<String, u16>,
    pub(crate) weight_vector: Option<Vec<f32>>,
}

impl Default for Parameters {
    fn default() -> Self {
        Self {
            font_matrix: Matrix::default(),
            encoding_type: EncodingType::Standard,
            subroutines: Map::new(),
            charstrings: Map::new(),
            charstring_names: Vec::new(),
            charstring_indices: Map::new(),
            weight_vector: None,
        }
    }
}

/// A Type1 font table.
#[derive(Debug, Clone)]
pub struct Table {
    params: Arc<Parameters>,
}

impl Table {
    /// Parses a table from raw data.
    pub fn parse(data: &[u8]) -> Option<Self> {
        let data = if data.starts_with(&[0x80, 0x01]) {
            extract_pfb_segments(data)?
        } else if data.starts_with(b"%!") {
            Cow::Borrowed(data)
        } else {
            error!("type1 font wasn't recognized!");

            return None;
        };

        let mut s = Stream::new(data.as_ref());
        let mut params = Parameters::default();

        while let Some(token) = s.next_token() {
            match token {
                b"/FontInfo" => s.skip_dict(),
                b"/FontName" => s.skip_token(),
                b"/PaintType" => s.skip_token(),
                b"/FontType" => s.skip_token(),
                b"/FontBBox" => s.skip_token(),
                b"/UniqueID" => s.skip_token(),
                b"/Metrics" => s.skip_dict(),
                b"/StrokeWidth" => s.skip_token(),
                b"/FontMatrix" => {
                    let matrix = s.read_font_matrix()?;
                    params.font_matrix = Matrix {
                        sx: matrix[0],
                        kx: matrix[2],
                        ky: matrix[1],
                        sy: matrix[3],
                        tx: matrix[4],
                        ty: matrix[5],
                    };
                }
                b"/WeightVector" => {
                    if let Some(wv) = s.read_float_array() {
                        params.weight_vector = Some(wv);
                    }
                }
                b"/Encoding" => params.encoding_type = s.read_encoding()?,
                b"eexec" => {
                    let decrypted = decrypt(s.tail()?, true)?;
                    Self::parse_eexec(&decrypted, &mut params)?;
                }
                _ => {}
            }
        }

        // Parsing seems to have failed, so reject as invalid.
        if params.charstrings.is_empty() {
            return None;
        }

        Some(Self {
            params: Arc::new(params),
        })
    }

    fn parse_eexec(data: &[u8], params: &mut Parameters) -> Option<()> {
        let mut s = Stream::new(data);

        let mut len_iv = 4;
        let mut use_decryption = true;

        while let Some(token) = s.next_token() {
            match token {
                b"/Subrs" => {
                    params.subroutines = s
                        .parse_subroutines(len_iv, use_decryption)
                        .unwrap_or_default();
                }
                b"/CharStrings" => {
                    if let Some((chars, names)) = s.parse_charstrings(len_iv, use_decryption) {
                        params.charstring_indices = names
                            .iter()
                            .enumerate()
                            .map(|(i, n)| (n.clone(), i as u16))
                            .collect();
                        params.charstrings = chars;
                        params.charstring_names = names;
                    }
                }
                b"/lenIV" => {
                    len_iv = s.next_int()?;

                    if len_iv < 0 {
                        use_decryption = false;
                        len_iv = 0;
                    }
                }
                b"/WeightVector" => {
                    if let Some(wv) = s.read_float_array() {
                        params.weight_vector = Some(wv);
                    }
                }
                _ => {}
            }
        }

        Some(())
    }

    /// Returns whether this is a `MultipleMaster` font.
    pub fn is_multiple_master(&self) -> bool {
        self.params.weight_vector.is_some()
    }

    /// Returns a font transformation matrix.
    pub fn matrix(&self) -> Matrix {
        self.params.font_matrix
    }

    /// Outlines a glyph.
    pub fn outline(&self, string: &str, builder: &mut dyn OutlineBuilder) -> Option<()> {
        let data = self.params.charstrings.get(string)?;

        parse_char_string(data, &self.params, builder).ok()?;

        Some(())
    }

    /// Return the glyph name of the code point.
    pub fn code_to_string(&self, code_point: u8) -> Option<&str> {
        self.params.encoding_type.encode(code_point)
    }

    /// Returns charstring names in their original insertion order.
    pub fn charstring_names(&self) -> &[String] {
        &self.params.charstring_names
    }

    /// Returns the insertion index of a charstring by name.
    pub fn charstring_index(&self, name: &str) -> Option<u16> {
        self.params.charstring_indices.get(name).copied()
    }
}

// <https://github.com/apache/pdfbox/blob/129aafe26548c1ff935af9c55cb40a996186c35f/fontbox/src/main/java/org/apache/fontbox/pfb/PfbParser.java#L119>
fn extract_pfb_segments(pfb: &[u8]) -> Option<Cow<'static, [u8]>> {
    const START_MARKER: u8 = 0x80;
    const ASCII_MARKER: u8 = 0x01;
    const BINARY_MARKER: u8 = 0x02;
    const EOF_MARKER: u8 = 0x03;
    const PFB_HEADER_LENGTH: usize = 18;

    if pfb.len() < PFB_HEADER_LENGTH {
        return None;
    }

    let mut stream = Stream::new(pfb);
    let mut type_list = Vec::new();
    let mut barr_list = Vec::new();
    let mut total = 0;

    loop {
        let r = stream.read_byte();
        if r.is_none() && total > 0 {
            break; // EOF
        }
        let r = r?;

        if r != START_MARKER {
            return None;
        }

        let record_type = stream.read_byte()?;
        if record_type == EOF_MARKER {
            break;
        }

        if record_type != ASCII_MARKER && record_type != BINARY_MARKER {
            return None;
        }

        let size_bytes = stream.read_bytes(4)?;
        let mut size = size_bytes[0] as usize;
        size += (size_bytes[1] as usize) << 8;
        size += (size_bytes[2] as usize) << 16;
        size += (size_bytes[3] as usize) << 24;

        // PDFJS-14462: Simply abort parsing if we have an invalid size.
        let Some(ar) = stream.read_bytes(size) else {
            break;
        };

        total += size;
        type_list.push(record_type);
        barr_list.push(ar);
    }

    // We now have ASCII and binary segments. Lets arrange these so that the ASCII segments
    // come first, then the binary segments, then the last ASCII segment if it is
    // 0000... cleartomark

    let mut pfbdata = Vec::with_capacity(total);
    let mut cleartomark_segment = None;

    // copy the ASCII segments
    for i in 0..type_list.len() {
        if type_list[i] != ASCII_MARKER {
            continue;
        }

        let ar = barr_list[i];
        if i == type_list.len() - 1
            && ar.len() < 600
            && let Ok(s) = core::str::from_utf8(ar)
            && s.contains("cleartomark")
        {
            cleartomark_segment = Some(ar);
            continue;
        }
        pfbdata.extend_from_slice(ar);
    }

    // copy the binary segments
    for i in 0..type_list.len() {
        if type_list[i] != BINARY_MARKER {
            continue;
        }
        let ar = barr_list[i];
        pfbdata.extend_from_slice(ar);
    }

    if let Some(segment) = cleartomark_segment {
        pfbdata.extend_from_slice(segment);
    }

    Some(Cow::Owned(pfbdata))
}

const ND: &[u8] = b"ND";
const ND_ALT: &[u8] = b"|-";

const RD: &[u8] = b"RD";
const RD_ALT: &[u8] = b"-|";

const NP: &[u8] = b"NP";
const NP_ALT: &[u8] = b"|";

impl<'a> Stream<'a> {
    fn next_int(&mut self) -> Option<i64> {
        parse_int(core::str::from_utf8(self.next_token()?).ok()?)
    }

    #[allow(clippy::type_complexity)]
    fn parse_charstrings(
        &mut self,
        len_iv: i64,
        use_decryption: bool,
    ) -> Option<(Map<String, Vec<u8>>, Vec<String>)> {
        let mut charstrings = Map::new();
        let mut names = Vec::new();

        let mut first_glyph_name = None;
        let mut int_token = None;

        while let Some(token) = self.next_token() {
            if token == b"end" {
                return Some((charstrings, names));
            }

            if token.starts_with(b"/") {
                first_glyph_name = Some(token);
            } else if token
                .iter()
                .all(|b| matches!(*b, b'#') || b.is_ascii_digit())
            {
                int_token = parse_int(core::str::from_utf8(token).ok()?);
            } else if token == RD || token == RD_ALT {
                break;
            }
        }

        let (first_glyph_name, int_token) = (first_glyph_name?, int_token?);

        let mut is_first = true;

        loop {
            let bin_len;
            let mut glyph_name;

            if is_first {
                is_first = false;
                bin_len = int_token;
                glyph_name = first_glyph_name;

                if glyph_name.starts_with(b"/") {
                    glyph_name = &glyph_name[1..];
                }

                self.read_byte();
            } else {
                let tok = self.next_token()?;
                if tok == b"end" {
                    break;
                }

                if tok.starts_with(b"/") {
                    glyph_name = &tok[1..];
                } else {
                    glyph_name = tok;
                }

                // See PDFBOX-3979.
                let Some(len) = self.next_int() else {
                    break;
                };
                bin_len = len;
                let tok = self.next_token()?;

                if tok == RD || tok == RD_ALT {
                    self.read_byte();
                } else {
                    error!("invalid charstring in start, expected RD");

                    return None;
                }
            }

            let encrypted_bytes = self.read_bytes(bin_len as usize)?;
            let decrypted_bytes = decrypt_charstring(encrypted_bytes, len_iv, use_decryption)?;
            let name = core::str::from_utf8(glyph_name).ok()?.to_string();
            names.push(name.clone());
            charstrings.insert(name, decrypted_bytes);

            let tok = self.next_token()?;
            if tok == ND || tok == ND_ALT {
            } else if tok.starts_with(b"/") {
                // PDFJS-14462: In case there are no end tokens, go back so
                // that we can parse the next charstring in the font.
                self.move_back(tok.len());
            } else {
                error!("invalid charstring in end, expected ND, found {tok:?}");

                if charstrings.is_empty() {
                    return None;
                } else {
                    // Return what we have extracted so far.
                    break;
                }
            }
        }

        Some((charstrings, names))
    }

    fn parse_subroutines(
        &mut self,
        len_iv: i64,
        use_decryption: bool,
    ) -> Option<Map<u32, Vec<u8>>> {
        let mut subroutines = Map::new();

        let num_subrs = parse_int(core::str::from_utf8(self.next_token()?).ok()?)?;

        if num_subrs < 1 {
            return Some(subroutines);
        }

        if !self.skip_until_before(b"dup", |b| matches!(b, ND | ND_ALT | b"noaccess")) {
            return Some(subroutines);
        }

        while let Some(token) = self.next_token() {
            if matches!(token, ND | ND_ALT) {
                break;
            }

            if token == b"noaccess" {
                if self.next_token() == Some(b"def") {
                    break;
                } else {
                    error!("invalid sequence noaccess");

                    return None;
                }
            }

            if token != b"dup" {
                error!("expected dup, got token {:?} instead", &token);

                return None;
            }

            let subr_idx = self.next_int()?;
            let bin_len = self.next_int()?;

            let tok = self.next_token()?;

            if tok != RD && tok != RD_ALT {
                error!("invalid subroutine start token {tok:?}");

                return None;
            } else {
                // Whitespace
                self.read_byte();
            }

            let encrypted_bytes = self.read_bytes(bin_len as usize)?;
            subroutines.insert(
                subr_idx as u32,
                decrypt_charstring(encrypted_bytes, len_iv, use_decryption)?,
            );

            let mut tok = self.next_token()?;
            if tok == NP || tok == NP_ALT {
            } else if tok == b"noaccess" {
                tok = self.next_token()?;
                if tok == b"def" {
                    break;
                }

                if tok == b"put" {
                } else {
                    error!("invalid subroutine end {tok:?}");

                    return None;
                }
            } else {
                error!("invalid subroutine end token {tok:?}");

                return None;
            }
        }

        Some(subroutines)
    }

    fn peek_token(&mut self) -> Option<&'a [u8]> {
        self.clone().next_token()
    }

    fn next_token(&mut self) -> Option<&'a [u8]> {
        let skip_token = |st: &mut Stream<'_>| -> usize {
            let mut count = 1;
            while let Some(ch) = st.read_bytes(1) {
                if is_whitespace(ch[0]) || is_self_delim_after_token(ch[0]) {
                    st.move_back(1);
                    break;
                }

                count += 1;
            }

            count
        };

        self.skip_whitespaces();

        while let Some(ch) = self.clone().read_bytes(1) {
            let tail = self.tail()?;
            self.read_bytes(1);

            match ch[0] {
                b'%' => self.skip_line_comment(),
                b'(' => return Some(b"("),
                b'<' => {
                    if let Some(ch2) = self.read_bytes(1) {
                        if ch2[0] == b'>' {
                            return Some(b"( )");
                        } else if ch2[0] == b'<' {
                            return Some(b"<<");
                        } else {
                            return Some(b"<");
                        }
                    }
                }
                b'>' => {
                    if let Some(ch2) = self.read_bytes(1) {
                        if ch2[0] == b'>' {
                            return Some(b">>");
                        } else {
                            self.move_back(1);
                            return Some(b">");
                        }
                    }
                }
                b'[' => {
                    return Some(b"[");
                }
                b']' => {
                    return Some(b"]");
                }
                b'{' => {
                    return Some(b"{");
                }
                b'}' => {
                    return Some(b"}");
                }
                b'/' => {
                    if let Some(ch2) = self.read_bytes(1) {
                        if is_whitespace(ch2[0]) || is_self_delim_after_token(ch2[0]) {
                            let token = b"/";

                            if is_self_delim_after_token(ch2[0]) {
                                self.move_back(1);
                            }

                            return Some(token);
                        } else {
                            let count = skip_token(self);

                            return Some(&tail[0..(count + 1)]);
                        }
                    }
                }
                _ => {
                    let count = skip_token(self);
                    return Some(&tail[0..count]);
                }
            }

            self.skip_whitespaces();
        }

        None
    }

    // Note: AI generated, haven't double-checked.
    fn read_float_array(&mut self) -> Option<Vec<f32>> {
        let mut entries = Vec::new();

        if self.next_token()? != b"[" {
            return None;
        }

        while let Some(token) = self.next_token() {
            if token == b"]" {
                break;
            }
            if let Ok(s) = core::str::from_utf8(token)
                && let Ok(v) = f32::from_str(s)
            {
                entries.push(v);
            }
        }

        Some(entries)
    }

    fn read_font_matrix(&mut self) -> Option<[f32; 6]> {
        let mut entries = [0.0_f32; 6];
        let mut idx = 0;

        // Skip '[';
        self.skip_token();

        while let Some(token) = self.next_token() {
            entries[idx] = f32::from_str(core::str::from_utf8(token).ok()?).ok()?;

            idx += 1;
            if idx == 5 {
                break;
            }
        }

        // Skip `]`.
        self.skip_token();

        Some(entries)
    }

    fn read_encoding(&mut self) -> Option<EncodingType> {
        let mut map = Map::new();

        let t1 = self.next_token()?;
        let t2 = self.next_token()?;

        if t1 == b"StandardEncoding" && t2 == b"def" {
            return Some(EncodingType::Standard);
        }

        if !self.skip_until_before(b"dup", |b| matches!(b, b"def" | b"readonly")) {
            return Some(EncodingType::Custom(Arc::new(map)));
        }

        while let Some(token) = self.next_token() {
            if matches!(token, b"def" | b"readonly") {
                break;
            }

            if token != b"dup" {
                error!("Unexpected token {token:?}");

                return None;
            }

            let next = self.next_token();
            // TODO: Should other places in the parser also use `parse_int`?
            let code = parse_int(core::str::from_utf8(next?).ok()?)?;
            let glyph_name = core::str::from_utf8(&self.next_token()?[1..])
                .ok()?
                .to_string();

            if self.next_token()? != b"put" {
                error!("Unexpected token {token:?}");

                return None;
            }

            map.insert(u8::try_from(code).ok()?, glyph_name);
        }

        Some(EncodingType::Custom(Arc::new(map)))
    }

    fn skip_dict(&mut self) {
        self.skip_until(b"begin", |b| matches!(b, b"end"));
    }

    fn skip_token(&mut self) {
        self.next_token();
    }

    fn skip_line_comment(&mut self) {
        while let Some(ch) = self.read_byte() {
            if matches!(ch, b'\n' | b'\r') {
                break;
            }
        }
    }

    fn skip_until(&mut self, find: &[u8], stop: impl Fn(&[u8]) -> bool) -> bool {
        while let Some(token) = self.next_token() {
            if token == find {
                return true;
            }

            if stop(token) {
                break;
            }
        }

        false
    }

    fn skip_whitespaces(&mut self) {
        while let Some(ch) = self.peek_byte() {
            if is_whitespace(ch) {
                self.read_byte();
            } else {
                break;
            }
        }
    }

    fn skip_until_before(&mut self, find: &[u8], stop: impl Fn(&[u8]) -> bool) -> bool {
        while let Some(token) = self.peek_token() {
            if token == find {
                return true;
            }

            if self.next_token().is_none() {
                return false;
            }

            if stop(token) {
                break;
            }
        }

        false
    }
}

fn decrypt_charstring(data: &[u8], len_iv: i64, use_decryption: bool) -> Option<Vec<u8>> {
    let mut r = 4330;
    let mut cb: Copied<Iter<'_, u8>> = data.iter().copied();
    let mut decrypted = vec![];

    for _ in 0..len_iv {
        let _ = decrypt_byte(cb.next()?, &mut r, use_decryption);
    }

    for byte in cb {
        decrypted.push(decrypt_byte(byte, &mut r, use_decryption));
    }

    Some(decrypted)
}

fn is_whitespace(c: u8) -> bool {
    if c <= 32 {
        return matches!(c, b' ' | b'\n' | b'\r' | b'\t' | 0x00 | 0x0C);
    }

    false
}

fn is_self_delim_after_token(c: u8) -> bool {
    // The characters ()<>[]{}/% are special. They delimit syntactic entities
    // such as strings, procedure bodies, name literals, and comments. Any of these
    // characters terminates the entity preceding it and is not included in the entity.

    matches!(
        c,
        b'(' | b'<' | b'>' | b'[' | b']' | b'{' | b'}' | b'/' | b'%' | b')'
    )

    // This checks for self delimiters appearing after tokens. Thus there is no
    // need to check for ')'. However, char '>' can appear in keyword >>, like
    // here: /Pages 2 0 R>>. So the char '>' must end the token R.
}

#[derive(Debug, Clone)]
pub(crate) enum EncodingType {
    Standard,
    Custom(Arc<Map<u8, String>>),
}

impl EncodingType {
    pub(crate) fn encode(&self, code: u8) -> Option<&str> {
        match self {
            Self::Standard => STANDARD.get(&code).copied(),
            Self::Custom(c) => c.get(&code).map(|s| s.as_str()),
        }
    }
}

fn parse_int(str: &str) -> Option<i64> {
    if let Some(hash_idx) = str.find('#') {
        if hash_idx == 1 || hash_idx == 2 {
            // It's a radix number, like 8#40.
            let radix_str = &str[0..hash_idx];
            let number_str = &str[hash_idx + 1..];

            let radix = radix_str.parse::<u32>().ok()?;

            if (2..=36).contains(&radix) {
                i64::from_str_radix(number_str, radix).ok()
            } else {
                None
            }
        } else {
            str.parse::<i64>().ok()
        }
    } else {
        str.parse::<i64>().ok()
    }
}

#[cfg(test)]
mod tests {
    use crate::type1::stream::Stream;

    macro_rules! assert_token {
        ($content:expr, $token:expr) => {
            assert_eq!($content.next_token(), Some(&$token[..]))
        };
    }

    #[test]
    fn lexing_1() {
        let mut content = Stream::new(b"/FontInfo ");

        assert_token!(content, b"/FontInfo");
    }

    #[test]
    fn lexing_2() {
        let mut content = Stream::new(b"/version (01) readonly def");

        assert_token!(content, b"/version");
        assert_token!(content, b"(");
        assert_token!(content, b"01");
        assert_token!(content, b")");
        assert_token!(content, b"readonly");
        assert_token!(content, b"def");
    }
}
