use rusttype::{point, Font, FontCollection, PositionedGlyph, Scale};
use std::io::Write;

// Add an allocator that lets us keep track of the system
extern crate stats_alloc;
use stats_alloc::{Region, StatsAlloc, INSTRUMENTED_SYSTEM};
use std::alloc::System;
#[global_allocator]
static GLOBAL: &StatsAlloc<System> = &INSTRUMENTED_SYSTEM;

fn main() {
    // Chinese test
    let font = include_bytes!("../resources/WenQuanYiMicroHei-01.ttf");
    let message = " 發啊你好";
    let rtl = false;
    println!();
    println!("RUSTTYPE TEST:");
    test_rusttype(font, message, rtl);
    println!("RUSTTYPE + RUSTYBUZZ TEST:");
    test_rusthbuzz(font, message);

    // Arabic test
    let font = include_bytes!("../resources/LateefRegOT.ttf");
    let message = "مرحبا بالعالم"; // "Hello, world!"
    // let message = "مممممم"; // Kerning test nonsense string
    let rtl = true;
    // let message = "agylixm";
    // let rtl = false;
    println!();
    println!("RUSTTYPE TEST:");
    test_rusttype(font, message, rtl);

    println!("RUSTTYPE + RUSTYBUZZ TEST:");
    test_rusthbuzz(font, message);
}

// fn test_fontdue(font: &[u8], message: &str, _rtl: bool) {
//     let reg = Region::new(&GLOBAL);
//     println!("start: {:#?}", reg.change());

//     // Parse it into the font type.
//     let font = fontdue::Font::from_bytes(font, fontdue::FontSettings::default()).unwrap();
//     println!("from_bytes: {:#?}", reg.change());

//     // Rasterize and get the layout metrics for the letter 'g' at 17px.
//     let (metrics, bitmap) = font.rasterize(message.chars().nth(0).unwrap(), 17.0);
//     println!("font.rasterize: {:#?}", reg.change());

//     // Used here to ensure that the value is not
//     // dropped before we check the statistics
//     ::std::mem::size_of_val(&metrics);
//     ::std::mem::size_of_val(&bitmap);
//     ::std::mem::size_of_val(&font);
// }

fn test_rusthbuzz(font: &[u8], message: &str) {
    const FB_WIDTH: usize = 520;
    const FB_HEIGHT: usize = 200;
    let mut buffer = [[0f32; FB_WIDTH]; FB_HEIGHT];

    let reg = Region::new(&GLOBAL);

    let mut buzz_font = rustybuzz::Font::from_slice(font, 0).unwrap();
    println!("buzz_font: {:#?}", reg.change());
    buzz_font.set_pixels_per_em(Some((196, 196)));

    let mut buzz_buffer = rustybuzz::UnicodeBuffer::new();
    println!("buzz_buffer: {:#?}", reg.change());

    buzz_buffer.push_str(message);
    println!("buzz_buffer_push: {:#?}", reg.change());

    let buzz_features = vec![];
    println!("buzz_features: {:#?}", reg.change());

    let glyph_buffer = rustybuzz::shape(&buzz_font, &buzz_features, buzz_buffer);
    println!("buzz_shape: {:#?}", reg.change());

    let rusttype_font = Font::from_bytes(font).unwrap_or_else(|e| {
        panic!("error constructing a FontCollection from bytes: {}", e);
    });
    println!("fonttype_collection: {:#?}", reg.change());

    let mut cursor_x = 0.0;
    let mut cursor_y = 0.0;

    let rusttype_height = 196.0;
    let rusttype_scale = rusttype::Scale {
        x: rusttype_height * 1.0,
        y: rusttype_height,
    };
    // Adapt the harfbuzz scale to rusttype's scale.
    // I'm not sure where the "1.2" comes from, but the units only *just* match.
    let harfbuzz_scale = rusttype_font.units_per_em() as f32 / rusttype_height * 1.2;

    let v_metrics = rusttype_font.v_metrics(rusttype_scale);
    let offset = point(0.0, v_metrics.ascent);

    // println!("font is {} units-per-em", rusttype_font.units_per_em());
    for (buzz_info, buzz_position) in glyph_buffer
        .glyph_infos()
        .iter()
        .zip(glyph_buffer.glyph_positions().iter())
    {
        // let codepoint_rs = char::try_from(buzz_info.codepoint).expect("invalid codepoint");
        let font_glyph = rusttype_font.glyph(rusttype::GlyphId(buzz_info.codepoint));
        let glyph_scaled = font_glyph.scaled(rusttype_scale);

        let x_offset = buzz_position.x_offset as f32 / harfbuzz_scale;
        let y_offset = buzz_position.y_offset as f32 / harfbuzz_scale;
        let x_advance = buzz_position.x_advance as f32 / harfbuzz_scale;
        let y_advance = buzz_position.y_advance as f32 / harfbuzz_scale;

        let glyph_positioned = glyph_scaled.positioned(rusttype::Point {
            x: (x_offset as f32) + offset.x,
            y: (y_offset as f32) + offset.y,
        });

        if let Some(bb) = glyph_positioned.pixel_bounding_box() {
            // println!(
            //     "Offset: {} / {}, {} / {}  {}/{}  {}/{}",
            //     x_offset, y_offset,
            //     x_advance, y_advance,
            //     cursor_x, cursor_y,
            //     bb.min.x, bb.min.y,
            // );
            glyph_positioned.draw(|x, y, weight| {
                let x = (x as f32) + ((cursor_x + x_offset) as f32) + (bb.min.x as f32);
                let y = (y as f32) + ((cursor_y + y_offset) as f32) + (bb.min.y as f32);

                if (x < 0.0)
                    || (x >= (buffer[0].len() as f32))
                    || (y < 0.0)
                    || (y >= (buffer.len() as f32))
                {
                    // println!("(x,y) out of bounds: ({}, {})", x, y);
                    return;
                }
                let x = x as usize;
                let y = y as usize;
                // if y < buffer.len() && x < buffer[0].len() {
                buffer[y][x] += weight;
                // }
            });
        }
        cursor_x += x_advance;
        cursor_y += y_advance;
    }

    // println!("Buffer: {:?}", buffer);
    for y in (0..buffer.len() - 4).step_by(4) {
        for x in (0..buffer[y].len() - 2).step_by(2) {
            let mut offset = 0;
            let threshold = 0.5;

            if buffer[y + 0][x + 0] > threshold {
                offset |= 1 << 0;
            }
            if buffer[y + 1][x + 0] > threshold {
                offset |= 1 << 1;
            }
            if buffer[y + 2][x + 0] > threshold {
                offset |= 1 << 2;
            }

            if buffer[y + 0][x + 1] > threshold {
                offset |= 1 << 3;
            }
            if buffer[y + 1][x + 1] > threshold {
                offset |= 1 << 4;
            }
            if buffer[y + 2][x + 1] > threshold {
                offset |= 1 << 5;
            }

            if buffer[y + 3][x + 0] > threshold {
                offset |= 1 << 6;
            }
            if buffer[y + 3][x + 1] > threshold {
                offset |= 1 << 7;
            }
            let pixel_br = std::char::from_u32(0x2800 + offset).unwrap();
            print!("{}", pixel_br);
        }
        println!();
    }
}

