//! Glyphs used for most text boxes.
//!
//! Glyphs are 1 bit per pixels and can be either 16×16 pixels or 8×16 pixels. If they are 8×16,
//! they are organized as two consecutive 8×16 columns. That results to either two or four 8×8px
//! tiles, organized top to bottom first, then left to right.
//!
//! Glyphs are stored as a byte defining their size and compression methods, followed by a variable
//! number of bytes representing the compressed pixels for each column. Each column can use a
//! different encoding from the other.
//!
//! In practice, the English glyphs aren't compressed and don't use the xor encoding. Most of them
//! only use a single column, with some exceptions such as hieroglyphs. On the other hand, the
//! Japanese glyphs are all two columns wide and make extensive use of compression.
//!
//! # The format description byte
//!
//! The format description byte is laid out as follows:
//!
//! | Bit | Description |
//! |-----|-------------|
//! | 7   | Whether the glyph is 8×16 and hence only has a single column, rather than two. |
//! | 6   | Unused. |
//! | 5   | Whether the second column uses vertical xor encoding, if any. |
//! | 4-3 | The second column's [compression](Compression), if any. |
//! | 2   | Whether the first column uses vertical xor encoding. |
//! | 1-0 | The first column's [compression](Compression). |
//!
//! # Compression
//!
//! The compression method used by a glyph's column. Too learn more about the available compression
//! methods, see [`Compression`].
//!
//! # Vertical xor encoding
//!
//! When used, each of the 16 rows of the colum is modified by xor-ing it with the previous one,
//! consecutevely. This takes advantage of the fact that glyphs can be repetitive vertically to only
//! store a few rows and copying them over via this method.
//!
//! This encoding is generally used in conjunction of [`Compression::Length`] or
//! [`Compression::Pattern`].
//!
//! # The Japanese and English glyph sets
//!
//! There are two glyph sets, one for Japanese and another for English. Glyphs of each set are
//! reached through indexed tables, one table for each set.
//!
//! Japanese glyphs they are heavily compressed using various techniques, so each of them take an
//! inconsistent amout of memory. Because of this and because they are tightly packed, it isn't
//! possible to guess a glyph's location. glyphs can also occasionally have unused bytes between
//! them, so we can't just assume the end of a glyph is the start of another one. Hence, the indexed
//! table is the only way to locate the Japanese glyphs.
//!
//! English glyphs on the other hand aren't compressed at all, and they all have the same size
//! except for the last few which are two-columns wide instead of one. While most of the English
//! glyphs are reachable via the indexed table, the offsets starting from 0x81 are wrong, making the
//! glyphs starting from there uneachable. Luckily, the glyphs are tightly packed with no unused
//! bits in between, so they can be read one after the other and their offsets can we guessed to
//! some extent once we know that.
//!
//! <div class="warning">I assume this is how the game reaches the last glyphs, by computing their
//! location rather than by using the table, but it must be checked.</div>
//!
//! The English glyph set matches the ASCII encoding, with a few exceptions:
//! - Glyphs `0x00` to `0x1f` won't be treated like the matching control characters in ASCII, but
//!   like the `0x20` space character, including the null, newline and carriage return characters.
//! - Glyph 0x7f isn't the delete control character but the copyright character.
//! - Additional characters 0x80 to 0x82 are hieroglyphs, and are two-columns wide.

use crate::gfx::{Bpp, Gfx};
use num_enum::TryFromPrimitive;
use std::convert::TryFrom;
use std::fs::File;
use std::io::{BufReader, Read, Result as IoResult, Seek, SeekFrom, Write};
use std::path::Path;

