/************************************************************************
 * Copyright (C) 2002-2009, Xiph.org Foundation
 * Copyright (C) 2010, Robin Watts for Pinknoise Productions Ltd
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the names of the Xiph.org Foundation nor Pinknoise
 * Productions Ltd nor the names of its contributors may be used to
 * endorse or promote products derived from this software without
 * specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ************************************************************************

 function: floor backend 0 implementation

 ************************************************************************/

#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <log/log.h>
#include "ogg.h"
#include "ivorbiscodec.h"
#include "codec_internal.h"
#include "codebook.h"
#include "misc.h"
#include "os.h"

#define LSP_FRACBITS 14
extern const ogg_int32_t FLOOR_fromdB_LOOKUP[];

/*************** LSP decode ********************/

#include "lsp_lookup.h"

/* interpolated 1./sqrt(p) where .5 <= a < 1. (.100000... to .111111...) in
   16.16 format
   returns in m.8 format */

static long ADJUST_SQRT2[2]={8192,5792};
static inline ogg_int32_t vorbis_invsqlook_i(long a,long e){
  long i=(a&0x7fff)>>(INVSQ_LOOKUP_I_SHIFT-1);
  long d=a&INVSQ_LOOKUP_I_MASK;                              /*  0.10 */
  long val=INVSQ_LOOKUP_I[i]-                                /*  1.16 */
    ((INVSQ_LOOKUP_IDel[i]*d)>>INVSQ_LOOKUP_I_SHIFT);        /* result 1.16 */
  val*=ADJUST_SQRT2[e&1];
  e=(e>>1)+21;
  return(val>>e);
}

/* interpolated lookup based fromdB function, domain -140dB to 0dB only */
/* a is in n.12 format */
#ifdef _LOW_ACCURACY_
static inline ogg_int32_t vorbis_fromdBlook_i(long a){
  if(a>0) return 0x7fffffff;
  if(a<(int)(((unsigned)-140)<<12)) return 0;
  return FLOOR_fromdB_LOOKUP[((a+140)*467)>>20]<<9;
}
#else
static inline ogg_int32_t vorbis_fromdBlook_i(long a){
  if(a>0) return 0x7fffffff;
  if(a<(int)(((unsigned)-140)<<12)) return 0;
  return FLOOR_fromdB_LOOKUP[((a+(140<<12))*467)>>20];
}
#endif

/* interpolated lookup based cos function, domain 0 to PI only */
/* a is in 0.16 format, where 0==0, 2^^16-1==PI, return 0.14 */
static inline ogg_int32_t vorbis_coslook_i(long a){
  int i=a>>COS_LOOKUP_I_SHIFT;
  int d=a&COS_LOOKUP_I_MASK;
  return COS_LOOKUP_I[i]- ((d*(COS_LOOKUP_I[i]-COS_LOOKUP_I[i+1]))>>
                           COS_LOOKUP_I_SHIFT);
}

/* interpolated half-wave lookup based cos function */
/* a is in 0.16 format, where 0==0, 2^^16==PI, return .LSP_FRACBITS */
static inline ogg_int32_t vorbis_coslook2_i(long a){
  int i=a>>COS_LOOKUP_I_SHIFT;
  int d=a&COS_LOOKUP_I_MASK;
  return ((COS_LOOKUP_I[i]<<COS_LOOKUP_I_SHIFT)-
          d*(COS_LOOKUP_I[i]-COS_LOOKUP_I[i+1]))>>
    (COS_LOOKUP_I_SHIFT-LSP_FRACBITS+14);
}

/* Values in barklook are defined such that toBARK(x) is an approximation to
   POW(2, 15) * ((13.1*ATAN(0.00074*(x)))+(2.24*ATAN((x)*(x)*0.0000000185))+(0.0001*(x))) */
static const ogg_uint16_t barklook[]={
  0,51,102,154,            206,258,311,365,
  420,477,535,594,         656,719,785,854,
  926,1002,1082,1166,      1256,1352,1454,1564,
  1683,1812,1953,2107,     2276,2463,2670,2900,
  3155,3440,3756,4106,     4493,4919,5387,5901,
  6466,7094,7798,8599,     9528,10623,11935,13524,
  15453,17775,20517,23667, 27183,31004,35069
};

