/******************************************************************************
 *
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *****************************************************************************
 * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

#include "impd_type_def.h"
#include "impd_drc_extr_delta_coded_info.h"
#include "impd_drc_common.h"
#include "impd_drc_struct.h"
#include "impd_drc_filter_bank.h"
#include "impd_drc_multi_band.h"
#include "impd_drc_gain_dec.h"
#include "impd_drc_process_audio.h"

WORD32 impd_apply_gains_and_add(
    ia_drc_instructions_struct* pstr_drc_instruction_arr,
    const WORD32 drc_instructions_index,
    ia_drc_params_struct* ia_drc_params_struct,
    ia_gain_buffer_struct* pstr_gain_buf,
    shape_filter_block shape_filter_block[], FLOAT32* deinterleaved_audio[],

    FLOAT32* channel_audio[], WORD32 impd_apply_gains) {
  WORD32 c, b, g, i;
  WORD32 offset = 0, signalIndex = 0;
  WORD32 gainIndexForGroup[CHANNEL_GROUP_COUNT_MAX];
  WORD32 signalIndexForChannel[MAX_CHANNEL_COUNT];
  FLOAT32* lpcm_gains;
  FLOAT32 sum;
  FLOAT32 drc_gain_last, gainThr;
  WORD32 iEnd, iStart;
  ia_drc_instructions_struct* str_drc_instruction_str =
      &(pstr_drc_instruction_arr[drc_instructions_index]);

  if (drc_instructions_index >= 0) {
    str_drc_instruction_str =
        &(pstr_drc_instruction_arr[drc_instructions_index]);
    {
      if (str_drc_instruction_str->drc_set_id > 0) {
        if (ia_drc_params_struct->delay_mode == DELAY_MODE_LOW_DELAY) {
          offset = ia_drc_params_struct->drc_frame_size;
        }
        gainIndexForGroup[0] = 0;
        for (g = 0; g < str_drc_instruction_str->num_drc_ch_groups - 1; g++) {
          gainIndexForGroup[g + 1] =
              gainIndexForGroup[g] +
              str_drc_instruction_str->band_count_of_ch_group[g];
        }
        signalIndexForChannel[0] = 0;
        for (c = 0; c < str_drc_instruction_str->audio_num_chan - 1; c++) {
          if (str_drc_instruction_str->channel_group_of_ch[c] >= 0) {
            signalIndexForChannel[c + 1] =
                signalIndexForChannel[c] +
                str_drc_instruction_str->band_count_of_ch_group
                    [str_drc_instruction_str->channel_group_of_ch[c]];
          } else {
            signalIndexForChannel[c + 1] = signalIndexForChannel[c] + 1;
          }
        }

        for (g = 0; g < str_drc_instruction_str->num_drc_ch_groups; g++) {
          for (b = 0; b < str_drc_instruction_str->band_count_of_ch_group[g];
               b++) {
            if (str_drc_instruction_str->ch_group_parametric_drc_flag[g] == 0) {
              lpcm_gains =
                  pstr_gain_buf->buf_interpolation[gainIndexForGroup[g] + b]
                      .lpcm_gains +
                  MAX_SIGNAL_DELAY - ia_drc_params_struct->gain_delay_samples -
                  ia_drc_params_struct->audio_delay_samples + offset;
            } else {
              lpcm_gains =
                  pstr_gain_buf->buf_interpolation[gainIndexForGroup[g] + b]
                      .lpcm_gains +
                  MAX_SIGNAL_DELAY +
                  str_drc_instruction_str
                      ->parametric_drc_look_ahead_samples[g] -
                  ia_drc_params_struct->audio_delay_samples;
            }
            iEnd = 0;
            iStart = 0;
            while (iEnd < ia_drc_params_struct->drc_frame_size) {
              if (shape_filter_block[g].shape_flter_block_flag) {
                drc_gain_last = shape_filter_block[g].drc_gain_last;
                gainThr = 0.0001f * drc_gain_last;
                while ((iEnd < ia_drc_params_struct->drc_frame_size) &&
                       (fabs(lpcm_gains[iEnd] - drc_gain_last) <= gainThr))
                  iEnd++;
              } else {
                iEnd = ia_drc_params_struct->drc_frame_size;
              }

              for (c = 0; c < str_drc_instruction_str->audio_num_chan; c++)

              {
                if (g == str_drc_instruction_str->channel_group_of_ch[c]) {
                  signalIndex = signalIndexForChannel[c] + b;

                  if (impd_apply_gains == 1) {
                    impd_shape_filt_block_time_process(
                        &shape_filter_block[g], &lpcm_gains[0], signalIndex,
                        &deinterleaved_audio[signalIndex][0], iStart, iEnd);

                  } else {
                    for (i = iStart; i < iEnd; i++) {
                      deinterleaved_audio[signalIndex][i] = lpcm_gains[i];
                    }
                  }
                }
              }
              if ((iEnd < ia_drc_params_struct->drc_frame_size) &&
                  (shape_filter_block[g].shape_flter_block_flag)) {
                impd_shape_filt_block_adapt(lpcm_gains[iEnd],
                                            &shape_filter_block[g]);
              }
              iStart = iEnd;
            }
          }
        }
      }
    }
  }

  signalIndex = 0;

  if (str_drc_instruction_str->drc_set_id > 0) {
    for (c = 0; c < str_drc_instruction_str->audio_num_chan; c++)

    {
      g = str_drc_instruction_str->channel_group_of_ch[c];
      if (g >= 0) {
        for (i = 0; i < ia_drc_params_struct->drc_frame_size; i++) {
          sum = 0.0f;
          for (b = 0; b < str_drc_instruction_str->band_count_of_ch_group[g];
               b++) {
            sum += deinterleaved_audio[signalIndex + b][i];
          }

          channel_audio[c][i] = sum;
        }
        signalIndex += str_drc_instruction_str->band_count_of_ch_group[g];
      } else {
        for (i = 0; i < ia_drc_params_struct->drc_frame_size; i++) {
          channel_audio[c][i] = deinterleaved_audio[signalIndex][i];
        }
        signalIndex++;
      }
    }
  } else {
    for (c = 0; c < str_drc_instruction_str->audio_num_chan; c++)

    {
      for (i = 0; i < ia_drc_params_struct->drc_frame_size; i++) {
        channel_audio[c][i] = deinterleaved_audio[c][i];
      }
    }
  }

  return (0);
}

/* subband-domain DRC: in-place application of DRC gains to audio frame */
WORD32
impd_apply_gains_subband(ia_drc_instructions_struct* pstr_drc_instruction_arr,
                         const WORD32 drc_instructions_index,
                         ia_drc_params_struct* ia_drc_params_struct,
                         ia_gain_buffer_struct* pstr_gain_buf,
                         ia_overlap_params_struct* pstr_overlap_params,
                         FLOAT32* deinterleaved_audio_delayed_re[],
                         FLOAT32* deinterleaved_audio_delayed_im[],
                         FLOAT32* deinterleaved_audio_re[],
                         FLOAT32* deinterleaved_audio_im[]) {
  WORD32 c, b, g, m, s;
  WORD32 gainIndexForGroup[CHANNEL_GROUP_COUNT_MAX];
  FLOAT32* lpcm_gains;
  FLOAT32 gainSb, gainLr;
  ia_drc_instructions_struct* str_drc_instruction_str;
  WORD32 offset = 0, signalIndex = 0;
  WORD32 drc_frame_sizeSb = 0;
  WORD32 nDecoderSubbands = 0;
  WORD32 L = 0; /* L: downsampling factor */
  WORD32 analysisDelay = 0;
  switch (ia_drc_params_struct->sub_band_domain_mode) {
    case SUBBAND_DOMAIN_MODE_QMF64:
      nDecoderSubbands = AUDIO_CODEC_SUBBAND_COUNT_QMF64;
      L = AUDIO_CODEC_SUBBAND_DOWNSAMPLING_FACTOR_QMF64;
      analysisDelay = AUDIO_CODEC_SUBBAND_ANALYSE_DELAY_QMF64;
      break;
    case SUBBAND_DOMAIN_MODE_QMF71:
      nDecoderSubbands = AUDIO_CODEC_SUBBAND_COUNT_QMF71;
      L = AUDIO_CODEC_SUBBAND_DOWNSAMPLING_FACTOR_QMF71;
      analysisDelay = AUDIO_CODEC_SUBBAND_ANALYSE_DELAY_QMF71;
      break;
    case SUBBAND_DOMAIN_MODE_STFT256:
      nDecoderSubbands = AUDIO_CODEC_SUBBAND_COUNT_STFT256;
      L = AUDIO_CODEC_SUBBAND_DOWNSAMPLING_FACTOR_STFT256;
      analysisDelay = AUDIO_CODEC_SUBBAND_ANALYSE_DELAY_STFT256;
      break;
    default:
      return -1;
      break;
  }
  drc_frame_sizeSb = ia_drc_params_struct->drc_frame_size / L;

  if (drc_instructions_index >= 0) {
    str_drc_instruction_str =
        &(pstr_drc_instruction_arr[drc_instructions_index]);
    {
      if (str_drc_instruction_str->drc_set_id > 0) {
        if (ia_drc_params_struct->delay_mode == DELAY_MODE_LOW_DELAY) {
          offset = ia_drc_params_struct->drc_frame_size;
        }
        gainIndexForGroup[0] = 0;
        for (g = 0; g < str_drc_instruction_str->num_drc_ch_groups - 1; g++) {
          gainIndexForGroup[g + 1] =
              gainIndexForGroup[g] +
              str_drc_instruction_str
                  ->band_count_of_ch_group[g]; /* index of first gain sequence
                                                  in channel group */
        }

        for (c = 0; c < str_drc_instruction_str->audio_num_chan; c++)

        {
          g = str_drc_instruction_str->channel_group_of_ch[c];
          if (g >= 0) {
            for (m = 0; m < drc_frame_sizeSb; m++) {
              if (str_drc_instruction_str->band_count_of_ch_group[g] >
                  1) { /* multiband DRC */
                for (s = 0; s < nDecoderSubbands; s++) {
                  gainSb = 0.0f;
                  for (b = 0;
                       b < str_drc_instruction_str->band_count_of_ch_group[g];
                       b++) {
                    if (str_drc_instruction_str
                            ->ch_group_parametric_drc_flag[g] == 0) {
                      lpcm_gains =
                          pstr_gain_buf
                              ->buf_interpolation[gainIndexForGroup[g] + b]
                              .lpcm_gains +
                          MAX_SIGNAL_DELAY -
                          ia_drc_params_struct->gain_delay_samples -
                          ia_drc_params_struct->audio_delay_samples + offset;
                    } else {
                      lpcm_gains =
                          pstr_gain_buf
                              ->buf_interpolation[gainIndexForGroup[g] + b]
                              .lpcm_gains +
                          MAX_SIGNAL_DELAY +
                          str_drc_instruction_str
                              ->parametric_drc_look_ahead_samples[g] -
                          ia_drc_params_struct->audio_delay_samples +
                          analysisDelay;
                    }
                    /* get gain for this timeslot by downsampling */
                    gainLr = lpcm_gains[(m * L + (L - 1) / 2)];
                    gainSb += pstr_overlap_params->str_group_overlap_params[g]
                                  .str_band_overlap_params[b]
                                  .overlap_weight[s] *
                              gainLr;
                  }
                  deinterleaved_audio_re[signalIndex][m * nDecoderSubbands +
                                                      s] =
                      gainSb *
                      deinterleaved_audio_delayed_re[signalIndex]
                                                    [m * nDecoderSubbands + s];
                  if (ia_drc_params_struct->sub_band_domain_mode ==
                      SUBBAND_DOMAIN_MODE_STFT256) { /* For STFT filterbank, the
                                                        real value of the
                                                        nyquist band is stored
                                                        at the imag value of the
                                                        first band */
                    if (s != 0)
                      deinterleaved_audio_im[signalIndex][m * nDecoderSubbands +
                                                          s] =
                          gainSb *
                          deinterleaved_audio_delayed_im[signalIndex]
                                                        [m * nDecoderSubbands +
                                                         s];
                    if (s == (nDecoderSubbands - 1))
                      deinterleaved_audio_im[signalIndex][m * nDecoderSubbands +
                                                          0] =
                          gainSb *
                          deinterleaved_audio_delayed_im[signalIndex]
                                                        [m * nDecoderSubbands +
                                                         0];
                  } else {
                    deinterleaved_audio_im[signalIndex][m * nDecoderSubbands +
                                                        s] =
                        gainSb *
                        deinterleaved_audio_delayed_im[signalIndex]
                                                      [m * nDecoderSubbands +
                                                       s];
                  }
                }
              } else { /* single-band DRC */
                if (str_drc_instruction_str->ch_group_parametric_drc_flag[g] ==
                    0) {
                  lpcm_gains =
                      pstr_gain_buf->buf_interpolation[gainIndexForGroup[g]]
                          .lpcm_gains +
                      MAX_SIGNAL_DELAY -
                      ia_drc_params_struct->gain_delay_samples -
                      ia_drc_params_struct->audio_delay_samples + offset;
                } else {
                  lpcm_gains =
                      pstr_gain_buf->buf_interpolation[gainIndexForGroup[g]]
                          .lpcm_gains +
                      MAX_SIGNAL_DELAY +
                      str_drc_instruction_str
                          ->parametric_drc_look_ahead_samples[g] -
                      ia_drc_params_struct->audio_delay_samples + analysisDelay;
                }
                /* get gain for this timeslot by downsampling */
                gainSb = lpcm_gains[(m * L + (L - 1) / 2)];
                for (s = 0; s < nDecoderSubbands; s++) {
                  deinterleaved_audio_re[signalIndex][m * nDecoderSubbands +
                                                      s] =
                      gainSb *
                      deinterleaved_audio_delayed_re[signalIndex]
                                                    [m * nDecoderSubbands + s];
                  deinterleaved_audio_im[signalIndex][m * nDecoderSubbands +
                                                      s] =
                      gainSb *
                      deinterleaved_audio_delayed_im[signalIndex]
                                                    [m * nDecoderSubbands + s];
                }
              }
            }
          }
          signalIndex++;
        }
      }
    }
  }
  return (0);
}

