//
// Copyright © 2025 Agora
// This file is part of TEN Framework, an open source project.
// Licensed under the Apache License, Version 2.0, with certain conditions.
// Refer to the "LICENSE" file in the root directory for more information.
//
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "fscvrt.h"
#include "fscvrt_st.h"
#include "biquad.h"

// ==========================================================================================
// internal tools
// ==========================================================================================

static int AUP_Fscvrt_FilterSet(int resampleRate, int* nsect,
                                const float* B[_FSCVRT_MAXNSEC],
                                const float* A[_FSCVRT_MAXNSEC],
                                const float** G) {
  int idx;
  if (resampleRate == 2) {
    *nsect = _FSCVRT_1over2_LOWPASS_NSEC;
    for (idx = 0; idx < (*nsect); idx++) {
      B[idx] = &(_FSCVRT_1over2_LOWPASS_B[idx][0]);
      A[idx] = &(_FSCVRT_1over2_LOWPASS_A[idx][0]);
    }
    *G = _FSCVRT_1over2_LOWPASS_G;
  } else if (resampleRate == 3) {
    *nsect = _FSCVRT_1over3_LOWPASS_NSEC;
    for (idx = 0; idx < (*nsect); idx++) {
      B[idx] = &(_FSCVRT_1over3_LOWPASS_B[idx][0]);
      A[idx] = &(_FSCVRT_1over3_LOWPASS_A[idx][0]);
    }
    *G = _FSCVRT_1over3_LOWPASS_G;
  } else if (resampleRate == 4) {
    *nsect = _FSCVRT_1over4_LOWPASS_NSEC;
    for (idx = 0; idx < (*nsect); idx++) {
      B[idx] = &(_FSCVRT_1over4_LOWPASS_B[idx][0]);
      A[idx] = &(_FSCVRT_1over4_LOWPASS_A[idx][0]);
    }
    *G = _FSCVRT_1over4_LOWPASS_G;
  } else if (resampleRate == 6) {
    *nsect = _FSCVRT_1over6_LOWPASS_NSEC;
    for (idx = 0; idx < (*nsect); idx++) {
      B[idx] = &(_FSCVRT_1over6_LOWPASS_B[idx][0]);
      A[idx] = &(_FSCVRT_1over6_LOWPASS_A[idx][0]);
    }
    *G = _FSCVRT_1over6_LOWPASS_G;
  } else {  // unknown resample rate
    return -1;
  }

  return 0;
}

static int AUP_Fscvrt_dynamMemPrepare(FscvrtSt* stHdl, void* memPtrExt,
                                      size_t memSize) {
  char* memPtr = NULL;
  int biquadInBufMemSize = 0;
  int biquadOutBufMemSize = 0;
  int totalMemSize = 0;

  if (stHdl == NULL) {
    return -1;
  }

  biquadInBufMemSize = _FSCVRT_ALIGN8(sizeof(float) * stHdl->biquadInBufLen);
  totalMemSize += biquadInBufMemSize;

  biquadOutBufMemSize = _FSCVRT_ALIGN8(sizeof(float) * stHdl->biquadOutBufLen);
  totalMemSize += biquadOutBufMemSize;

  totalMemSize = _FSCVRT_MAX(totalMemSize, 80);

  // if no external memory provided, we are only profiling the memory
  // requirement
  if (memPtrExt == NULL) {
    return (totalMemSize);
  }

  // if required memory is more than provided, error
  if ((size_t)totalMemSize > memSize) {
    return -1;
  }
  memPtr = (char*)memPtrExt;

  stHdl->biquadInBuf = NULL;
  if (biquadInBufMemSize != 0) {
    stHdl->biquadInBuf = (float*)memPtr;
    memPtr += biquadInBufMemSize;
  }

  stHdl->biquadOutBuf = NULL;
  if (biquadOutBufMemSize != 0) {
    stHdl->biquadOutBuf = (float*)memPtr;
    memPtr += biquadOutBufMemSize;
  }

  if (((int)(memPtr - (char*)memPtrExt)) > totalMemSize) {
    return -1;
  }

  return (totalMemSize);
}