/* used in init only; interpolate the long way */
static inline ogg_int32_t toBARK(ogg_uint16_t n){
  int i;
  int barklook_size = (sizeof(barklook) / sizeof(barklook[0]));
  for(i=1;i<barklook_size;i++){
    if(n<barklook[i]){
      i--;
      return (i<<14)+(((n-barklook[i])*
                 ((1UL<<31)/(barklook[i+1]-barklook[i])))>>17);
    }
  }
  /* for a valid input n, which is half of info->rate (i.e. max 32767
     as info->rate is 16 bit unsigned value), loop above will return
     an output. So the following return will be used only when toBARK()
     is called with invalid value */
  return (barklook_size-1)<<14;
}

static const unsigned char MLOOP_1[64]={
   0,10,11,11, 12,12,12,12, 13,13,13,13, 13,13,13,13,
  14,14,14,14, 14,14,14,14, 14,14,14,14, 14,14,14,14,
  15,15,15,15, 15,15,15,15, 15,15,15,15, 15,15,15,15,
  15,15,15,15, 15,15,15,15, 15,15,15,15, 15,15,15,15,
};

static const unsigned char MLOOP_2[64]={
  0,4,5,5, 6,6,6,6, 7,7,7,7, 7,7,7,7,
  8,8,8,8, 8,8,8,8, 8,8,8,8, 8,8,8,8,
  9,9,9,9, 9,9,9,9, 9,9,9,9, 9,9,9,9,
  9,9,9,9, 9,9,9,9, 9,9,9,9, 9,9,9,9,
};

static const unsigned char MLOOP_3[8]={0,1,2,2,3,3,3,3};

