extern crate rustbox;
extern crate memmap;
extern crate clap;

use std::error::Error;
// use std::default::Default;
// use std::cmp;
// use std::char;
use std::env;

use memmap::{Mmap, Protection};

use rustbox::{Color, RustBox, Key, Event};

use clap::{App, Arg};

fn longest_prefix<'a>(a: &'a str, b: &'a str) -> usize {
    a.chars().zip(b.chars()).take_while(|&(c1, c2)| c1 == c2).count()
}

fn header(width: usize) -> String {
    format!("{:1$}0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F", "", width + 3)
}

fn string_repeat(s: &str, n: usize) -> String {
    std::iter::repeat(s).take(n).collect::<String>()
}

fn offset(offset: usize, width: usize, trimming_offset: Option<String>) -> String {
    match trimming_offset {
        None => { format!("{:01$x}: ", offset, width) }
        Some(trimming_string) => {
            let new_fmt = format!("{:01$x}: ", offset, width);
            let n_chars_to_remove = longest_prefix(&new_fmt, &trimming_string);
            let padding = string_repeat(" ", n_chars_to_remove);
            padding + &new_fmt[n_chars_to_remove..]
        }
    }
}

fn offset_width(num_bytes: usize) -> usize {
    // +1 byte for the final EOF symbol
    // +1 for the base-16 computation
    (((num_bytes + 1) as f64).log(16.0) as usize) + 1
}

fn try_open_file(file_path: &str) -> Result<(Mmap, bool), std::io::Error> {
    let mut is_read_only = false;
    let mut file_mmap_try = Mmap::open_path(file_path, Protection::ReadWrite);
    if file_mmap_try.is_err() {
        file_mmap_try = Mmap::open_path(file_path, Protection::Read);
        is_read_only = true;
    }

    file_mmap_try.map(|x| (x, is_read_only))
}

fn standard_output(file_path: &str) {
    let file_mmap = Mmap::open_path(file_path, Protection::Read).unwrap();
    let bytes: &[u8] = unsafe { file_mmap.as_slice() };

    let offset_width = offset_width(bytes.len());
    println!("{}\n", header(offset_width));

    let mut prev_offset: String = offset(0, offset_width, None);
    let mut should_trim = true;
    print!("{}", prev_offset);
    for (idx, chunk) in bytes.chunks(16).enumerate() {
        if chunk.iter().all(|&x| x == 0) {
            should_trim = false;
            continue;
        }
        for b in chunk {
            match *b {
                // ASCII printable
                0x20u8...0x7e => { print!(".{} ", *b as char) }
                0x00 => 

                0xFF => { print!("## ") }
                _ => { print!("{:02X} ", b) }
            }
        }
        let trimming_offset = if should_trim { Some(prev_offset.clone()) } else { None };
        let new_offset = offset((idx + 1) * 16, offset_width, trimming_offset); // TODO: idx + 1 is wrong
        print!("\n{}", new_offset);
        prev_offset = new_offset;
        should_trim = true;
    }
    if should_trim { // that means just skipped a bunch of zeroes at the end

    }
    println!("Ω⸫");
}

struct EditorState {
    open_file: Option<Mmap>,
    read_only: bool,
}

fn rustbox_output(file_path: &str) {
    let rustbox = match RustBox::init(Default::default()) {
        Result::Ok(v) => v,
        Result::Err(e) => panic!("{}", e),
    };

    let mut state = EditorState { open_file: None, read_only: false };

    // rustbox.print(1, 1, rustbox::RB_BOLD, Color::White, Color::Black, "Hello, world!");
    // rustbox.print(1, 3, rustbox::RB_BOLD, Color::White, Color::Black,
    //               "Press 'q' to quit.");

    // let mut file_mmap_try = try_open_file(file_path);
    // let offset_width = offset_width(bytes.len());

    loop {
        rustbox.print(rustbox.width() - 10, 0, rustbox::RB_BOLD, Color::White, Color::Black, "HexRS");

        rustbox.present();

        match rustbox.poll_event(false).unwrap() {
            Event::KeyEvent(key) => {
                match key {
                    Key::Char('q') => { return; }
                    Key::Esc => { return; }
                    _ => { }
                }
            }
            _ => { }
        }
    }
}

fn main() {
    let matches = App::new("HexRS").version("0.1").args_from_usage(
        "-i, --interactive 'Use the interactive version instead of outputting to stdout'
         [INPUT]           'Specifies the input file to use'").get_matches();


    if let Some(file_path) = matches.value_of("INPUT") {
        if matches.is_present("interactive") {
            rustbox_output(&file_path);
        } else {
            standard_output(&file_path);
        }
    }
}

{ print!("   ") }