static int AUP_Fscvrt_checkStatCfg(FscvrtStaticCfg* pCfg) {
  if (pCfg == NULL) {
    return -1;
  }

  if (pCfg->inputFs != 16000 && pCfg->inputFs != 24000 &&
      pCfg->inputFs != 32000 && pCfg->inputFs != 48000) {
    return -1;
  }

  if (pCfg->outputFs != 16000 && pCfg->outputFs != 24000 &&
      pCfg->outputFs != 32000 && pCfg->outputFs != 48000) {
    return -1;
  }

  if (pCfg->stepSz > AUP_FSCVRT_MAX_INPUT_LEN || pCfg->stepSz < 1) {
    return -1;
  }

  if (pCfg->inputType != 0) {
    pCfg->inputType = 1;
  }

  if (pCfg->outputType != 0) {
    pCfg->outputType = 1;
  }

  return 0;
}

static int AUP_Fscvrt_publishStaticCfg(FscvrtSt* stHdl) {
  int tmpRatio;
  int ret;
  int maxResmplRate = 0;

  stHdl->mode = 0;
  stHdl->upSmplRate = 1;
  stHdl->downSmplRate = 1;
  if (stHdl->stCfg.inputFs != stHdl->stCfg.outputFs) {
    if (stHdl->stCfg.outputFs > stHdl->stCfg.inputFs) {
      tmpRatio = (stHdl->stCfg.outputFs / stHdl->stCfg.inputFs);
      if (stHdl->stCfg.outputFs == tmpRatio * stHdl->stCfg.inputFs) {
        stHdl->mode = 1;
        stHdl->upSmplRate = tmpRatio;
        stHdl->downSmplRate = 1;
      } else {
        stHdl->mode = 3;
        stHdl->upSmplRate = _FSCVRT_COMMON_FS / stHdl->stCfg.inputFs;
        stHdl->downSmplRate = _FSCVRT_COMMON_FS / stHdl->stCfg.outputFs;
      }
    } else {  // stHdl->stCfg.outputFs < stHdl->stCfg.inputFs
      tmpRatio = (stHdl->stCfg.inputFs / stHdl->stCfg.outputFs);
      if (stHdl->stCfg.inputFs == tmpRatio * stHdl->stCfg.outputFs) {
        stHdl->mode = 2;
        stHdl->upSmplRate = 1;
        stHdl->downSmplRate = tmpRatio;
      } else {
        stHdl->mode = 3;
        stHdl->upSmplRate = _FSCVRT_COMMON_FS / stHdl->stCfg.inputFs;
        stHdl->downSmplRate = _FSCVRT_COMMON_FS / stHdl->stCfg.outputFs;
      }
    }
  }

  if (stHdl->mode == 0) {
    stHdl->biquadInBufLen = 0;
    stHdl->biquadOutBufLen = 0;
  } else {
    stHdl->biquadInBufLen = stHdl->stCfg.stepSz * stHdl->upSmplRate;
    stHdl->biquadOutBufLen = 2 * (stHdl->stCfg.stepSz * stHdl->upSmplRate);
  }

  maxResmplRate = _FSCVRT_MAX(stHdl->upSmplRate, stHdl->downSmplRate);

  stHdl->nSec = 0;
  memset(stHdl->biquadB, 0, sizeof(stHdl->biquadB));
  memset(stHdl->biquadA, 0, sizeof(stHdl->biquadA));
  stHdl->biquadG = NULL;  // gain for each section

  if (stHdl->mode != 0) {
    ret = AUP_Fscvrt_FilterSet(maxResmplRate, &(stHdl->nSec), stHdl->biquadB,
                               stHdl->biquadA, &(stHdl->biquadG));
    if (ret < 0) {
      return -1;
    }
  }

  return 0;
}

static int AUP_Fscvrt_resetVariables(FscvrtSt* stHdl) {
  stHdl->biquadInBufCnt = 0;
  stHdl->biquadOutBufCnt = 0;

  if (stHdl->dynamMemPtr != NULL && stHdl->dynamMemSize > 0) {
    memset(stHdl->dynamMemPtr, 0, stHdl->dynamMemSize);
  }
  return 0;
}

// ==========================================================================================
// public APIs
// ==========================================================================================

