/* Copyright 2010 Google Inc. All Rights Reserved.

   Distributed under MIT license.
   See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
*/

/* Entropy encoding (Huffman) utilities. */
use core::cmp::max;

#[derive(Clone, Copy, Default)]
pub struct HuffmanTree {
    pub total_count_: u32,
    pub index_left_: i16,
    pub index_right_or_value_: i16,
}

impl HuffmanTree {
    pub fn new(count: u32, left: i16, right: i16) -> Self {
        Self {
            total_count_: count,
            index_left_: left,
            index_right_or_value_: right,
        }
    }
}

pub fn BrotliSetDepth(p0: i32, pool: &mut [HuffmanTree], depth: &mut [u8], max_depth: i32) -> bool {
    let mut stack: [i32; 16] = [0; 16];
    let mut level: i32 = 0i32;
    let mut p: i32 = p0;
    stack[0] = -1i32;
    loop {
        if (pool[(p as usize)]).index_left_ as i32 >= 0i32 {
            level += 1;
            if level > max_depth {
                return false;
            }
            stack[level as usize] = (pool[(p as usize)]).index_right_or_value_ as i32;
            p = (pool[(p as usize)]).index_left_ as i32;
            {
                continue;
            }
        } else {
            let pp = pool[(p as usize)];
            depth[((pp).index_right_or_value_ as usize)] = level as u8;
        }
        while level >= 0i32 && (stack[level as usize] == -1i32) {
            level -= 1;
        }
        if level < 0i32 {
            return true;
        }
        p = stack[level as usize];
        stack[level as usize] = -1i32;
    }
}

pub trait HuffmanComparator {
    fn Cmp(&self, a: &HuffmanTree, b: &HuffmanTree) -> bool;
}
pub struct SortHuffmanTree {}
impl HuffmanComparator for SortHuffmanTree {
    fn Cmp(&self, v0: &HuffmanTree, v1: &HuffmanTree) -> bool {
        if v0.total_count_ != v1.total_count_ {
            v0.total_count_ < v1.total_count_
        } else {
            v0.index_right_or_value_ > v1.index_right_or_value_
        }
    }
}
pub fn SortHuffmanTreeItems<Comparator: HuffmanComparator>(
    items: &mut [HuffmanTree],
    n: usize,
    comparator: Comparator,
) {
    static gaps: [usize; 6] = [132, 57, 23, 10, 4, 1];
    if n < 13 {
        for i in 1..n {
            let mut tmp: HuffmanTree = items[i];
            let mut k: usize = i;
            let mut j: usize = i.wrapping_sub(1);
            while comparator.Cmp(&mut tmp, &mut items[j]) {
                items[k] = items[j];
                k = j;
                if {
                    let _old = j;
                    j = j.wrapping_sub(1);
                    _old
                } == 0
                {
                    break;
                }
            }
            items[k] = tmp;
        }
    } else {
        let mut g: i32 = if n < 57usize { 2i32 } else { 0i32 };
        while g < 6i32 {
            {
                let gap: usize = gaps[g as usize];
                for i in gap..n {
                    let mut j: usize = i;
                    let mut tmp: HuffmanTree = items[i];
                    while j >= gap && (comparator.Cmp(&mut tmp, &mut items[j.wrapping_sub(gap)])) {
                        {
                            items[j] = items[j.wrapping_sub(gap)];
                        }
                        j = j.wrapping_sub(gap);
                    }
                    items[j] = tmp;
                }
            }
            g += 1;
        }
    }
}