WORD32
impd_filter_banks_process(ia_drc_instructions_struct* pstr_drc_instruction_arr,
                          const WORD32 drc_instructions_index,
                          ia_drc_params_struct* ia_drc_params_struct,
                          FLOAT32* audio_io_buf[],
                          ia_audio_band_buffer_struct* audio_band_buffer,
                          ia_filter_banks_struct* ia_filter_banks_struct,
                          const WORD32 passThru) {
  WORD32 c, g, e, i, num_bands;
  // WORD32 err = 0;
  FLOAT32* audio_in;
  FLOAT32** audio_out;
  ia_drc_filter_bank_struct* str_drc_filter_bank;
  ia_drc_instructions_struct* str_drc_instruction_str;
  WORD32 drc_frame_size = ia_drc_params_struct->drc_frame_size;

  if (drc_instructions_index >= 0) {
    str_drc_instruction_str =
        &(pstr_drc_instruction_arr[drc_instructions_index]);
  } else {
    return -1;
  }

  e = 0;

  for (c = 0; c < str_drc_instruction_str->audio_num_chan; c++)

  {
    str_drc_filter_bank = NULL;

    audio_in = audio_io_buf[c];

    audio_out = &(audio_band_buffer->non_interleaved_audio[e]);
    if ((passThru == 0) && (drc_instructions_index >= 0)) {
      if (str_drc_instruction_str->drc_set_id < 0) {
        num_bands = 1;
      } else {
        g = str_drc_instruction_str->channel_group_of_ch[c];
        if (g == -1) {
          num_bands = 1;
          // if (ia_filter_banks_struct->str_drc_filter_bank != NULL)
          //{
          str_drc_filter_bank =
              &(ia_filter_banks_struct->str_drc_filter_bank
                    [str_drc_instruction_str->num_drc_ch_groups]);
          //}
        } else {
          num_bands = str_drc_instruction_str->band_count_of_ch_group[g];
          // if (ia_filter_banks_struct->str_drc_filter_bank != NULL)
          //{
          str_drc_filter_bank =
              &(ia_filter_banks_struct->str_drc_filter_bank[g]);
          //}
        }
        // if (ia_filter_banks_struct->str_drc_filter_bank != NULL)
        //{
        // if (&str_drc_filter_bank->str_all_pass_cascade != NULL)
        //{
        impd_all_pass_cascade_process(
            &str_drc_filter_bank->str_all_pass_cascade, c, drc_frame_size,
            audio_in);
        //}
        //}
      }
    } else {
      num_bands = 1;
    }
    switch (num_bands) {
      case 1:
        for (i = 0; i < drc_frame_size; i++) {
          audio_out[0][i] = audio_in[i];
        }
        e++;
        break;
      case 2:
        impd_two_band_filter_process(&str_drc_filter_bank->str_two_band_bank, c,
                                     drc_frame_size, audio_in, audio_out);
        e += 2;
        break;
      case 3:
        impd_three_band_filter_process(
            &str_drc_filter_bank->str_three_band_bank, c, drc_frame_size,
            audio_in, audio_out);
        e += 3;
        break;
      case 4:
        impd_four_band_filter_process(&str_drc_filter_bank->str_four_band_bank,
                                      c, drc_frame_size, audio_in, audio_out);
        e += 4;
        break;
      default:
        return (PARAM_ERROR);
        break;
    }
  }

  return (0);
}