int AUP_Fscvrt_create(void** stPtr) {
  FscvrtSt* tmpPtr;

  if (stPtr == NULL) {
    return -1;
  }
  *stPtr = (void*)malloc(sizeof(FscvrtSt));
  if (*stPtr == NULL) {
    return -1;
  }
  memset(*stPtr, 0, sizeof(FscvrtSt));

  tmpPtr = (FscvrtSt*)(*stPtr);

  tmpPtr->dynamMemPtr = NULL;
  tmpPtr->dynamMemSize = 0;

  tmpPtr->stCfg.inputFs = 24000;
  tmpPtr->stCfg.outputFs = 32000;
  tmpPtr->stCfg.stepSz = 240;    // 10ms processing step
  tmpPtr->stCfg.inputType = 0;   // short in
  tmpPtr->stCfg.outputType = 0;  // short out

  if (AUP_Biquad_create(&(tmpPtr->biquadSt)) < 0) {
    return -1;
  }

  return 0;
}

int AUP_Fscvrt_destroy(void** stPtr) {
  FscvrtSt* stHdl;

  if (stPtr == NULL) {
    return 0;
  }

  stHdl = (FscvrtSt*)(*stPtr);
  if (stHdl == NULL) {
    return 0;
  }

  AUP_Biquad_destroy(&(stHdl->biquadSt));
  if (stHdl->dynamMemPtr != NULL) {
    free(stHdl->dynamMemPtr);
  }
  stHdl->dynamMemPtr = NULL;

  free(stHdl);
  (*stPtr) = NULL;

  return 0;
}

int AUP_Fscvrt_memAllocate(void* stPtr, const FscvrtStaticCfg* pCfg) {
  FscvrtSt* stHdl = NULL;
  FscvrtStaticCfg tmpStatCfg = {0};
  Biquad_StaticCfg bqStatCfg;
  int idx, ret;
  int totalMemSize = 0;

  if (stPtr == NULL || pCfg == NULL) {
    return -1;
  }
  stHdl = (FscvrtSt*)(stPtr);

  memcpy(&tmpStatCfg, pCfg, sizeof(FscvrtStaticCfg));
  if (AUP_Fscvrt_checkStatCfg(&tmpStatCfg) < 0) {
    return -1;
  }
  memcpy(&(stHdl->stCfg), &tmpStatCfg, sizeof(FscvrtStaticCfg));

  if (AUP_Fscvrt_publishStaticCfg(stHdl) < 0) {
    return -1;
  }

  // check memory requirement
  totalMemSize = AUP_Fscvrt_dynamMemPrepare(stHdl, NULL, 0);
  if (totalMemSize < 0) {
    return -1;
  }

  // allocate dynamic memory
  if ((size_t)totalMemSize > stHdl->dynamMemSize) {
    if (stHdl->dynamMemPtr != NULL) {
      free(stHdl->dynamMemPtr);
      stHdl->dynamMemSize = 0;
    }
    stHdl->dynamMemPtr = (void*)malloc(totalMemSize);
    if (stHdl->dynamMemPtr == NULL) {
      return -1;
    }
    stHdl->dynamMemSize = totalMemSize;
  }
  memset(stHdl->dynamMemPtr, 0, stHdl->dynamMemSize);

  // setup the pointers/variable
  if (AUP_Fscvrt_dynamMemPrepare(stHdl, stHdl->dynamMemPtr,
                                 stHdl->dynamMemSize) < 0) {
    return -1;
  }

  // memAllocation for upSmplBiquadSt and downSmplBiquadSt
  if (stHdl->nSec != 0) {
    if (stHdl->nSec > AGORA_UAP_BIQUAD_MAX_SECTION) {
      return -1;
    }
    memset(&bqStatCfg, 0, sizeof(Biquad_StaticCfg));
    bqStatCfg.maxNSample = (size_t)(stHdl->biquadInBufLen);
    bqStatCfg.nsect = stHdl->nSec;
    for (idx = 0; idx < stHdl->nSec; idx++) {
      bqStatCfg.B[idx] = stHdl->biquadB[idx];
      bqStatCfg.A[idx] = stHdl->biquadA[idx];
    }
    bqStatCfg.G = stHdl->biquadG;

    ret = AUP_Biquad_memAllocate(stHdl->biquadSt, &bqStatCfg);
    if (ret < 0) {
      return -1;
    }
  }

  return 0;
}

