#![allow(dead_code)]
#![feature(proc_macro_span)]
#![warn(
    clippy::correctness,
    clippy::nursery,
    clippy::unnecessary_cast,
    clippy::all,
    clippy::suspicious,
    clippy::perf,
    rustdoc::missing_errors_doc,
    rustdoc::missing_panics_doc
)]

use proc_macro::{Span, TokenStream};
use quote::quote;
use std::path::PathBuf;
use syn::{LitStr, parse_macro_input};
extern crate proc_macro;

#[proc_macro]
/// Expects the file path to be relative to the current file so this works
/// similarly to the standard Rust include! macros.
pub fn include_font(item: TokenStream) -> TokenStream {
    let span = Span::call_site();
    let source_file = span.source_file();

    if !source_file.is_real() {
        panic!(
            "We can't handle finding files if the source file does not exist. TODO: Can we?"
        )
    }

    let filename = parse_macro_input!(item as LitStr);
    let source_filepath: PathBuf = source_file.path();
    let file_path = format!(
        "{}/{}",
        source_filepath.parent()
            .unwrap_or_else(|| panic!("Expected to find the calling source file in a folder like src! Got: {}", source_filepath.display())).display(),
        filename.value()
    );

    let font_bytes = match std::fs::read(file_path) {
        Ok(bytes) => bytes,
        Err(why) => panic!("{}", why),
    };

    let font_data = match FontBuilder::load(font_bytes) {
        Ok(font) => font.data,
        Err(why) => panic!("{}", why),
    };

    quote!(
        [
            #(
                [#(#font_data),*]
            ),*
        ]
    )
    .into()
}

struct Font([[u8; 16]; 512]);

struct FontData {
    width: u8,
    height: u8,
    length: u16,
    pub data: [[u8; 16]; 512],
}
enum FontBuilder {
    Psf1(FontData),
    Psf2(FontData),
}

impl FontBuilder {
    const PSF1_MAGIC: u16 = 0x3604;
    const PSF2_MAGIC: u32 = 0x72b54a86;

    fn revision(data: &[u8]) -> u8 {
        if (data[0] as u16) << 8 | data[1] as u16 == Self::PSF1_MAGIC {
            1
        } else if (data[0] as u32) << 24
            | (data[1] as u32) << 16
            | (data[2] as u32) << 8
            | data[3] as u32
            == Self::PSF2_MAGIC
        {
            2
        } else {
            0
        }
    }

    pub fn load(data: Vec<u8>) -> Result<FontData, &'static str> {
        match Self::revision(&data[0..4]) {
            1 => Self::parse_psf1(&data),
            2 => Self::parse_psf2(&data),
            _ => panic!("invalid font revision result"),
        }
    }

    fn parse_psf1(data: &[u8]) -> Result<FontData, &'static str> {
        let mode = data[2];
        let size = data[3];

        let has_512_glyphs = (mode & 0x01) != 0;
        let mut glyphs = [[0; 16]; 512];
        let glyph_count = if has_512_glyphs { 512 } else { 256 };

        for i in 0..(glyph_count as usize) {
            let mut buff: [u8; 16] = [0; 16];
            for j in 0..(size as usize) {
                buff[j] = data[4 + i * (size as usize) + j];
            }
            glyphs[i] = buff;
        }

        Ok(FontData {
            width: 8,
            height: size,
            length: glyph_count,
            data: glyphs,
        })
    }

    const fn parse_psf2(_data: &[u8]) -> Result<FontData, &'static str> {
        Err("PSF2 support is not implemented yet!")
    }
}