void vorbis_lsp_to_curve(ogg_int32_t *curve,int n,int ln,
                         ogg_int32_t *lsp,int m,
                         ogg_int32_t amp,
                         ogg_int32_t ampoffset,
                         ogg_int32_t nyq){

  /* 0 <= m < 256 */

  /* set up for using all int later */
  int i;
  int ampoffseti=ampoffset*4096;
  int ampi=amp;
  ogg_int32_t *ilsp=(ogg_int32_t *)alloca(m*sizeof(*ilsp));

  ogg_uint32_t inyq= (1UL<<31) / toBARK(nyq);
  ogg_uint32_t imap= (1UL<<31) / ln;
  ogg_uint32_t tBnyq1 = toBARK(nyq)<<1;

  /* Besenham for frequency scale to avoid a division */
  int f=0;
  int fdx=n;
  int fbase=nyq/fdx;
  int ferr=0;
  int fdy=nyq-fbase*fdx;
  int map=0;

#ifdef _LOW_ACCURACY_
  ogg_uint32_t nextbark=((tBnyq1<<11)/ln)>>12;
#else
  ogg_uint32_t nextbark=MULT31(imap>>1,tBnyq1);
#endif
  /* nextbark is guaranteed to be less than 54 << 14 here and that ensures index
     to barklook can at max be 53 and 54 here */
  int nextf=barklook[nextbark>>14]+(((nextbark&0x3fff)*
            (barklook[(nextbark>>14)+1]-barklook[nextbark>>14]))>>14);

  /* lsp is in 8.24, range 0 to PI; coslook wants it in .16 0 to 1*/
  for(i=0;i<m;i++){
#ifndef _LOW_ACCURACY_
    ogg_int32_t val=MULT32(lsp[i],0x517cc2);
#else
    ogg_int32_t val=((lsp[i]>>10)*0x517d)>>14;
#endif

    /* safeguard against a malicious stream */
    if(val<0 || (val>>COS_LOOKUP_I_SHIFT)>=COS_LOOKUP_I_SZ){
      memset(curve,0,sizeof(*curve)*n);
      return;
    }

    ilsp[i]=vorbis_coslook_i(val);
  }

  i=0;
  while(i<n){
    int j;
    ogg_uint32_t pi=46341; /* 2**-.5 in 0.16 */
    ogg_uint32_t qi=46341;
    ogg_int32_t qexp=0,shift;
    ogg_int32_t wi;

    wi=vorbis_coslook2_i((map*imap)>>15);


#ifdef _V_LSP_MATH_ASM
    lsp_loop_asm(&qi,&pi,&qexp,ilsp,wi,m);

    pi=((pi*pi)>>16);
    qi=((qi*qi)>>16);

    if(m&1){
      qexp= qexp*2-28*((m+1)>>1)+m;
      pi*=(1<<14)-((wi*wi)>>14);
      qi+=pi>>14;
    }else{
      qexp= qexp*2-13*m;

      pi*=(1<<14)-wi;
      qi*=(1<<14)+wi;

      qi=(qi+pi)>>14;
    }

    if(qi&0xffff0000){ /* checks for 1.xxxxxxxxxxxxxxxx */
      qi>>=1; qexp++;
    }else
      lsp_norm_asm(&qi,&qexp);

#else

    qi*=labs(ilsp[0]-wi);
    pi*=labs(ilsp[1]-wi);

    for(j=3;j<m;j+=2){
      if(!(shift=MLOOP_1[(pi|qi)>>25]))
        if(!(shift=MLOOP_2[(pi|qi)>>19]))
          shift=MLOOP_3[(pi|qi)>>16];

      qi=(qi>>shift)*labs(ilsp[j-1]-wi);
      pi=(pi>>shift)*labs(ilsp[j]-wi);
      qexp+=shift;
    }
    if(!(shift=MLOOP_1[(pi|qi)>>25]))
      if(!(shift=MLOOP_2[(pi|qi)>>19]))
        shift=MLOOP_3[(pi|qi)>>16];

    /* pi,qi normalized collectively, both tracked using qexp */

    if(m&1){
      /* odd order filter; slightly assymetric */
      /* the last coefficient */
      qi=(qi>>shift)*labs(ilsp[j-1]-wi);
      pi=(pi>>shift)<<14;
      qexp+=shift;

      if(!(shift=MLOOP_1[(pi|qi)>>25]))
        if(!(shift=MLOOP_2[(pi|qi)>>19]))
          shift=MLOOP_3[(pi|qi)>>16];

      pi>>=shift;
      qi>>=shift;
      qexp+=shift-14*((m+1)>>1);

      pi=((pi*pi)>>16);
      qi=((qi*qi)>>16);
      qexp=qexp*2+m;

      pi*=(1<<14)-((wi*wi)>>14);
      qi+=pi>>14;

    }else{
      /* even order filter; still symmetric */

      /* p*=p(1-w), q*=q(1+w), let normalization drift because it isn't
         worth tracking step by step */

      pi>>=shift;
      qi>>=shift;
      qexp+=shift-7*m;

      pi=((pi*pi)>>16);
      qi=((qi*qi)>>16);
      qexp=qexp*2+m;

      pi*=(1<<14)-wi;
      qi*=(1<<14)+wi;
      qi=(qi+pi)>>14;

    }


    /* we've let the normalization drift because it wasn't important;
       however, for the lookup, things must be normalized again.  We
       need at most one right shift or a number of left shifts */

    if(qi&0xffff0000){ /* checks for 1.xxxxxxxxxxxxxxxx */
      qi>>=1; qexp++;
    }else
      while(qi && !(qi&0x8000)){ /* checks for 0.0xxxxxxxxxxxxxxx or less*/
        qi<<=1; qexp--;
      }

#endif

    amp=vorbis_fromdBlook_i(ampi*                     /*  n.4         */
                            vorbis_invsqlook_i(qi,qexp)-
                                                      /*  m.8, m+n<=8 */
                            ampoffseti);              /*  8.12[0]     */

#ifdef _LOW_ACCURACY_
    amp>>=9;
#endif
    curve[i]= MULT31_SHIFT15(curve[i],amp);

    while(++i<n){

      /* line plot to get new f */
      ferr+=fdy;
      if(ferr>=fdx){
        ferr-=fdx;
        f++;
      }
      f+=fbase;

      if(f>=nextf)break;

      curve[i]= MULT31_SHIFT15(curve[i],amp);
    }

    while(1){
      map++;

      if(map+1<ln){

#ifdef _LOW_ACCURACY_
        nextbark=((tBnyq1<<11)/ln*(map+1))>>12;
#else
        nextbark=MULT31((map+1)*(imap>>1),tBnyq1);
#endif
        /* nextbark is guaranteed to be less than 54 << 14 here and that ensures index
           to barklook can at max be 53 and 54 here */
        nextf=barklook[nextbark>>14]+
          (((nextbark&0x3fff)*
            (barklook[(nextbark>>14)+1]-barklook[nextbark>>14]))>>14);
        if(f<=nextf)break;

      }else{
        nextf=9999999;
        break;
      }
    }
    if(map>=ln){
      map=ln-1; /* guard against the approximation */
      nextf=9999999;
    }
  }
}

