/******************************************************************************
 *                                                                            *
 * 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 <stdlib.h>
#include <math.h>

#include <ixheaacd_type_def.h>

#include "ixheaacd_bitbuffer.h"
#include "ixheaacd_interface.h"

#include "ixheaacd_tns_usac.h"
#include "ixheaacd_cnst.h"

#include "ixheaacd_acelp_info.h"

#include "ixheaacd_td_mdct.h"

#include "ixheaacd_sbrdecsettings.h"
#include "ixheaacd_info.h"
#include "ixheaacd_sbr_common.h"
#include "ixheaacd_drc_data_struct.h"
#include "ixheaacd_drc_dec.h"
#include "ixheaacd_sbrdecoder.h"
#include "ixheaacd_mps_polyphase.h"
#include "ixheaacd_sbr_const.h"

#include "ixheaacd_main.h"
#include "ixheaacd_arith_dec.h"

#include "ixheaacd_func_def.h"
#include "ixheaacd_constants.h"
#include <ixheaacd_type_def.h>
#include <ixheaacd_basic_ops32.h>
#include <ixheaacd_basic_ops40.h>

extern const WORD32 ixheaacd_pre_post_twid_cos_sin_512[4][512];
extern const WORD32 ixheaacd_pre_post_twid_cos_sin_384[4][384];
extern const WORD32 ixheaacd_pre_post_twid_cos_sin_256[4][256];
extern const WORD32 ixheaacd_pre_post_twid_cos_sin_192[4][192];
extern const WORD32 ixheaacd_pre_post_twid_cos_sin_128[4][128];
extern const WORD32 ixheaacd_pre_post_twid_cos_sin_96[4][96];
extern const WORD32 ixheaacd_pre_post_twid_cos_sin_64[4][64];
extern const WORD32 ixheaacd_pre_post_twid_cos_sin_48[4][48];
extern const WORD32 ixheaacd_pre_post_twid_cos_sin_32[4][32];
extern const WORD32 ixheaacd_pre_post_twid_cos_sin_24[4][24];

static PLATFORM_INLINE WORD32 ixheaacd_mul_sub64_sat_32(WORD32 a, WORD32 b,
                                                        WORD32 c, WORD32 d) {
  WORD64 diff;
  WORD64 temp_result1;
  WORD64 temp_result2;

  temp_result1 = (WORD64)a * (WORD64)c;
  temp_result2 = (WORD64)b * (WORD64)d;

  diff = (temp_result1 - temp_result2) >> 32;

  if (diff >= 2147483647)
    diff = 2147483647;
  else if (diff <= -2147483647 - 1)
    diff = -2147483647 - 1;

  return ((WORD32)diff);
}

static PLATFORM_INLINE WORD32 ixheaacd_mul_add64_sat_32(WORD32 a, WORD32 b,
                                                        WORD32 c, WORD32 d) {
  WORD64 sum;
  WORD64 temp_result1;
  WORD64 temp_result2;

  temp_result1 = (WORD64)a * (WORD64)c;
  temp_result2 = (WORD64)b * (WORD64)d;

  sum = (temp_result1 + temp_result2) >> 32;

  if (sum >= 2147483647)
    sum = 2147483647;
  else if (sum <= -2147483647 - 1)
    sum = -2147483647 - 1;

  return ((WORD32)sum);
}

static void ixheaacd_pre_twid(WORD32 *in, WORD32 *r_ptr, WORD32 *i_ptr,
                              WORD32 nlength, const WORD32 *ptr_pre_cos_sin) {
  WORD32 i;

  const WORD32 *cos_ptr = &ptr_pre_cos_sin[0];
  const WORD32 *sin_ptr = &ptr_pre_cos_sin[nlength];

  for (i = 0; i < nlength; i += 4) {
    *r_ptr++ = ixheaacd_mul_sub64_sat_32(in[i], in[nlength + i], cos_ptr[i],
                                         sin_ptr[i]);
    *i_ptr++ = ixheaacd_mul_add64_sat_32(in[i], in[nlength + i], sin_ptr[i],
                                         cos_ptr[i]);

    *r_ptr++ = ixheaacd_mul_sub64_sat_32(in[i + 1], in[nlength + i + 1],
                                         cos_ptr[i + 1], sin_ptr[i + 1]);
    *i_ptr++ = ixheaacd_mul_add64_sat_32(in[i + 1], in[nlength + i + 1],
                                         sin_ptr[i + 1], cos_ptr[i + 1]);

    *r_ptr++ = ixheaacd_mul_sub64_sat_32(in[i + 2], in[nlength + i + 2],
                                         cos_ptr[i + 2], sin_ptr[i + 2]);
    *i_ptr++ = ixheaacd_mul_add64_sat_32(in[i + 2], in[nlength + i + 2],
                                         sin_ptr[i + 2], cos_ptr[i + 2]);

    *r_ptr++ = ixheaacd_mul_sub64_sat_32(in[i + 3], in[nlength + i + 3],
                                         cos_ptr[i + 3], sin_ptr[i + 3]);
    *i_ptr++ = ixheaacd_mul_add64_sat_32(in[i + 3], in[nlength + i + 3],
                                         sin_ptr[i + 3], cos_ptr[i + 3]);
  }
}

static void ixheaacd_post_twid(WORD32 *data_re, WORD32 *data_im, WORD32 *out,
                               WORD32 nlength, const WORD32 *ptr_post_cos_sin) {
  WORD32 i;

  const WORD32 *cos_ptr = &ptr_post_cos_sin[nlength * 2];
  const WORD32 *sin_ptr = &ptr_post_cos_sin[nlength * 3];

  WORD32 *out_ptr = &out[2 * nlength - 1];
  for (i = 0; i < nlength; i += 4) {
    out[0] = ixheaacd_mul_sub64_sat_32(data_re[i], data_im[i], cos_ptr[i],
                                       sin_ptr[i]);
    out_ptr[0] = -ixheaacd_mul_add64_sat_32(data_re[i], data_im[i], sin_ptr[i],
                                            cos_ptr[i]);

    out[2] = ixheaacd_mul_sub64_sat_32(data_re[i + 1], data_im[i + 1],
                                       cos_ptr[i + 1], sin_ptr[i + 1]);
    out_ptr[-2] = -ixheaacd_mul_add64_sat_32(data_re[i + 1], data_im[i + 1],
                                             sin_ptr[i + 1], cos_ptr[i + 1]);

    out[4] = ixheaacd_mul_sub64_sat_32(data_re[i + 2], data_im[i + 2],
                                       cos_ptr[i + 2], sin_ptr[i + 2]);
    out_ptr[-4] = -ixheaacd_mul_add64_sat_32(data_re[i + 2], data_im[i + 2],
                                             sin_ptr[i + 2], cos_ptr[i + 2]);

    out[6] = ixheaacd_mul_sub64_sat_32(data_re[i + 3], data_im[i + 3],
                                       cos_ptr[i + 3], sin_ptr[i + 3]);
    out_ptr[-6] = -ixheaacd_mul_add64_sat_32(data_re[i + 3], data_im[i + 3],
                                             sin_ptr[i + 3], cos_ptr[i + 3]);
    out += 8;
    out_ptr -= 8;
  }
}

WORD32 ixheaacd_acelp_mdct(WORD32 *ptr_in, WORD32 *ptr_out, WORD32 *preshift,
                           WORD32 length, WORD32 *ptr_scratch) {
  WORD32 *ptr_data_r = ptr_scratch;
  WORD32 *ptr_data_i = ptr_scratch + 512;
  const WORD32 *ptr_pre_post_twid;
  WORD32 err = 0;

  switch (length) {
    case 1024:
      ptr_pre_post_twid = &ixheaacd_pre_post_twid_cos_sin_512[0][0];
      break;
    case 768:
      ptr_pre_post_twid = &ixheaacd_pre_post_twid_cos_sin_384[0][0];
      break;
    case 512:
      ptr_pre_post_twid = &ixheaacd_pre_post_twid_cos_sin_256[0][0];
      break;
    case 384:
      ptr_pre_post_twid = &ixheaacd_pre_post_twid_cos_sin_192[0][0];
      break;
    case 256:
      ptr_pre_post_twid = &ixheaacd_pre_post_twid_cos_sin_128[0][0];
      break;
    case 192:
      ptr_pre_post_twid = &ixheaacd_pre_post_twid_cos_sin_96[0][0];
      break;
    case 128:
      ptr_pre_post_twid = &ixheaacd_pre_post_twid_cos_sin_64[0][0];
      break;
    case 96:
      ptr_pre_post_twid = &ixheaacd_pre_post_twid_cos_sin_48[0][0];
      break;
    case 64:
      ptr_pre_post_twid = &ixheaacd_pre_post_twid_cos_sin_32[0][0];
      break;
    case 48:
      ptr_pre_post_twid = &ixheaacd_pre_post_twid_cos_sin_24[0][0];
      break;
    default:
      ptr_pre_post_twid = &ixheaacd_pre_post_twid_cos_sin_24[0][0];
      break;
  }

  ixheaacd_pre_twid(ptr_in, ptr_data_r, ptr_data_i, length / 2,
                    ptr_pre_post_twid);

  ixheaacd_complex_fft(ptr_data_r, ptr_data_i, length / 2, -1, preshift);
  *preshift += 1;

  ixheaacd_post_twid(ptr_data_r, ptr_data_i, ptr_out, length / 2,
                     ptr_pre_post_twid);
  *preshift += 1;
  return err;
}

WORD32 ixheaacd_acelp_mdct_main(ia_usac_data_struct *usac_data, WORD32 *in,
                                WORD32 *out, WORD32 l, WORD32 m,
                                WORD32 *preshift) {
  WORD32 i;
  WORD32 *ptr_scratch = &usac_data->scratch_buffer[0];
  WORD32 *output_buffer = &usac_data->x_ac_dec[0];
  WORD32 err = 0;

  err = ixheaacd_acelp_mdct(in, output_buffer, preshift, l + m, ptr_scratch);
  if (err == -1) return err;

  for (i = 0; i < m / 2; i++) {
    out[l + m / 2 - 1 - i] = -output_buffer[m / 2 + l / 2 + i];
  }
  for (i = 0; i < l / 2; i++) {
    out[i] = output_buffer[m + l / 2 + i];
    out[l - 1 - i] = -output_buffer[m + l / 2 + i];
  }
  for (i = 0; i < m / 2; i++) {
    out[l + m / 2 + i] = -output_buffer[m / 2 + l / 2 - 1 - i];
  }
  for (i = 0; i < l / 2; i++) {
    out[l + m + i] = -output_buffer[l / 2 - 1 - i];
    out[2 * l + m - 1 - i] = -output_buffer[l / 2 - 1 - i];
  }
  return err;
}