int AUP_Fscvrt_init(void* stPtr) {
  FscvrtSt* stHdl;

  if (stPtr == NULL) {
    return -1;
  }
  stHdl = (FscvrtSt*)(stPtr);

  // clear/reset run-time variables
  if (AUP_Fscvrt_resetVariables(stHdl) < 0) {
    return -1;
  }

  // init submodules ...
  if (stHdl->biquadSt != NULL && stHdl->nSec != 0) {
    if (AUP_Biquad_init(stHdl->biquadSt) < 0) {
      return -1;
    }
  }

  return 0;
}

int AUP_Fscvrt_getStaticCfg(const void* stPtr, FscvrtStaticCfg* pCfg) {
  const FscvrtSt* stHdl;

  if (stPtr == NULL || pCfg == NULL) {
    return -1;
  }
  stHdl = (const FscvrtSt*)(stPtr);

  memcpy(pCfg, &(stHdl->stCfg), sizeof(FscvrtStaticCfg));

  return 0;
}

int AUP_Fscvrt_getInfor(const void* stPtr, FscvrtGetData* buff) {
  const FscvrtSt* stHdl;
  int delayBiquad = 0;
  int tmp;

  if (stPtr == NULL || buff == NULL) {
    return -1;
  }
  stHdl = (const FscvrtSt*)(stPtr);

  if (stHdl->nSec != 0) {
    if (AUP_Biquad_getAlgDelay(stHdl->biquadSt, &delayBiquad) < 0) {
      return -1;
    }
  }

  if (stHdl->mode == 0) {
    buff->delayInInputFs = 0;
  } else if (stHdl->mode == 1) {
    buff->delayInInputFs =
        (int)roundf(delayBiquad / (float)(stHdl->upSmplRate));
  } else if (stHdl->mode == 2) {  // direct downsampling
    buff->delayInInputFs = delayBiquad;
  } else {  // stHdl->mode == 3
    buff->delayInInputFs =
        (int)roundf(delayBiquad / (float)(stHdl->upSmplRate));
  }
  tmp = stHdl->stCfg.stepSz * stHdl->upSmplRate / stHdl->downSmplRate;
  if (tmp * stHdl->downSmplRate == stHdl->stCfg.stepSz * stHdl->upSmplRate) {
    buff->maxOutputStepSz = tmp;
  } else {
    buff->maxOutputStepSz = tmp + 1;
  }

  return 0;
}