/*************** vorbis decode glue ************/

void floor0_free_info(vorbis_info_floor *i){
  vorbis_info_floor0 *info=(vorbis_info_floor0 *)i;
  if(info)_ogg_free(info);
}

vorbis_info_floor *floor0_info_unpack (vorbis_info *vi,oggpack_buffer *opb){
  codec_setup_info     *ci=(codec_setup_info *)vi->codec_setup;
  int j;

  vorbis_info_floor0 *info=(vorbis_info_floor0 *)_ogg_malloc(sizeof(*info));
  info->order=oggpack_read(opb,8);
  info->rate=oggpack_read(opb,16);
  info->barkmap=oggpack_read(opb,16);
  info->ampbits=oggpack_read(opb,6);
  info->ampdB=oggpack_read(opb,8);
  info->numbooks=oggpack_read(opb,4)+1;

  /* order must be greater than 1 to calculate p and q parameters for the linear floor value */
  if(info->order<=1)goto err_out;
  if(info->rate<1)goto err_out;
  if(info->barkmap<1)goto err_out;

  for(j=0;j<info->numbooks;j++){
    info->books[j]=(char)oggpack_read(opb,8);
    if(info->books[j]>=ci->books)goto err_out;
  }

  if(oggpack_eop(opb))goto err_out;
  return(info);

 err_out:
  floor0_free_info(info);
  return(NULL);
}

int floor0_memosize(vorbis_info_floor *i){
  vorbis_info_floor0 *info=(vorbis_info_floor0 *)i;
  return info->order+1;
}

ogg_int32_t *floor0_inverse1(vorbis_dsp_state *vd,vorbis_info_floor *i,
                             ogg_int32_t *lsp){
  vorbis_info_floor0 *info=(vorbis_info_floor0 *)i;
  int j,k;
  uint64_t ampraw;
  if(info->ampbits<=32){
    ampraw=oggpack_read(&vd->opb,info->ampbits);
  }else{
    //max value possible for info->ampbits is 63
    ampraw=oggpack_read(&vd->opb,info->ampbits-32);
    ampraw<<=32;
    ampraw|=oggpack_read(&vd->opb,32);
  }
  if(ampraw>0){ /* also handles the -1 out of data case */
    uint64_t maxval=(1<<info->ampbits)-1;
    float ampRatio=(float)ampraw/maxval;
    int amp=ampRatio*(info->ampdB<<4);
    int booknum=oggpack_read(&vd->opb,_ilog(info->numbooks));
    if(booknum!=-1 && booknum<info->numbooks){ /* be paranoid */
      codec_setup_info  *ci=(codec_setup_info *)vd->vi->codec_setup;
      codebook *b=ci->book_param+info->books[booknum];
      int sz = floor0_memosize(i);
      if (sz < b->dim) {
          ALOGE("lsp too small: %d < %ld", sz, b->dim);
          return NULL;
      }
      ogg_int32_t last=0;

      if(vorbis_book_decodev_set(b,lsp,&vd->opb,info->order,-24)==-1)goto eop;
      for(j=0;j<info->order;){
        for(k=0;k<b->dim && j<info->order;k++,j++)lsp[j]+=last;
        last=lsp[j-1];
      }

      lsp[info->order]=amp;
      return(lsp);
    }
  }
 eop:
  return(NULL);
}

int floor0_inverse2(vorbis_dsp_state *vd,vorbis_info_floor *i,
                           ogg_int32_t *lsp,ogg_int32_t *out){
  vorbis_info_floor0 *info=(vorbis_info_floor0 *)i;
  codec_setup_info  *ci=(codec_setup_info *)vd->vi->codec_setup;

  if(lsp){
    ogg_int32_t amp=lsp[info->order];

    /* take the coefficients back to a spectral envelope curve */
    vorbis_lsp_to_curve(out,ci->blocksizes[vd->W]/2,info->barkmap,
                        lsp,info->order,amp,info->ampdB,
                        info->rate>>1);
    return(1);
  }
  memset(out,0,sizeof(*out)*ci->blocksizes[vd->W]/2);
  return(0);
}