fn test_rusttype(font: &[u8], message: &str, rtl: bool) {
    let reg = Region::new(&GLOBAL);
    let collection = FontCollection::from_bytes(font).unwrap_or_else(|e| {
        panic!("error constructing a FontCollection from bytes: {}", e);
    });
    println!("collection: {:#?}", reg.change());

    let font = collection
        .into_font() // only succeeds if collection consists of one font
        .unwrap_or_else(|e| {
            panic!("error turning FontCollection into a Font: {}", e);
        });

    // Desired font pixel height
    let height: f32 = 20.0; // to get 80 chars across (fits most terminals); adjust as desired
    let pixel_height = height.ceil() as usize;

    // 2x scale in x direction to counter the aspect ratio of monospace characters.
    let scale = Scale {
        x: height * 2.0,
        y: height,
    };

    // The origin of a line of text is at the baseline (roughly where
    // non-descending letters sit). We don't want to clip the text, so we shift
    // it down with an offset when laying it out. v_metrics.ascent is the
    // distance between the baseline and the highest edge of any glyph in
    // the font. That's enough to guarantee that there's no clipping.
    let v_metrics = font.v_metrics(scale);
    let offset = point(0.0, v_metrics.ascent);
    println!("metrics: {:#?}", reg.change());

    // Glyphs to draw for "RustType". Feel free to try other strings.
    let glyphs: Vec<PositionedGlyph<'_>> = font.layout(message, scale, offset).collect();
    println!("glyphs: {:#?}", reg.change());

    // Find the most visually pleasing width to display
    let width = glyphs
        .iter()
        .rev()
        .map(|g| g.position().x as f32 + g.unpositioned().h_metrics().advance_width)
        .next()
        .unwrap_or(0.0)
        .ceil() as usize;

    println!("width: {}, height: {}", width, pixel_height);

    // Rasterise directly into ASCII art.
    let mut pixel_data = vec!['@'; width * pixel_height];
    let mapping = [' ', '░', '▒', '▓', '█']; // The approximation of greyscale
    let mapping_scale = (mapping.len() - 1) as f32;
    for g in glyphs {
        if let Some(bb) = g.pixel_bounding_box() {
            g.draw(|x, y, v| {
                // v should be in the range 0.0 to 1.0
                let i = (v * mapping_scale + 0.5) as usize;
                // so something's wrong if you get $ in the output.
                let c = mapping.get(i).cloned().unwrap_or(' ');
                let x = x as i32 + bb.min.x;
                let y = y as i32 + bb.min.y;
                // There's still a possibility that the glyph clips the boundaries of the bitmap
                if x >= 0 && x < width as i32 && y >= 0 && y < pixel_height as i32 {
                    let x = if rtl {
                        width - (x as usize) - 1
                    } else {
                        x as usize
                    };
                    let y = y as usize;
                    pixel_data[(x + y * width)] = c;
                }
            })
        }
    }
    println!("rasterization: {:#?}", reg.change());

    // Print it out
    let stdout = ::std::io::stdout();
    let mut handle = stdout.lock();
    for j in 0..pixel_height {
        for px in pixel_data[j * width..(j + 1) * width].iter() {
            let mut b = [0; 4];
            px.encode_utf8(&mut b);
            handle.write_all(&b).unwrap();
        }
        // handle
        //     .write_all(&b)
        //     .unwrap();
        handle.write_all(b"\n").unwrap();
    }
}