/* This function will create a Huffman tree.

The catch here is that the tree cannot be arbitrarily deep.
Brotli specifies a maximum depth of 15 bits for "code trees"
and 7 bits for "code length code trees."

count_limit is the value that is to be faked as the minimum value
and this minimum value is raised until the tree matches the
maximum length requirement.

This algorithm is not of excellent performance for very long data blocks,
especially when population counts are longer than 2**tree_limit, but
we are not planning to use this with extremely long blocks.

See https://en.wikipedia.org/wiki/Huffman_coding */
pub fn BrotliCreateHuffmanTree(
    data: &[u32],
    length: usize,
    tree_limit: i32,
    tree: &mut [HuffmanTree],
    depth: &mut [u8],
) {
    let sentinel = HuffmanTree::new(u32::MAX, -1, -1);
    let mut count_limit = 1u32;
    'break1: loop {
        {
            let mut n: usize = 0usize;
            let mut i: usize;
            let mut j: usize;
            let mut k: usize;
            i = length;
            while i != 0usize {
                i = i.wrapping_sub(1);
                if data[i] != 0 {
                    let count: u32 = max(data[i], count_limit);
                    tree[n] = HuffmanTree::new(count, -1, i as i16);
                    n = n.wrapping_add(1);
                }
            }
            if n == 1 {
                depth[((tree[0]).index_right_or_value_ as usize)] = 1u8;
                {
                    break 'break1;
                }
            }
            SortHuffmanTreeItems(tree, n, SortHuffmanTree {});
            tree[n] = sentinel;
            tree[n.wrapping_add(1)] = sentinel;
            i = 0usize;
            j = n.wrapping_add(1);
            k = n.wrapping_sub(1);
            while k != 0usize {
                {
                    let left: usize;
                    let right: usize;
                    if (tree[i]).total_count_ <= (tree[j]).total_count_ {
                        left = i;
                        i = i.wrapping_add(1);
                    } else {
                        left = j;
                        j = j.wrapping_add(1);
                    }
                    if (tree[i]).total_count_ <= (tree[j]).total_count_ {
                        right = i;
                        i = i.wrapping_add(1);
                    } else {
                        right = j;
                        j = j.wrapping_add(1);
                    }
                    {
                        let j_end: usize = (2usize).wrapping_mul(n).wrapping_sub(k);
                        (tree[j_end]).total_count_ = (tree[left])
                            .total_count_
                            .wrapping_add((tree[right]).total_count_);
                        (tree[j_end]).index_left_ = left as i16;
                        (tree[j_end]).index_right_or_value_ = right as i16;
                        tree[j_end.wrapping_add(1)] = sentinel;
                    }
                }
                k = k.wrapping_sub(1);
            }
            if BrotliSetDepth(
                (2usize).wrapping_mul(n).wrapping_sub(1) as i32,
                tree,
                depth,
                tree_limit,
            ) {
                break 'break1;
            }
        }
        count_limit = count_limit.wrapping_mul(2);
    }
}
pub fn BrotliOptimizeHuffmanCountsForRle(
    mut length: usize,
    counts: &mut [u32],
    good_for_rle: &mut [u8],
) {
    let mut nonzero_count: usize = 0usize;
    let mut stride: usize;
    let mut limit: usize;
    let mut sum: usize;
    let streak_limit: usize = 1240usize;
    for i in 0usize..length {
        if counts[i] != 0 {
            nonzero_count = nonzero_count.wrapping_add(1);
        }
    }
    if nonzero_count < 16usize {
        return;
    }
    while length != 0usize && (counts[length.wrapping_sub(1)] == 0u32) {
        length = length.wrapping_sub(1);
    }
    if length == 0usize {
        return;
    }
    {
        let mut nonzeros: usize = 0usize;
        let mut smallest_nonzero: u32 = (1i32 << 30) as u32;
        for i in 0usize..length {
            if counts[i] != 0u32 {
                nonzeros = nonzeros.wrapping_add(1);
                if smallest_nonzero > counts[i] {
                    smallest_nonzero = counts[i];
                }
            }
        }
        if nonzeros < 5usize {
            return;
        }
        if smallest_nonzero < 4u32 {
            let zeros: usize = length.wrapping_sub(nonzeros);
            if zeros < 6 {
                for i in 1..length.wrapping_sub(1) {
                    if counts[i - 1] != 0 && counts[i] == 0 && counts[i + 1] != 0 {
                        counts[i] = 1;
                    }
                }
            }
        }
        if nonzeros < 28usize {
            return;
        }
    }
    for rle_item in good_for_rle.iter_mut() {
        *rle_item = 0;
    }
    {
        let mut symbol: u32 = counts[0];
        let mut step: usize = 0usize;
        for i in 0..=length {
            if i == length || counts[i] != symbol {
                if symbol == 0u32 && (step >= 5usize) || symbol != 0u32 && (step >= 7usize) {
                    for k in 0usize..step {
                        good_for_rle[i.wrapping_sub(k).wrapping_sub(1)] = 1u8;
                    }
                }
                step = 1;
                if i != length {
                    symbol = counts[i];
                }
            } else {
                step = step.wrapping_add(1);
            }
        }
    }
    stride = 0usize;
    limit = (256u32)
        .wrapping_mul((counts[0]).wrapping_add(counts[1]).wrapping_add(counts[2]))
        .wrapping_div(3)
        .wrapping_add(420) as usize;
    sum = 0usize;
    for i in 0..=length {
        if i == length
            || good_for_rle[i] != 0
            || i != 0usize && (good_for_rle[i.wrapping_sub(1)] != 0)
            || ((256u32).wrapping_mul(counts[i]) as usize)
                .wrapping_sub(limit)
                .wrapping_add(streak_limit)
                >= (2usize).wrapping_mul(streak_limit)
        {
            if stride >= 4usize || stride >= 3usize && (sum == 0usize) {
                let mut count: usize = sum
                    .wrapping_add(stride.wrapping_div(2))
                    .wrapping_div(stride);
                if count == 0usize {
                    count = 1;
                }
                if sum == 0usize {
                    count = 0usize;
                }
                for k in 0usize..stride {
                    counts[i.wrapping_sub(k).wrapping_sub(1)] = count as u32;
                }
            }
            stride = 0usize;
            sum = 0usize;
            if i < length.wrapping_sub(2) {
                limit = (256u32)
                    .wrapping_mul(
                        (counts[i])
                            .wrapping_add(counts[i.wrapping_add(1)])
                            .wrapping_add(counts[i.wrapping_add(2)]),
                    )
                    .wrapping_div(3)
                    .wrapping_add(420) as usize;
            } else if i < length {
                limit = (256u32).wrapping_mul(counts[i]) as usize;
            } else {
                limit = 0usize;
            }
        }
        stride = stride.wrapping_add(1);
        if i != length {
            sum = sum.wrapping_add(counts[i] as usize);
            if stride >= 4usize {
                limit = (256usize)
                    .wrapping_mul(sum)
                    .wrapping_add(stride.wrapping_div(2))
                    .wrapping_div(stride);
            }
            if stride == 4usize {
                limit = limit.wrapping_add(120);
            }
        }
    }
}

