use crate::silk::define::{
    MAX_LPC_ORDER, NLSF_QUANT_DEL_DEC_STATES, NLSF_QUANT_DEL_DEC_STATES_LOG2, NLSF_QUANT_LEVEL_ADJ,
    NLSF_QUANT_MAX_AMPLITUDE, NLSF_QUANT_MAX_AMPLITUDE_EXT,
};
use crate::silk::macros::{silk_limit, silk_mla, silk_smlabb, silk_smulbb};

pub fn silk_nlsf_del_dec_quant(
    indices: &mut [i8],
    x_q10: &[i16],
    w_q5: &[i16],
    pred_coef_q8: &[u8],
    ec_ix: &[i16],
    ec_rates_q5: &[u8],
    quant_step_size_q16: i32,
    inv_quant_step_size_q6: i16,
    mu_q20: i32,
    order: i16,
) -> i32 {
    let mut n_states: usize = 1;
    let mut ind_tmp: i32;
    let mut ind_min_max: usize;
    let mut ind_max_min: usize;
    let mut in_q10: i32;
    let mut res_q10: i32;
    let mut pred_q10: i32;
    let mut diff_q10: i32;
    let mut rate0_q5: i32;
    let mut rate1_q5: i32;
    let mut out0_q10: i32;
    let mut out1_q10: i32;
    let mut rd_tmp_q25: i32;
    let mut min_q25: i32;
    let mut min_max_q25: i32;
    let mut max_min_q25: i32;

    let mut ind_sort = [0usize; NLSF_QUANT_DEL_DEC_STATES];
    let mut ind = [[0i8; MAX_LPC_ORDER]; NLSF_QUANT_DEL_DEC_STATES];
    let mut prev_out_q10 = [0i16; 2 * NLSF_QUANT_DEL_DEC_STATES];
    let mut rd_q25 = [0i32; 2 * NLSF_QUANT_DEL_DEC_STATES];
    let mut rd_min_q25 = [0i32; NLSF_QUANT_DEL_DEC_STATES];
    let mut rd_max_q25 = [0i32; NLSF_QUANT_DEL_DEC_STATES];

    let mut out0_q10_table = [0i32; 2 * NLSF_QUANT_MAX_AMPLITUDE_EXT as usize];
    let mut out1_q10_table = [0i32; 2 * NLSF_QUANT_MAX_AMPLITUDE_EXT as usize];

    for i in -(NLSF_QUANT_MAX_AMPLITUDE_EXT)..NLSF_QUANT_MAX_AMPLITUDE_EXT {
        let mut tmp_out0_q10 = i << 10;
        let mut tmp_out1_q10 = tmp_out0_q10 + 1024;
        if i > 0 {
            tmp_out0_q10 -= NLSF_QUANT_LEVEL_ADJ;
            tmp_out1_q10 -= NLSF_QUANT_LEVEL_ADJ;
        } else if i == 0 {
            tmp_out1_q10 -= NLSF_QUANT_LEVEL_ADJ;
        } else if i == -1 {
            tmp_out0_q10 += NLSF_QUANT_LEVEL_ADJ;
        } else {
            tmp_out0_q10 += NLSF_QUANT_LEVEL_ADJ;
            tmp_out1_q10 += NLSF_QUANT_LEVEL_ADJ;
        }
        out0_q10_table[(i + NLSF_QUANT_MAX_AMPLITUDE_EXT) as usize] =
            (silk_smulbb(tmp_out0_q10, quant_step_size_q16)) >> 16;
        out1_q10_table[(i + NLSF_QUANT_MAX_AMPLITUDE_EXT) as usize] =
            (silk_smulbb(tmp_out1_q10, quant_step_size_q16)) >> 16;
    }

    rd_q25[0] = 0;
    prev_out_q10[0] = 0;

    for i in (0..order as usize).rev() {
        let rates_q5_ptr = &ec_rates_q5[ec_ix[i] as usize..];
        in_q10 = x_q10[i] as i32;

        for j in 0..n_states {
            pred_q10 = silk_smulbb(pred_coef_q8[i] as i32, prev_out_q10[j] as i32) >> 8;
            res_q10 = in_q10 - pred_q10;
            ind_tmp = (silk_smulbb(inv_quant_step_size_q6 as i32, res_q10)) >> 16;
            ind_tmp = silk_limit(
                ind_tmp,
                -NLSF_QUANT_MAX_AMPLITUDE_EXT,
                NLSF_QUANT_MAX_AMPLITUDE_EXT - 1,
            );
            ind[j][i] = ind_tmp as i8;

            out0_q10 = out0_q10_table[(ind_tmp + NLSF_QUANT_MAX_AMPLITUDE_EXT) as usize];
            out1_q10 = out1_q10_table[(ind_tmp + NLSF_QUANT_MAX_AMPLITUDE_EXT) as usize];

            out0_q10 += pred_q10;
            out1_q10 += pred_q10;
            prev_out_q10[j] = out0_q10 as i16;
            prev_out_q10[j + n_states] = out1_q10 as i16;

            if ind_tmp + 1 >= NLSF_QUANT_MAX_AMPLITUDE {
                if ind_tmp + 1 == NLSF_QUANT_MAX_AMPLITUDE {
                    rate0_q5 = rates_q5_ptr[(ind_tmp + NLSF_QUANT_MAX_AMPLITUDE) as usize] as i32;
                    rate1_q5 = 280;
                } else {
                    rate0_q5 = silk_smlabb(280 - 43 * NLSF_QUANT_MAX_AMPLITUDE, 43, ind_tmp);
                    rate1_q5 = rate0_q5 + 43;
                }
            } else if ind_tmp <= -NLSF_QUANT_MAX_AMPLITUDE {
                if ind_tmp == -NLSF_QUANT_MAX_AMPLITUDE {
                    rate0_q5 = 280;
                    rate1_q5 =
                        rates_q5_ptr[(ind_tmp + 1 + NLSF_QUANT_MAX_AMPLITUDE) as usize] as i32;
                } else {
                    rate0_q5 = silk_smlabb(280 - 43 * NLSF_QUANT_MAX_AMPLITUDE, -43, ind_tmp);
                    rate1_q5 = rate0_q5 - 43;
                }
            } else {
                rate0_q5 = rates_q5_ptr[(ind_tmp + NLSF_QUANT_MAX_AMPLITUDE) as usize] as i32;
                rate1_q5 = rates_q5_ptr[(ind_tmp + 1 + NLSF_QUANT_MAX_AMPLITUDE) as usize] as i32;
            }

            rd_tmp_q25 = rd_q25[j];
            diff_q10 = in_q10 - out0_q10;
            rd_q25[j] = silk_smlabb(
                silk_mla(rd_tmp_q25, silk_smulbb(diff_q10, diff_q10), w_q5[i] as i32),
                mu_q20,
                rate0_q5,
            );
            diff_q10 = in_q10 - out1_q10;
            rd_q25[j + n_states] = silk_smlabb(
                silk_mla(rd_tmp_q25, silk_smulbb(diff_q10, diff_q10), w_q5[i] as i32),
                mu_q20,
                rate1_q5,
            );
        }

        if n_states <= NLSF_QUANT_DEL_DEC_STATES / 2 {
            for j in 0..n_states {
                ind[j + n_states][i] = ind[j][i] + 1;
            }
            n_states <<= 1;
            for j in n_states..NLSF_QUANT_DEL_DEC_STATES {
                ind[j][i] = ind[j - n_states][i];
            }
        } else {
            for j in 0..NLSF_QUANT_DEL_DEC_STATES {
                if rd_q25[j] > rd_q25[j + NLSF_QUANT_DEL_DEC_STATES] {
                    rd_max_q25[j] = rd_q25[j];
                    rd_min_q25[j] = rd_q25[j + NLSF_QUANT_DEL_DEC_STATES];
                    rd_q25[j] = rd_min_q25[j];
                    rd_q25[j + NLSF_QUANT_DEL_DEC_STATES] = rd_max_q25[j];

                    prev_out_q10.swap(j, j + NLSF_QUANT_DEL_DEC_STATES);
                    ind_sort[j] = j + NLSF_QUANT_DEL_DEC_STATES;
                } else {
                    rd_min_q25[j] = rd_q25[j];
                    rd_max_q25[j] = rd_q25[j + NLSF_QUANT_DEL_DEC_STATES];
                    ind_sort[j] = j;
                }
            }

            loop {
                min_max_q25 = i32::MAX;
                max_min_q25 = 0;
                ind_min_max = 0;
                ind_max_min = 0;
                for j in 0..NLSF_QUANT_DEL_DEC_STATES {
                    if min_max_q25 > rd_max_q25[j] {
                        min_max_q25 = rd_max_q25[j];
                        ind_min_max = j;
                    }
                    if max_min_q25 < rd_min_q25[j] {
                        max_min_q25 = rd_min_q25[j];
                        ind_max_min = j;
                    }
                }
                if min_max_q25 >= max_min_q25 {
                    break;
                }

                ind_sort[ind_max_min] = ind_sort[ind_min_max] ^ NLSF_QUANT_DEL_DEC_STATES;
                rd_q25[ind_max_min] = rd_q25[ind_min_max + NLSF_QUANT_DEL_DEC_STATES];
                prev_out_q10[ind_max_min] = prev_out_q10[ind_min_max + NLSF_QUANT_DEL_DEC_STATES];
                rd_min_q25[ind_max_min] = 0;
                rd_max_q25[ind_min_max] = i32::MAX;
                ind[ind_max_min] = ind[ind_min_max];
            }

            for j in 0..NLSF_QUANT_DEL_DEC_STATES {
                ind[j][i] += (ind_sort[j] >> NLSF_QUANT_DEL_DEC_STATES_LOG2) as i8;
            }
        }
    }

    ind_tmp = 0;
    min_q25 = i32::MAX;
    for j in 0..2 * NLSF_QUANT_DEL_DEC_STATES {
        if min_q25 > rd_q25[j] {
            min_q25 = rd_q25[j];
            ind_tmp = j as i32;
        }
    }
    indices[..(order as usize)].copy_from_slice(
        &ind[(ind_tmp & (NLSF_QUANT_DEL_DEC_STATES as i32 - 1)) as usize][..(order as usize)],
    );
    indices[0] += (ind_tmp >> NLSF_QUANT_DEL_DEC_STATES_LOG2) as i8;

    min_q25
}