WORD32
impd_store_audio_io_buffer_time(FLOAT32* audio_in_out_buf[],
                                ia_audio_in_out_buf* audio_io_buf_internal) {
  WORD32 i, j;

  if (audio_io_buf_internal->audio_delay_samples) {
    for (i = 0; i < audio_io_buf_internal->audio_num_chan; i++) {
      for (j = 0; j < audio_io_buf_internal->frame_size; j++) {
        audio_io_buf_internal->audio_io_buffer_delayed
            [i][audio_io_buf_internal->audio_delay_samples + j] =
            audio_in_out_buf[i][j];
      }
    }
  } else {
    audio_io_buf_internal->audio_io_buffer_delayed = audio_in_out_buf;
    audio_io_buf_internal->audio_in_out_buf = audio_in_out_buf;
  }

  return 0;
}

WORD32
impd_store_audio_io_buffer_freq(FLOAT32* audio_real_buff[],
                                FLOAT32* audio_imag_buff[],
                                ia_audio_in_out_buf* audio_io_buf_internal) {
  WORD32 i, j;

  if (audio_io_buf_internal->audio_delay_sub_band_samples) {
    for (i = 0; i < audio_io_buf_internal->audio_num_chan; i++) {
      for (j = 0; j < audio_io_buf_internal->audio_sub_band_frame_size *
                          audio_io_buf_internal->audio_sub_band_count;
           j++) {
        audio_io_buf_internal->audio_buffer_delayed_real
            [i][audio_io_buf_internal->audio_delay_sub_band_samples *
                    audio_io_buf_internal->audio_sub_band_count +
                j] = audio_real_buff[i][j];
        audio_io_buf_internal->audio_buffer_delayed_imag
            [i][audio_io_buf_internal->audio_delay_sub_band_samples *
                    audio_io_buf_internal->audio_sub_band_count +
                j] = audio_imag_buff[i][j];
      }
    }
  } else {
    audio_io_buf_internal->audio_buffer_delayed_real = audio_real_buff;
    audio_io_buf_internal->audio_buffer_delayed_imag = audio_imag_buff;
    audio_io_buf_internal->audio_real_buff = audio_real_buff;
    audio_io_buf_internal->audio_imag_buff = audio_imag_buff;
  }

  return 0;
}