pub(crate) fn decide_over_rle_use(depth: &[u8], length: usize) -> (bool, bool) {
    let mut total_reps_zero: usize = 0usize;
    let mut total_reps_non_zero: usize = 0usize;
    let mut count_reps_zero: usize = 1;
    let mut count_reps_non_zero: usize = 1;
    let mut i: usize;
    i = 0usize;
    while i < length {
        let value: u8 = depth[i];
        let mut reps: usize = 1;
        let mut k: usize;
        k = i.wrapping_add(1);
        while k < length && (depth[k] as i32 == value as i32) {
            {
                reps = reps.wrapping_add(1);
            }
            k = k.wrapping_add(1);
        }
        if reps >= 3usize && (value as i32 == 0i32) {
            total_reps_zero = total_reps_zero.wrapping_add(reps);
            count_reps_zero = count_reps_zero.wrapping_add(1);
        }
        if reps >= 4usize && (value as i32 != 0i32) {
            total_reps_non_zero = total_reps_non_zero.wrapping_add(reps);
            count_reps_non_zero = count_reps_non_zero.wrapping_add(1);
        }
        i = i.wrapping_add(reps);
    }
    let use_rle_for_non_zero = total_reps_non_zero > count_reps_non_zero.wrapping_mul(2);
    let use_rle_for_zero = total_reps_zero > count_reps_zero.wrapping_mul(2);

    (use_rle_for_non_zero, use_rle_for_zero)
}

fn Reverse(v: &mut [u8], mut start: usize, mut end: usize) {
    end = end.wrapping_sub(1);
    while start < end {
        v.swap(start, end);
        start = start.wrapping_add(1);
        end = end.wrapping_sub(1);
    }
}

fn BrotliWriteHuffmanTreeRepetitions(
    previous_value: u8,
    value: u8,
    mut repetitions: usize,
    tree_size: &mut usize,
    tree: &mut [u8],
    extra_bits_data: &mut [u8],
) {
    if previous_value as i32 != value as i32 {
        tree[*tree_size] = value;
        extra_bits_data[*tree_size] = 0u8;
        *tree_size = tree_size.wrapping_add(1);
        repetitions = repetitions.wrapping_sub(1);
    }
    if repetitions == 7usize {
        tree[*tree_size] = value;
        extra_bits_data[*tree_size] = 0u8;
        *tree_size = tree_size.wrapping_add(1);
        repetitions = repetitions.wrapping_sub(1);
    }
    if repetitions < 3usize {
        for _i in 0usize..repetitions {
            tree[*tree_size] = value;
            extra_bits_data[*tree_size] = 0u8;
            *tree_size = tree_size.wrapping_add(1);
        }
    } else {
        let start: usize = *tree_size;
        repetitions = repetitions.wrapping_sub(3);
        loop {
            tree[*tree_size] = 16u8;
            extra_bits_data[*tree_size] = (repetitions & 0x03) as u8;
            *tree_size = tree_size.wrapping_add(1);
            repetitions >>= 2i32;
            if repetitions == 0usize {
                break;
            }
            repetitions = repetitions.wrapping_sub(1);
        }
        Reverse(tree, start, *tree_size);
        Reverse(extra_bits_data, start, *tree_size);
    }
}