int AUP_Fscvrt_proc(void* stPtr, const FscvrtInData* pIn, FscvrtOutData* pOut) {
  FscvrtSt* stHdl = NULL;
  const FscvrtStaticCfg* pCfg;
  Biquad_InputData bqdInData;
  Biquad_OutputData bqdOutData;
  const short* shortSrcPtr = NULL;
  const float* floatSrcPtr = NULL;
  short* shortTgtPtr = NULL;
  float* floatTgtPtr = NULL;
  int idx, tgtIdx;
  int nOutSamples = 0, samplesTaken = 0, samplesLeft = 0;
  int jumpRate;

  if (stPtr == NULL || pIn == NULL || pOut == NULL || pIn->inDataSeq == NULL ||
      pOut->outDataSeq == NULL) {  //  pCtrl == NULL
    return -1;
  }

  stHdl = (FscvrtSt*)(stPtr);
  pCfg = (const FscvrtStaticCfg*)&(stHdl->stCfg);
  shortSrcPtr = (const short*)(pIn->inDataSeq);
  floatSrcPtr = (const float*)(pIn->inDataSeq);
  // ==============================================================================
  // mode-0: bypass
  if (stHdl->mode == 0) {  // direct bypass
    if (pIn->outDataSeqLen < pCfg->stepSz) {
      return -1;
    }
    pOut->nOutData = pCfg->stepSz;
    pOut->outDataType = pCfg->outputType;
    if (pIn->inDataSeq == pOut->outDataSeq) {
      if (pCfg->outputType == pCfg->inputType)
        return 0;  // we don't need to do anything
      return -1;
      // if input buffer and the output buffer are the same, but required
      // different data type: error, we currently do not support such usecase
    }

    if (pCfg->inputType == 0 && pCfg->outputType == 0) {
      memcpy(pOut->outDataSeq, pIn->inDataSeq, sizeof(short) * pCfg->stepSz);
    } else if (pCfg->inputType == 1 && pCfg->outputType == 1) {
      memcpy(pOut->outDataSeq, pIn->inDataSeq, sizeof(float) * pCfg->stepSz);
    } else if (pCfg->inputType == 0 && pCfg->outputType == 1) {
      for (idx = 0; idx < pCfg->stepSz; idx++) {
        ((float*)pOut->outDataSeq)[idx] = ((short*)pIn->inDataSeq)[idx];
      }
    } else {  // if (pCfg->inputType == 1 && pCfg->outputType == 0)
      for (idx = 0; idx < pCfg->stepSz; idx++) {
        ((short*)pOut->outDataSeq)[idx] =
            (short)_FSCVRT_FLOAT2SHORT(((float*)pIn->inDataSeq)[idx]);
      }
    }

    return 0;
  }

  // prepare input buffer for Biquad .....
  memset(stHdl->biquadInBuf, 0, sizeof(float) * stHdl->biquadInBufLen);
  if (pCfg->inputType == 0) {
    for (idx = 0; idx < pCfg->stepSz; idx++) {
      stHdl->biquadInBuf[idx * (stHdl->upSmplRate)] =
          ((float)shortSrcPtr[idx]) * stHdl->upSmplRate;
    }
  } else {
    for (idx = 0; idx < pCfg->stepSz; idx++) {
      stHdl->biquadInBuf[idx * (stHdl->upSmplRate)] =
          floatSrcPtr[idx] * stHdl->upSmplRate;
    }
  }

  // biquad filtering ......
  memset(&bqdInData, 0, sizeof(Biquad_InputData));
  memset(&bqdOutData, 0, sizeof(Biquad_OutputData));
  bqdInData.samplesPtr = (const void*)(stHdl->biquadInBuf);
  bqdInData.sampleType = 1;
  bqdInData.nsamples = (size_t)(pCfg->stepSz * stHdl->upSmplRate);
  bqdOutData.outputBuff = (void*)&(stHdl->biquadOutBuf[stHdl->biquadOutBufCnt]);
  if (stHdl->biquadOutBufCnt + (pCfg->stepSz * stHdl->upSmplRate) >
      stHdl->biquadOutBufLen) {
    return -1;
  }
  if (AUP_Biquad_proc(stHdl->biquadSt, &bqdInData, &bqdOutData) < 0) {
    return -1;
  }
  stHdl->biquadOutBufCnt += (pCfg->stepSz * stHdl->upSmplRate);

  // checking the output buffer .........
  nOutSamples = stHdl->biquadOutBufCnt / stHdl->downSmplRate;
  if (pIn->outDataSeqLen < nOutSamples) {
    return -1;
  }

  // prepare output data, downsampling and throwing out ......
  pOut->nOutData = nOutSamples;
  pOut->outDataType = pCfg->outputType;

  shortTgtPtr = (short*)pOut->outDataSeq;
  floatTgtPtr = (float*)pOut->outDataSeq;
  jumpRate = stHdl->downSmplRate;
  if (pCfg->outputType == 0) {  // -> shortTgtPtr
    for (idx = (jumpRate - 1), tgtIdx = 0; idx < stHdl->biquadOutBufCnt;
         idx += jumpRate, tgtIdx++) {
      shortTgtPtr[tgtIdx] = _FSCVRT_FLOAT2SHORT(stHdl->biquadOutBuf[idx]);
    }
  } else {  // -> floatTgtPtr
    for (idx = (jumpRate - 1), tgtIdx = 0; idx < stHdl->biquadOutBufCnt;
         idx += jumpRate, tgtIdx++) {
      floatTgtPtr[tgtIdx] = stHdl->biquadOutBuf[idx];
    }
  }
  if (nOutSamples != tgtIdx) {
    return -1;
  }

  // update the stHdl->biquadOutBuf and stHdl->biquadOutBufCnt
  samplesTaken = nOutSamples * jumpRate;
  samplesLeft = stHdl->biquadOutBufCnt - samplesTaken;
  if (samplesLeft == 0) {
    stHdl->biquadOutBufCnt = 0;
  } else if (samplesLeft > 0) {
    stHdl->biquadOutBufCnt = samplesLeft;
    memmove(stHdl->biquadOutBuf, &(stHdl->biquadOutBuf[samplesTaken]),
            sizeof(float) * samplesLeft);
  } else {  // samplesLeft < 0
    stHdl->biquadOutBufCnt = 0;
    return -1;
  }

  return 0;
}