WORD32
impd_retrieve_audio_io_buffer_time(FLOAT32* audio_in_out_buf[],
                                   ia_audio_in_out_buf* audio_io_buf_internal) {
  WORD32 i, j;

  if (audio_io_buf_internal->audio_delay_samples) {
    for (i = 0; i < audio_io_buf_internal->audio_num_chan; i++) {
      for (j = 0; j < audio_io_buf_internal->frame_size; j++) {
        audio_in_out_buf[i][j] =
            audio_io_buf_internal->audio_io_buffer_delayed[i][j];
      }
    }
  }

  return 0;
}

WORD32
impd_retrieve_audio_buffer_freq(FLOAT32* audio_real_buff[],
                                FLOAT32* audio_imag_buff[],
                                ia_audio_in_out_buf* audio_io_buf_internal) {
  WORD32 i, j;

  if (audio_io_buf_internal->audio_delay_sub_band_samples) {
    for (i = 0; i < audio_io_buf_internal->audio_num_chan; i++) {
      for (j = 0; j < audio_io_buf_internal->audio_sub_band_frame_size *
                          audio_io_buf_internal->audio_sub_band_count;
           j++) {
        audio_real_buff[i][j] =
            audio_io_buf_internal->audio_buffer_delayed_real
                [i][audio_io_buf_internal->audio_sub_band_count + j];
        audio_imag_buff[i][j] =
            audio_io_buf_internal->audio_buffer_delayed_imag
                [i][audio_io_buf_internal->audio_sub_band_count + j];
      }
    }
  }

  return 0;
}