fn BrotliWriteHuffmanTreeRepetitionsZeros(
    mut repetitions: usize,
    tree_size: &mut usize,
    tree: &mut [u8],
    extra_bits_data: &mut [u8],
) {
    if repetitions == 11 {
        tree[*tree_size] = 0u8;
        extra_bits_data[*tree_size] = 0u8;
        *tree_size = tree_size.wrapping_add(1);
        repetitions = repetitions.wrapping_sub(1);
    }
    if repetitions < 3usize {
        for _i in 0usize..repetitions {
            tree[*tree_size] = 0u8;
            extra_bits_data[*tree_size] = 0u8;
            *tree_size = tree_size.wrapping_add(1);
        }
    } else {
        let start: usize = *tree_size;
        repetitions = repetitions.wrapping_sub(3);
        loop {
            tree[*tree_size] = 17u8;
            extra_bits_data[*tree_size] = (repetitions & 0x7usize) as u8;
            *tree_size = tree_size.wrapping_add(1);
            repetitions >>= 3i32;
            if repetitions == 0usize {
                break;
            }
            repetitions = repetitions.wrapping_sub(1);
        }
        Reverse(tree, start, *tree_size);
        Reverse(extra_bits_data, start, *tree_size);
    }
}

pub fn BrotliWriteHuffmanTree(
    depth: &[u8],
    length: usize,
    tree_size: &mut usize,
    tree: &mut [u8],
    extra_bits_data: &mut [u8],
) {
    let mut previous_value: u8 = 8u8;
    let mut i: usize;
    let mut use_rle_for_non_zero = false;
    let mut use_rle_for_zero = false;
    let mut new_length: usize = length;
    i = 0usize;
    'break27: while i < length {
        {
            if depth[length.wrapping_sub(i).wrapping_sub(1)] as i32 == 0i32 {
                new_length = new_length.wrapping_sub(1);
            } else {
                break 'break27;
            }
        }
        i = i.wrapping_add(1);
    }
    if length > 50 {
        (use_rle_for_non_zero, use_rle_for_zero) = decide_over_rle_use(depth, new_length);
    }
    i = 0usize;
    while i < new_length {
        let value: u8 = depth[i];
        let mut reps: usize = 1;
        if value != 0 && use_rle_for_non_zero || value == 0 && use_rle_for_zero {
            let mut k: usize;
            k = i.wrapping_add(1);
            while k < new_length && (depth[k] as i32 == value as i32) {
                {
                    reps = reps.wrapping_add(1);
                }
                k = k.wrapping_add(1);
            }
        }
        if value as i32 == 0i32 {
            BrotliWriteHuffmanTreeRepetitionsZeros(reps, tree_size, tree, extra_bits_data);
        } else {
            BrotliWriteHuffmanTreeRepetitions(
                previous_value,
                value,
                reps,
                tree_size,
                tree,
                extra_bits_data,
            );
            previous_value = value;
        }
        i = i.wrapping_add(reps);
    }
}

fn BrotliReverseBits(num_bits: usize, mut bits: u16) -> u16 {
    static kLut: [usize; 16] = [
        0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe, 0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf,
    ];
    let mut retval: usize = kLut[(bits as i32 & 0xfi32) as usize];
    let mut i: usize;
    i = 4usize;
    while i < num_bits {
        {
            retval <<= 4i32;
            bits = (bits as i32 >> 4) as u16;
            retval |= kLut[(bits as i32 & 0xfi32) as usize];
        }
        i = i.wrapping_add(4);
    }
    retval >>= (0usize.wrapping_sub(num_bits) & 0x3usize);
    retval as u16
}
const MAX_HUFFMAN_BITS: usize = 16;
pub fn BrotliConvertBitDepthsToSymbols(depth: &[u8], len: usize, bits: &mut [u16]) {
    /* In Brotli, all bit depths are [1..15]
    0 bit depth means that the symbol does not exist. */

    let mut bl_count: [u16; MAX_HUFFMAN_BITS] = [0; MAX_HUFFMAN_BITS];
    let mut next_code: [u16; MAX_HUFFMAN_BITS] = [0; MAX_HUFFMAN_BITS];
    let mut code: i32 = 0i32;
    for i in 0usize..len {
        let _rhs = 1;
        let _lhs = &mut bl_count[depth[i] as usize];
        *_lhs = (*_lhs as i32 + _rhs) as u16;
    }
    bl_count[0] = 0u16;
    next_code[0] = 0u16;
    for i in 1..MAX_HUFFMAN_BITS {
        code = (code + bl_count[i - 1] as i32) << 1;
        next_code[i] = code as u16;
    }
    for i in 0usize..len {
        if depth[i] != 0 {
            bits[i] = BrotliReverseBits(depth[i] as usize, {
                let _rhs = 1;
                let _lhs = &mut next_code[depth[i] as usize];
                let _old = *_lhs;
                *_lhs = (*_lhs as i32 + _rhs) as u16;
                _old
            });
        }
    }
}
