#[cfg(test)]
mod tests;

use bitstream_io::{BigEndian, BitRead, BitReader};
use std::io::{Error as IoError, ErrorKind};
use std::io::{Read, Result};

#[derive(Clone, Copy, Debug)]
pub enum Leaf {
    Byte(u8),
    Flagged(u8),
}

#[derive(Clone, Copy, Debug)]
pub enum Node {
    Leaf(Leaf),
    Index(u16),
}

fn set(btree: &mut Vec<Option<Node>>, index: usize, node: Node) {
    while btree.len() <= index {
        btree.push(None);
    }
    btree[index] = Some(node);
}

fn read_leaf<R: BitRead>(reader: &mut R) -> Result<Leaf> {
    if reader.read_bit()? {
        let value: u8 = reader.read_var(5)?;
        Ok(Leaf::Flagged(value))
    } else {
        let value: u8 = reader.read_var(8)?;
        Ok(Leaf::Byte(value))
    }
}

fn get_leaf<R: BitRead>(reader: &mut R, btree: &[Node]) -> Result<Leaf> {
    /* Navigate the tree until we find a leaf. */
    let mut index = 0;
    loop {
        /* Parse the next bit, if it is 1, take the right branch (advance the tree),
         * take the left branch otherwise. */
        index += reader.read_bit()? as usize;

        match btree[index] {
            Node::Leaf(Leaf::Flagged(0b11111)) => return read_leaf(reader),
            Node::Leaf(l) => return Ok(l),
            Node::Index(i) => index = i as usize,
        }
    }
}

fn read_btree<R: BitRead>(reader: &mut R) -> Result<Vec<Node>> {
    let mut btree: Vec<Option<Node>> = vec![];
    let mut btree_node_stack: Vec<u16> = vec![];

    let mut use_right_branch = false;
    let mut btree_branch_i = 0;
    let mut btree_node_i = 0;
    loop {
        let mut bit = reader.read_bit()?;

        /* Parse branch nodes until we find a leaf. */
        while bit {
            /* Get the next node. */
            btree_branch_i += 2;
            if use_right_branch {
                /* Store the next node in the right branch of the current node. */
                set(
                    &mut btree,
                    btree_node_i as usize + 1,
                    Node::Index(btree_branch_i),
                );
                use_right_branch = false;
            } else {
                btree_node_stack.push(btree_node_i);
                /* Store the next node in the left branch of the current node. */
                set(
                    &mut btree,
                    btree_node_i as usize,
                    Node::Index(btree_branch_i),
                );
            }
            btree_node_i = btree_branch_i;

            bit = reader.read_bit()?;
        }

        /* Parse the leaf node. */
        let leaf = read_leaf(reader)?;
        if use_right_branch {
            set(&mut btree, btree_node_i as usize + 1, Node::Leaf(leaf));
            if let Some(i) = btree_node_stack.pop() {
                btree_node_i = i;
            } else {
                break;
            }
        } else {
            set(&mut btree, btree_node_i as usize, Node::Leaf(leaf));
            use_right_branch = true;
        }
    }

    btree
        .iter()
        .map(|node| node.ok_or(IoError::new(ErrorKind::InvalidInput, "FIXME")))
        .collect()
}

pub fn uncompress<R: Read>(reader: R) -> Result<Vec<u8>> {
    let mut reader = BitReader::endian(reader, BigEndian);

    // The first bit is ignored for some reason.
    reader.skip(1)?;

    let btree = read_btree(&mut reader)?;

    const CHUNK_SIZE: i16 = 0x400;
    let mut dst: Vec<u8> = vec![];
    let mut iter_j = CHUNK_SIZE - 1;
    while iter_j >= 0 {
        match get_leaf(&mut reader, &btree)? {
            Leaf::Byte(byte) => {
                dst.push(byte);
            }
            Leaf::Flagged(flagged) => {
                /* We got a special value, maybe for LZ77-compressed data?
                 * The leaf minus the flag is the length of the data to write - 3.
                 * We then retrieve a value from the stream, it is the offset from the
                 * current destination to copy data from.
                 */

                /* https://algo.developpez.com/actu/308570/Comprendre-l-algorithme-de-compressio n-LZ77-un-billet-de-fbonhomm/ */

                /* The leaf minus the flag is the length of data to copy - 3. */
                let length = flagged as i16 + 3;
                iter_j -= length - 1;

                let mut tmp_index = match get_leaf(&mut reader, &btree)? {
                    Leaf::Byte(byte) => dst.len() - byte as usize - 1,
                    Leaf::Flagged(flagged) => dst.len() - flagged as usize - 0x100 - 1,
                };

                for _ in 0..length {
                    dst.push(dst[tmp_index]);
                    tmp_index += 1;
                }
            }
        }

        iter_j -= 1;
    }

    Ok(dst)
}