WORD32
impd_advance_audio_io_buffer_time(ia_audio_in_out_buf* audio_io_buf_internal) {
  WORD32 i;
  if (audio_io_buf_internal->audio_delay_samples) {
    for (i = 0; i < audio_io_buf_internal->audio_num_chan; i++) {
      memmove(
          audio_io_buf_internal->audio_io_buffer_delayed[i],
          &audio_io_buf_internal
               ->audio_io_buffer_delayed[i][audio_io_buf_internal->frame_size],
          sizeof(FLOAT32) * audio_io_buf_internal->audio_delay_samples);
    }
  }

  return 0;
}

WORD32
impd_advance_audio_buff_freq(ia_audio_in_out_buf* audio_io_buf_internal) {
  WORD32 i;
  if (audio_io_buf_internal->audio_delay_sub_band_samples) {
    for (i = 0; i < audio_io_buf_internal->audio_num_chan; i++) {
      memmove(audio_io_buf_internal->audio_buffer_delayed_real[i],
              &audio_io_buf_internal->audio_buffer_delayed_real
                   [i][audio_io_buf_internal->audio_sub_band_frame_size *
                       audio_io_buf_internal->audio_sub_band_count],
              sizeof(FLOAT32) *
                  audio_io_buf_internal->audio_delay_sub_band_samples *
                  audio_io_buf_internal->audio_sub_band_count);
      memmove(audio_io_buf_internal->audio_buffer_delayed_imag[i],
              &audio_io_buf_internal->audio_buffer_delayed_imag
                   [i][audio_io_buf_internal->audio_sub_band_frame_size *
                       audio_io_buf_internal->audio_sub_band_count],
              sizeof(FLOAT32) *
                  audio_io_buf_internal->audio_delay_sub_band_samples *
                  audio_io_buf_internal->audio_sub_band_count);
    }
  }
  return 0;
}