/******************************************************************************
 *
 * 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_parametric_drc_dec.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"
#include "impd_drc_interface.h"
#include "impd_drc_gain_dec.h"
#include "impd_drc_eq.h"
#include "impd_drc_gain_decoder.h"
#include "impd_drc_rom.h"

WORD32 impd_shape_filt_block_adapt(const FLOAT32 drc_gain,
                                   shape_filter_block* shape_filter_block) {
  //    WORD32 err = 0;
  WORD32 i;
  FLOAT32 warpedGain, x1, y1;
  shape_filter_block->drc_gain_last = drc_gain;
  for (i = 0; i < 4; i++) {
    if (shape_filter_block->shape_filter[i].type == SHAPE_FILTER_TYPE_OFF)
      continue;
    else if (shape_filter_block->shape_filter[i].type ==
                 SHAPE_FILTER_TYPE_LF_CUT ||
             shape_filter_block->shape_filter[i].type ==
                 SHAPE_FILTER_TYPE_HF_CUT) {
      if (drc_gain < 1.0f)
        warpedGain = -1.0f;
      else
        warpedGain =
            (drc_gain - 1.0f) /
            (drc_gain - 1.0f + shape_filter_block->shape_filter[i].gain_offset);
      x1 = shape_filter_block->shape_filter[i].a1;
    } else if (shape_filter_block->shape_filter[i].type ==
                   SHAPE_FILTER_TYPE_LF_BOOST ||
               shape_filter_block->shape_filter[i].type ==
                   SHAPE_FILTER_TYPE_HF_BOOST) {
      if (drc_gain >= 1.0f)
        warpedGain = -1.0f;
      else
        warpedGain =
            (1.0f - drc_gain) /
            (1.0f +
             drc_gain *
                 (shape_filter_block->shape_filter[i].gain_offset - 1.0f));
      x1 = shape_filter_block->shape_filter[i].b1;
    }

    if (warpedGain <= 0.0f) {
      y1 = x1;
    } else if (warpedGain <
               shape_filter_block->shape_filter[i].warped_gain_max) {
      y1 = x1 + shape_filter_block->shape_filter[i].factor * warpedGain;
    } else {
      y1 = shape_filter_block->shape_filter[i].y1_bound;
    }
    if (shape_filter_block->shape_filter[i].type == SHAPE_FILTER_TYPE_LF_CUT) {
      shape_filter_block->shape_filter[i].b1 = y1;
    } else if (shape_filter_block->shape_filter[i].type ==
               SHAPE_FILTER_TYPE_HF_CUT) {
      shape_filter_block->shape_filter[i].g_norm =
          shape_filter_block->shape_filter[i].coeff_sum /
          (shape_filter_block->shape_filter[i].partial_coeff_sum + y1);
      shape_filter_block->shape_filter[i].b1 = y1;
    } else if (shape_filter_block->shape_filter[i].type ==
               SHAPE_FILTER_TYPE_HF_BOOST) {
      shape_filter_block->shape_filter[i].g_norm =
          (shape_filter_block->shape_filter[i].partial_coeff_sum + y1) /
          shape_filter_block->shape_filter[i].coeff_sum;
      shape_filter_block->shape_filter[i].a1 = y1;
    } else if (shape_filter_block->shape_filter[i].type ==
               SHAPE_FILTER_TYPE_LF_BOOST) {
      shape_filter_block->shape_filter[i].a1 = y1;
    }
  }
  return (0);
}

WORD32 resetshape_flter_block(shape_filter_block* shape_filter_block) {
  WORD32 i, c;
  shape_filter_block->drc_gain_last = -1.0f;
  impd_shape_filt_block_adapt(1.0f, shape_filter_block);
  for (i = 0; i < 4; i++) {
    for (c = 0; c < MAX_CHANNEL_COUNT; c++) {
      shape_filter_block->shape_filter[i].audio_in_state_1[c] = 0.0f;
      shape_filter_block->shape_filter[i].audio_in_state_2[c] = 0.0f;
      shape_filter_block->shape_filter[i].audio_out_state_1[c] = 0.0f;
      shape_filter_block->shape_filter[i].audio_out_state_2[c] = 0.0f;
    }
  }
  return (0);
}

WORD32 impd_shape_filt_block_init(
    ia_shape_filter_block_params_struct* shape_flter_block_params,
    shape_filter_block* shape_filter_block) {
  // WORD32 err = 0;
  FLOAT32 x1;
  FLOAT32 x2 = 0.0f;
  FLOAT32 radius;
  if (shape_flter_block_params->lf_cut_filter_present) {
    ia_shape_filter_params_struct* params =
        &shape_flter_block_params->str_lf_cut_params;
    shape_filter_block->shape_filter[0].type = SHAPE_FILTER_TYPE_LF_CUT;
    shape_filter_block->shape_filter[0].gain_offset =
        shape_filt_lf_gain_offset_tbl[params->corner_freq_index]
                                     [params->filter_strength_index];
    shape_filter_block->shape_filter[0].y1_bound =
        shape_filt_lf_y1_bound_tbl[params->corner_freq_index]
                                  [params->filter_strength_index];
    x1 = -shape_filt_lf_radius_tbl[params->corner_freq_index];
    shape_filter_block->shape_filter[0].warped_gain_max =
        SHAPE_FILTER_DRC_GAIN_MAX_MINUS_ONE /
        (SHAPE_FILTER_DRC_GAIN_MAX_MINUS_ONE +
         shape_filter_block->shape_filter[0].gain_offset);
    shape_filter_block->shape_filter[0].factor =
        (shape_filter_block->shape_filter[0].y1_bound - x1) /
        shape_filter_block->shape_filter[0].warped_gain_max;
    shape_filter_block->shape_filter[0].a1 = x1;

  } else {
    shape_filter_block->shape_filter[0].type = SHAPE_FILTER_TYPE_OFF;
  }
  if (shape_flter_block_params->lf_boost_filter_present) {
    ia_shape_filter_params_struct* params =
        &shape_flter_block_params->str_lf_boost_params;
    shape_filter_block->shape_filter[1].type = SHAPE_FILTER_TYPE_LF_BOOST;
    shape_filter_block->shape_filter[1].gain_offset =
        shape_filt_lf_gain_offset_tbl[params->corner_freq_index]
                                     [params->filter_strength_index];
    shape_filter_block->shape_filter[1].y1_bound =
        shape_filt_lf_y1_bound_tbl[params->corner_freq_index]
                                  [params->filter_strength_index];
    x1 = -shape_filt_lf_radius_tbl[params->corner_freq_index];
    shape_filter_block->shape_filter[1].warped_gain_max =
        SHAPE_FILTER_DRC_GAIN_MAX_MINUS_ONE /
        (SHAPE_FILTER_DRC_GAIN_MAX_MINUS_ONE +
         shape_filter_block->shape_filter[1].gain_offset);
    shape_filter_block->shape_filter[1].factor =
        (shape_filter_block->shape_filter[1].y1_bound - x1) /
        shape_filter_block->shape_filter[1].warped_gain_max;
    shape_filter_block->shape_filter[1].b1 = x1;

  } else {
    shape_filter_block->shape_filter[1].type = SHAPE_FILTER_TYPE_OFF;
  }
  if (shape_flter_block_params->hf_cut_filter_present) {
    ia_shape_filter_params_struct* params =
        &shape_flter_block_params->str_hfCutParams;
    shape_filter_block->shape_filter[2].type = SHAPE_FILTER_TYPE_HF_CUT;
    shape_filter_block->shape_filter[2].gain_offset =
        shape_filt_hf_gain_offset_tbl[params->corner_freq_index]
                                     [params->filter_strength_index];
    shape_filter_block->shape_filter[2].y1_bound =
        shape_filt_hf_y1_bound_tbl[params->corner_freq_index]
                                  [params->filter_strength_index];
    radius = shape_filt_hf_radius_tbl[params->corner_freq_index];
    x1 = (FLOAT32)(
        -2.0f * radius *
        cos(2.0f * M_PI *
            shape_filt_cutoff_freq_norm_hf_tbl[params->corner_freq_index]));
    x2 = radius * radius;
    shape_filter_block->shape_filter[2].warped_gain_max =
        SHAPE_FILTER_DRC_GAIN_MAX_MINUS_ONE /
        (SHAPE_FILTER_DRC_GAIN_MAX_MINUS_ONE +
         shape_filter_block->shape_filter[2].gain_offset);
    shape_filter_block->shape_filter[2].factor =
        (shape_filter_block->shape_filter[2].y1_bound - x1) /
        shape_filter_block->shape_filter[2].warped_gain_max;
    shape_filter_block->shape_filter[2].coeff_sum = 1.0f + x1 + x2;
    shape_filter_block->shape_filter[2].partial_coeff_sum = 1.0f + x2;
    shape_filter_block->shape_filter[2].a1 = x1;
    shape_filter_block->shape_filter[2].a2 = x2;
    shape_filter_block->shape_filter[2].b2 = x2;
  } else {
    shape_filter_block->shape_filter[2].type = SHAPE_FILTER_TYPE_OFF;
  }
  if (shape_flter_block_params->hf_boost_filter_present) {
    ia_shape_filter_params_struct* params =
        &shape_flter_block_params->str_hf_boost_params;
    shape_filter_block->shape_filter[3].type = SHAPE_FILTER_TYPE_HF_BOOST;
    shape_filter_block->shape_filter[3].gain_offset =
        shape_filt_hf_gain_offset_tbl[params->corner_freq_index]
                                     [params->filter_strength_index];
    shape_filter_block->shape_filter[3].y1_bound =
        shape_filt_hf_y1_bound_tbl[params->corner_freq_index]
                                  [params->filter_strength_index];
    radius = shape_filt_hf_radius_tbl[params->corner_freq_index];
    x1 = (FLOAT32)(
        -2.0f * radius *
        cos(2.0f * M_PI *
            shape_filt_cutoff_freq_norm_hf_tbl[params->corner_freq_index]));
    x2 = radius * radius;
    shape_filter_block->shape_filter[3].warped_gain_max =
        SHAPE_FILTER_DRC_GAIN_MAX_MINUS_ONE /
        (SHAPE_FILTER_DRC_GAIN_MAX_MINUS_ONE +
         shape_filter_block->shape_filter[3].gain_offset);
    shape_filter_block->shape_filter[3].factor =
        (shape_filter_block->shape_filter[3].y1_bound - x1) /
        shape_filter_block->shape_filter[3].warped_gain_max;
    shape_filter_block->shape_filter[3].coeff_sum = 1.0f + x1 + x2;
    shape_filter_block->shape_filter[3].partial_coeff_sum = 1.0f + x2;
    shape_filter_block->shape_filter[3].b1 = x1;
    shape_filter_block->shape_filter[3].b2 = x2;
    shape_filter_block->shape_filter[3].a2 = x2;

  } else {
    shape_filter_block->shape_filter[3].type = SHAPE_FILTER_TYPE_OFF;
  }
  resetshape_flter_block(shape_filter_block);
  shape_filter_block->shape_flter_block_flag = 1;
  return (0);
}

WORD32 impd_shape_filt_block_time_process(
    shape_filter_block* shape_filter_block, FLOAT32* drc_gain,
    const WORD32 channel, FLOAT32* audio_in, WORD32 start, WORD32 end) {
  WORD32 i, j, err = 0;
  FLOAT32 audio_out;

  if (shape_filter_block->shape_flter_block_flag) {
    for (i = start; i < end; i++) {
      FLOAT32 tmp = audio_in[i];
      for (j = 0; j < 4; j++) {
        if (shape_filter_block->shape_filter[j].type ==
                SHAPE_FILTER_TYPE_LF_CUT ||
            shape_filter_block->shape_filter[j].type ==
                SHAPE_FILTER_TYPE_LF_BOOST) {
          audio_out = tmp +
                      shape_filter_block->shape_filter[j].b1 *
                          shape_filter_block->shape_filter[j]
                              .audio_in_state_1[channel] -
                      shape_filter_block->shape_filter[j].a1 *
                          shape_filter_block->shape_filter[j]
                              .audio_out_state_1[channel];
          shape_filter_block->shape_filter[j].audio_in_state_1[channel] = tmp;
          shape_filter_block->shape_filter[j].audio_out_state_1[channel] =
              audio_out;

        } else if (shape_filter_block->shape_filter[j].type ==
                       SHAPE_FILTER_TYPE_HF_CUT ||
                   shape_filter_block->shape_filter[j].type ==
                       SHAPE_FILTER_TYPE_HF_BOOST) {
          audio_out = shape_filter_block->shape_filter[j].g_norm * tmp +
                      shape_filter_block->shape_filter[j].b1 *
                          shape_filter_block->shape_filter[j]
                              .audio_in_state_1[channel] +
                      shape_filter_block->shape_filter[j].b2 *
                          shape_filter_block->shape_filter[j]
                              .audio_in_state_2[channel] -
                      shape_filter_block->shape_filter[j].a1 *
                          shape_filter_block->shape_filter[j]
                              .audio_out_state_1[channel] -
                      shape_filter_block->shape_filter[j].a2 *
                          shape_filter_block->shape_filter[j]
                              .audio_out_state_2[channel];
          shape_filter_block->shape_filter[j].audio_in_state_2[channel] =
              shape_filter_block->shape_filter[j].audio_in_state_1[channel];
          shape_filter_block->shape_filter[j].audio_in_state_1[channel] =
              shape_filter_block->shape_filter[j].g_norm * tmp;
          shape_filter_block->shape_filter[j].audio_out_state_2[channel] =
              shape_filter_block->shape_filter[j].audio_out_state_1[channel];
          shape_filter_block->shape_filter[j].audio_out_state_1[channel] =
              audio_out;
        } else {
          audio_out = tmp;
        }
        tmp = audio_out;
      }

      audio_in[i] = audio_out * drc_gain[i];
    }

  } else {
    for (i = start; i < end; i++) {
      audio_in[i] = audio_in[i] * drc_gain[i];
    }
  }

  return err;
}