#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, TryFromPrimitive)]
#[repr(u8)]
pub enum Compression {
    /// The data isn't compressed, just read and write the next 16 bytes.
    Uncompressed,
    /// The data is a stream of bytes in the `byyyyxxx` format, each encoding a glyph's pixel,
    /// terminated when a byte has its `b` bit set. The `y` and `x` bits encode the coordinates of
    /// the pixel to set.
    ///
    /// It's only used for a single glyph: the Japanese glyph 126 at 0x07eb92. Unless I
    /// implemented this wrongly, the glyph seems broken and is likely unused.
    Pixels,
    /// The first byte is in the `llllssss` format, where the `s` bits are the index of the first
    /// row to draw, and the `l` bits are the number of rows to draw. The rows to draw are stored
    /// after this first byte.
    Length,
    /// The first byte is a little endian bit pattern. For each bit, if it is set, we must read a
    /// byte and write it, otherwise we write an empty row. This lets us fill the height rows of a
    /// tile. This is done twice to draw the column's two tiles.
    Pattern,
}

/// Reads a glyph.
///
/// It will read either one or two 8x16 columns one after the other. The left column comes first.
///
/// Returns the size that was written.
#[doc(hidden)]
fn read_glyph<R, W>(reader: &mut R, writer: &mut W) -> IoResult<usize>
where
    R: Read,
    W: Write,
{
    let mut byte = [0];
    reader.read_exact(&mut byte)?;
    let mut flags = byte[0];

    // Narrow glyphs are two tiles one on top of the other, from top to bottom.
    // Wide glyphs are two narrow glyphs side by side.
    let narrow = (flags & 0b10000000) != 0;
    flags &= 0b01111111;
    let size = if narrow { 16 } else { 32 };

    let columns = if narrow { 1 } else { 2 };
    for _column in 0..columns {
        let mut rows = vec![0u8; 16];

        let compression = Compression::try_from(flags & 0b00000011).unwrap();
        let xor = (flags & 0b00000100) != 0;
        flags >>= 3;

        match compression {
            Compression::Uncompressed => {
                reader.read_exact(&mut rows)?;
            }
            Compression::Pixels => 'inner: loop {
                let mut byte = [0];
                reader.read_exact(&mut byte)?;
                let byte = byte[0];

                let done = (byte & 0b10000000) != 0;
                if done {
                    break 'inner;
                }

                let x = byte & 7;
                let y = (byte >> 3) as usize;
                rows[y] |= 1 << x;
            },
            Compression::Length => {
                let mut byte = [0];
                reader.read_exact(&mut byte)?;
                let start = (byte[0] & 0xf) as usize;
                let end = start + (byte[0] >> 4) as usize;

                reader.read_exact(&mut rows[start..end])?;
            }
            Compression::Pattern => {
                for i in 0..2 {
                    let mut byte = [0];
                    reader.read_exact(&mut byte)?;
                    let pattern = byte[0];

                    for j in 0..8 {
                        let _row = if (pattern & (1 << j)) != 0 {
                            let index = i * 8 + j;
                            reader.read_exact(&mut rows[index..(index + 1)])?;
                        };
                    }
                }
            }
        }

        if xor {
            let mut row = 0u8;
            for i in 0..16 {
                // Read what has been write and modify it somehow.
                row ^= rows[i];
                rows[i] = row
            }
        }

        writer.write(&rows)?;
    }

    Ok(size)
}

/// Reads a glyph and arrange it into a 16×16 image.
///
/// If the glyph is 8×16, it will be aligned to the left.
#[doc(hidden)]
fn read_drawable_glyph<R, W>(reader: &mut R, writer: &mut W) -> IoResult<()>
where
    R: Read,
    W: Write,
{
    let mut data = Vec::new();
    let _ = read_glyph(reader, &mut data)?;

    let (left, right) = match data.len() {
        16 => (data, vec![0u8; 16]),
        32 => {
            let right = data.split_off(16);
            (data, right)
        }
        _ => unreachable!(),
    };
    let data: Vec<u8> = std::iter::zip(left, right)
        .map(|(a, b)| [a, b])
        .flatten()
        .collect();

    writer.write(&data)?;

    Ok(())
}

