/*
 *  Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 *
 */

#include <stdint.h>

#include "dl/api/omxtypes.h"
#include "dl/sp/api/omxSP.h"
#include "dl/sp/api/mipsSP.h"

static OMX_U16 SplitRadixPermutation(int i, int size, int inverse) {
  int m;
  if (size <= 2)
    return (i & 1);
  m = size >> 1;
  if (!(i & m))
    return SplitRadixPermutation(i, m, inverse) * 2;
  m >>= 1;
  if (inverse == !(i & m))
    return SplitRadixPermutation(i, m, inverse) * 4 + 1;

  return SplitRadixPermutation(i, m, inverse) * 4 - 1;
}

static void InitFFTOffsetsLUT(OMX_U16* offset_table,
                              int offset,
                              int size,
                              OMX_U32* index) {
  if (size < 16) {
    offset_table[*index] = (OMX_U16)(offset >> 2);
    (*index)++;
  } else {
    InitFFTOffsetsLUT(offset_table, offset, size >> 1, index);
    InitFFTOffsetsLUT(offset_table, offset + (size >> 1), size >> 2, index);
    InitFFTOffsetsLUT(offset_table, offset + 3 * (size >> 2), size >> 2, index);
  }
}

OMXResult omxSP_FFTInit_R_F32(OMXFFTSpec_R_F32* pFFTSpec, OMX_INT order) {
  OMX_U32 n;
  uint32_t fft_size;
  OMX_U16* p_bit_rev;
  OMX_U16* p_bit_rev_inv;
  OMX_U16* p_offset;
  OMX_F32* p_twiddle;
  OMX_F32* p_buf;
  OMX_U32 tmp;
  MIPSFFTSpec_R_FC32* pFFTStruct = (MIPSFFTSpec_R_FC32*)pFFTSpec;

  if (!pFFTSpec || (order < 1) || (order > TWIDDLE_TABLE_ORDER))
    return OMX_Sts_BadArgErr;

  /* For order larger than 4, compute Real FFT as Complex FFT of (order - 1). */
  if (order > 4)
    fft_size = 1 << (order - 1);
  else
    fft_size = 1 << order;

  p_twiddle = mipsSP_FFT_F32TwiddleTable;

  p_bit_rev = (OMX_U16*)((OMX_S8*)pFFTSpec + sizeof(MIPSFFTSpec_R_FC32));
  /* Align to 32 byte boundary. */
  tmp = ((uintptr_t)p_bit_rev) & 31;
  if (tmp)
    p_bit_rev = (OMX_U16*)((OMX_S8*)p_bit_rev + (32 - tmp));

  p_bit_rev_inv = (OMX_U16*)((OMX_S8*)p_bit_rev + fft_size * sizeof(OMX_U16));
  /* Align to 32 byte boundary. */
  tmp = ((uintptr_t)p_bit_rev_inv) & 31;
  if (tmp)
    p_bit_rev_inv = (OMX_U16*)((OMX_S8*)p_bit_rev_inv + (32 - tmp));

  p_offset = (OMX_U16*)((OMX_S8*)p_bit_rev_inv + fft_size * sizeof(OMX_U16));
  /* Align to 32 byte boundary. */
  tmp = ((uintptr_t)p_offset) & 31;
  if (tmp)
    p_offset = (OMX_U16*)((OMX_S8*)p_offset + (32 - tmp));

  p_buf = (OMX_F32*)((OMX_S8*)p_offset +
                     ((SUBTRANSFORM_CONST >> (16 - TWIDDLE_TABLE_ORDER)) | 1) *
                         sizeof(OMX_U16));

  /* Align to 32 byte boundary. */
  tmp = ((uintptr_t)p_buf) & 31;
  if (tmp)
    p_buf = (OMX_F32*)((OMX_S8*)p_buf + (32 - tmp));

  /* Calculate BitRevInv indexes. */
  for (uint32_t i = 0; i < fft_size; ++i)
    p_bit_rev_inv[-SplitRadixPermutation(i, fft_size, 0) & (fft_size - 1)] = i;

  /* Calculate BitRev indexes. */
  for (uint32_t i = 0; i < fft_size; ++i)
    p_bit_rev[p_bit_rev_inv[i]] = i;

  /* Calculate Offsets. */
  n = 0;
  InitFFTOffsetsLUT(p_offset, 0, 1 << TWIDDLE_TABLE_ORDER, &n);

  /*
   * Double-check if the offset tables are initialized correctly.
   * Note: the bit-reverse tables and the initialization algorithm for
   * pFFTStruct->pOffset table are thoroughly tested, so this check is
   * probabaly redundant. However, keeping this just to make sure the offsets
   * will not exceed the buffer boundaries.
   */
  if (order == 2) {
    /* Only check the offsets for the p_bit_rev_inv table. */
    for (uint32_t i = 0; i < fft_size; ++i) {
      if (p_bit_rev_inv[i] >= fft_size)
        return OMX_Sts_BadArgErr;
    }
  } else if (order < 5) {
    /* Check for p_offset table. */
    int shift = 2;
    int over = 4;
    int num_transforms = (SUBTRANSFORM_CONST >> (16 - order)) | 1;
    for (uint32_t i = 2; i < order; ++i) {
      for (uint32_t j = 0; j < num_transforms; ++j) {
        if (((p_offset[j] << shift) + over - 1) >= fft_size)
          return OMX_Sts_BadArgErr;
      }
      shift++;
      over <<= 1;
      num_transforms = (num_transforms >> 1) | 1;
    }
    /* Check for bit-reverse tables. */
    for (uint32_t i = 0; i < fft_size; ++i) {
      if ((p_bit_rev[i] >= fft_size) || (p_bit_rev_inv[i] >= fft_size))
        return OMX_Sts_BadArgErr;
    }
  } else {
    /* Check for p_offset table. */
    int shift = 2;
    int over = 4;
    int num_transforms = (SUBTRANSFORM_CONST >> (17 - order)) | 1;
    for (uint32_t i = 2; i < order; ++i) {
      for (uint32_t j = 0; j < num_transforms; ++j) {
        if (((p_offset[j] << shift) + over - 1) >= fft_size)
          return OMX_Sts_BadArgErr;
      }
      shift++;
      over <<= 1;
      num_transforms = (num_transforms >> 1) | 1;
    }
    /* Check for bit-reverse tables. */
    for (uint32_t i = 0; i < fft_size; ++i) {
      if ((p_bit_rev[i] >= fft_size) || (p_bit_rev_inv[i] >= fft_size))
        return OMX_Sts_BadArgErr;
    }
  }

  pFFTStruct->order = order;
  pFFTStruct->pBitRev = p_bit_rev;
  pFFTStruct->pBitRevInv = p_bit_rev_inv;
  pFFTStruct->pOffset = (const OMX_U16*)p_offset;
  pFFTStruct->pTwiddle = p_twiddle;
  pFFTStruct->pBuf = p_buf;

  return OMX_Sts_NoErr;
}
