/*
 *  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 "dl/api/omxtypes.h"
#include "dl/sp/api/armSP.h"
#include "dl/sp/api/omxSP.h"

extern void armSP_FFTFwd_CToC_FC32_Radix2_fs_OutOfPlace(
    const OMX_FC32* pSrc,
    OMX_FC32* pDst,
    OMX_FC32* pTwiddle,
    long* subFFTNum,
    long* subFFTSize);

extern void armSP_FFTFwd_CToC_FC32_Radix2_ls_OutOfPlace(
    const OMX_FC32* pSrc,
    OMX_FC32* pDst,
    OMX_FC32* pTwiddle,
    long* subFFTNum,
    long* subFFTSize);

extern void armSP_FFTFwd_CToC_FC32_Radix2_OutOfPlace(
    const OMX_FC32* pSrc,
    OMX_FC32* pDst,
    OMX_FC32* pTwiddle,
    long* subFFTNum,
    long* subFFTSize);

extern void armSP_FFTFwd_CToC_FC32_Radix4_fs_OutOfPlace(
    const OMX_FC32* pSrc,
    OMX_FC32* pDst,
    OMX_FC32* pTwiddle,
    long* subFFTNum,
    long* subFFTSize);

extern void armSP_FFTFwd_CToC_FC32_Radix4_OutOfPlace(
    const OMX_FC32* pSrc,
    OMX_FC32* pDst,
    OMX_FC32* pTwiddle,
    long* subFFTNum,
    long* subFFTSize);

extern void armSP_FFTFwd_CToC_FC32_Radix4_ls_OutOfPlace(
    const OMX_FC32* pSrc,
    OMX_FC32* pDst,
    OMX_FC32* pTwiddle,
    long* subFFTNum,
    long* subFFTSize);

extern void armSP_FFTFwd_CToC_FC32_Radix8_fs_OutOfPlace(
    const OMX_FC32* pSrc,
    OMX_FC32* pDst,
    OMX_FC32* pTwiddle,
    long* subFFTNum,
    long* subFFTSize);

extern void ComplexToRealFixup(OMX_FC32* pSrc,
                               OMX_F32* pDst,
                               const OMX_FC32* pTwiddle,
                               OMX_F32* pBuf,
                               long N);

/**
 * Function:  omxSP_FFTFwd_CToC_FC32_Sfs   (2.2.4.2.2)
 *
 * Description:
 * Compute an FFT for a complex signal of length of 2^order,
 * where 0 <= order <= 15.
 * Transform length is determined by the specification structure, which
 * must be initialized prior to calling the FFT function using the appropriate
 * helper, i.e., <FFTInit_C_sc32> or <FFTInit_C_SC16>. The relationship
 * between the input and output sequences can be expressed in terms of the
 * DFT, i.e.,
 *
 *      X[k] = SUM[n=0...N-1]x[n].e^(-jnk.2.pi/N)
 *      k = 0,1,2,..., N-1
 *      N = 2^order
 *
 * Input Arguments:
 *   pSrc - pointer to the input signal, a complex-valued vector of length
 *            2^order; must be aligned on a 32 byte boundary.
 *   pFFTSpec - pointer to the preallocated and initialized specification
 *            structure
 *
 * Output Arguments:
 *   pDst - pointer to the complex-valued output vector, of length 2^order;
 *            must be aligned on an 32-byte boundary.
 *
 * Return Value:
 *
 *    OMX_Sts_NoErr - no error
 *    OMX_Sts_BadArgErr - returned if one or more of the following conditions
 *              is true:
 *    -   one or more of the following pointers is NULL: pSrc, pDst, or
 *              pFFTSpec.
 *    -    pSrc or pDst is not 32-byte aligned
 *
 */