/// Reads a glyph and arrange it into a 16×16 image.
///
/// If the glyph is 8×16, it will be aligned to the left.
#[doc(hidden)]
fn read_gfx<R>(reader: &mut R) -> IoResult<Gfx>
where
    R: Read,
{
    let mut pixels = Vec::new();
    read_drawable_glyph(reader, &mut pixels)?;
    Ok(Gfx::new(pixels, Bpp::One))
}

#[derive(Clone, Copy, Debug)]
#[doc(hidden)]
struct GlyphRef {
    offset: u32,
}

#[doc(hidden)]
fn read_glyph_refs(file: &File, offset: u32, length: u32) -> IoResult<Vec<GlyphRef>> {
    let mut reader = BufReader::new(file);
    reader.seek(SeekFrom::Start(offset as u64))?;
    let mut buffer = Vec::with_capacity(length as usize);
    reader.take(length as u64).read_to_end(&mut buffer)?;

    Ok(buffer
        .chunks_exact(2)
        .map(|glyph_ref| GlyphRef {
            offset: offset + u16::from_be_bytes([glyph_ref[0], glyph_ref[1]]) as u32,
        })
        .collect())
}

/// Extracts the glyphs.
///
/// This extracts the Japanese and english glyph sets, and draws each set as a one bit per pixel PNG
/// image. Each glyph is drawn in a left-aligned 16×16 pixels square, with white for the glyph and
/// black for the background. Glyphs in a set are drawn one on top of the other, in a continuous
/// vertical stream.
///
/// The glyphs are extracted into `data/src/glyph/`.
pub fn extract(file: &File) -> IoResult<()> {
    let glyph_dir = Path::new("data/src/glyph");
    std::fs::create_dir_all(&glyph_dir)?;

    // The following reads glyphs though the table and draws them in a single image.
    {
        const JA_GLYPHS_TABLE_OFFSET: u32 = 0x07de72;
        const JA_GLYPHS_TABLE_SPAN: u32 = 0x392;

        let glyph_refs = read_glyph_refs(file, JA_GLYPHS_TABLE_OFFSET, JA_GLYPHS_TABLE_SPAN)?;

        std::fs::write(
            glyph_dir.join("glyph_ja.png"),
            glyph_refs
                .iter()
                .map(|glyph_ref| {
                    assert!(glyph_ref.offset >= JA_GLYPHS_TABLE_OFFSET + JA_GLYPHS_TABLE_SPAN);
                    let mut reader = BufReader::new(file);
                    reader.seek(SeekFrom::Start(glyph_ref.offset as u64))?;
                    read_gfx(&mut reader)
                })
                .collect::<IoResult<Vec<Gfx>>>()?
                .iter_mut()
                .reduce(|acc, mut e| acc.reduce(&mut e))
                .unwrap()
                .png(2)?,
        )?;
    }

    // The following reads glyphs starting from the first one and draws them in a single image.
    {
        const EN_GLYPHS_TABLE_OFFSET: u32 = 0x155000;
        const EN_GLYPHS_TABLE_SPAN: u32 = 0x400;
        const EN_GLYPHS_OFFSET: u32 = EN_GLYPHS_TABLE_OFFSET + EN_GLYPHS_TABLE_SPAN;
        // FIXME While 138 glyphs seem available, the last few seem wrong and unused.
        const EN_GLYPHS_COUNT: u32 = 137;

        let mut reader = BufReader::new(file);
        reader.seek(SeekFrom::Start(EN_GLYPHS_OFFSET as u64))?;

        std::fs::write(
            glyph_dir.join("glyph_en.png"),
            (0..EN_GLYPHS_COUNT)
                .map(|_| read_gfx(&mut reader))
                .collect::<IoResult<Vec<Gfx>>>()?
                .iter_mut()
                .reduce(|acc, mut e| acc.reduce(&mut e))
                .unwrap()
                .png(2)?,
        )?;
    }

    Ok(())
}