OMXResult omxSP_FFTFwd_RToCCS_F32_Sfs(const OMX_F32* pSrc,
                                      OMX_F32* pDst,
                                      const OMXFFTSpec_R_F32* pFFTSpec) {
  ARMsFFTSpec_R_FC32* spec = (ARMsFFTSpec_R_FC32*)pFFTSpec;
  int order;
  long subFFTSize;
  long subFFTNum;
  OMX_FC32* pTwiddle;
  OMX_FC32* pOut;
  OMX_FC32* pComplexSrc = (OMX_FC32*) pSrc;
  OMX_FC32* pComplexDst = (OMX_FC32*) pDst;

  /*
   * Check args are not NULL and the source and destination pointers
   * are properly aligned.
   */
  if (!validateParametersF32(pSrc, pDst, spec))
    return OMX_Sts_BadArgErr;

  /*
   * Compute the RFFT using a complex FFT of one less order, so set
   * order to be the order of the complex FFT.
   */
  order = fastlog2(spec->N) - 1;

  subFFTSize = 1;
  subFFTNum = spec->N >> 1;
  pTwiddle = spec->pTwiddle;
  pOut = (OMX_FC32*) spec->pBuf;

  if (order > 3) {
    OMX_FC32* argDst;
    OMX_FC32* pComplexDst = (OMX_FC32*) pDst;

    /*
     * Set up argDst and pOut appropriately so that pOut = pDst for
     * ComplexToRealFixup.
     */
    if ((order & 2) != 0) {
      argDst = pOut;
      pOut = pComplexDst;
    } else {
      argDst = pComplexDst;
    }

    /*
     * Odd order uses a radix 8 first stage; even order, a radix 4
     * first stage.
     */
    if (order & 1) {
      armSP_FFTFwd_CToC_FC32_Radix8_fs_OutOfPlace(
          pComplexSrc, argDst, pTwiddle, &subFFTNum, &subFFTSize);
    } else {
      armSP_FFTFwd_CToC_FC32_Radix4_fs_OutOfPlace(
          pComplexSrc, argDst, pTwiddle, &subFFTNum, &subFFTSize);
    }

    /*
     * Now use radix 4 stages to finish rest of the FFT
     */
    if (subFFTNum >= 4) {
      while (subFFTNum > 4) {
        OMX_FC32* tmp;

        armSP_FFTFwd_CToC_FC32_Radix4_OutOfPlace(
            argDst, pOut, pTwiddle, &subFFTNum, &subFFTSize);
        /*
         * Swap argDst and pOut
         */
        tmp = pOut;
        pOut = argDst;
        argDst = tmp;
      }

      armSP_FFTFwd_CToC_FC32_Radix4_ls_OutOfPlace(
          argDst, pOut, pTwiddle, &subFFTNum, &subFFTSize);
    }
  } else if (order == 3) {
    armSP_FFTFwd_CToC_FC32_Radix2_fs_OutOfPlace(
        pComplexSrc, pOut, pTwiddle, &subFFTNum, &subFFTSize);
    armSP_FFTFwd_CToC_FC32_Radix2_OutOfPlace(
        pOut, pComplexDst, pTwiddle, &subFFTNum, &subFFTSize);
    armSP_FFTFwd_CToC_FC32_Radix2_ls_OutOfPlace(
        pComplexDst, pOut, pTwiddle, &subFFTNum, &subFFTSize);
  } else if (order == 2) {
    armSP_FFTFwd_CToC_FC32_Radix2_fs_OutOfPlace(
        pComplexSrc, pComplexDst, pTwiddle, &subFFTNum, &subFFTSize);
    armSP_FFTFwd_CToC_FC32_Radix2_ls_OutOfPlace(
        pComplexDst, pOut, pTwiddle, &subFFTNum, &subFFTSize);
  } else if (order == 1) {
    armSP_FFTFwd_CToC_FC32_Radix2_fs_OutOfPlace(
        pComplexSrc, pOut, pTwiddle, &subFFTNum, &subFFTSize);
  } else {
    /* Handle complex order 0 specially */
    pOut->Re = pSrc[0];
    pOut->Im = pSrc[1];
  }

  /*
   * Complex FFT done. Fix up the complex result to give the correct
   * RFFT.
   */

  ComplexToRealFixup(pOut, pDst, pTwiddle, spec->pBuf, spec->N);

  return OMX_Sts_NoErr;
}
