/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: vorbis_mode_info.c,v 1.2.4.1 2004/11/24 18:02:52 acolwell Exp $
 * 
 * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved.
 * 
 * The contents of this file, and the files included with this file,
 * are subject to the current version of the RealNetworks Public
 * Source License (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (the "RCSL") available at
 * http://www.helixcommunity.org/content/rcsl, in which case the RCSL
 * will apply. You may also obtain the license terms directly from
 * RealNetworks.  You may not use this file except in compliance with
 * the RPSL or, if you have a valid RCSL with RealNetworks applicable
 * to this file, the RCSL.  Please see the applicable RPSL or RCSL for
 * the rights, obligations and limitations governing use of the
 * contents of the file.
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. If you do not delete the provisions above, a recipient may
 * use your version of this file under the terms of any one of the
 * RPSL, the RCSL or the GPL.
 * 
 * This file is part of the Helix DNA Technology. RealNetworks is the
 * developer of the Original Code and owns the copyrights in the
 * portions it created.
 * 
 * This file, and the files included with this file, is distributed
 * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
 * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
 * ENJOYMENT OR NON-INFRINGEMENT.
 * 
 * Technology Compatibility Kit Test Suite(s) Location:
 *    http://www.helixcommunity.org/content/tck
 * 
 * Contributor(s):
 * 
 * ***** END LICENSE BLOCK ***** */

/* 
 * A bunch of the code in this file was taken from the Vorbis source code
 * and stripped down.
 * Here is the license header from the Vorbis source files.
 */
/********************************************************************
 *                                                                  *
 * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE.   *
 * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS     *
 * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE *
 * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING.       *
 *                                                                  *
 * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2003             *
 * by the XIPHOPHORUS Company http://www.xiph.org/                  *
 *                                                                  *
 ********************************************************************/

#include "ogg/ogg.h"
#include "math.h"
#include "stdlib.h"
#include "memory.h"

#include "vorbis_mode_info.h"

#define VI_TRANSFORMB 1
#define VI_WINDOWB 1
#define VI_TIMEB 1
#define VI_FLOORB 2
#define VI_RESB 3
#define VI_MAPB 1

/* helpers */
static int 
ilog(unsigned int v)
{
    int ret = 0;
    while(v)
    {
        ret++;
        v >>=1;
    }
    return(ret);
}

static int 
ilog2(unsigned int v)
{
    int ret = 0;
    if (v)
        --v;
    
    return ilog(v);
}

static void 
_v_readstring(oggpack_buffer *o, char *buf, int bytes)
{
    while (bytes--)
    {
        *buf++ = (char)oggpack_read(o, 8);
    }
}

static int 
icount(unsigned int v)
{
    int ret = 0;
    while(v)
    {
        ret += v & 1;
        v >>= 1;
    }

    return(ret);
}

static int 
floor0_unpack (oggpack_buffer *opb, int totalBooks)
{
    int j;

    int order = oggpack_read(opb,8);
    int rate = oggpack_read(opb,16);
    int barkmap = oggpack_read(opb,16);
    int ampbits = oggpack_read(opb,6);
    int ampdB = oggpack_read(opb,8);
    int numbooks = oggpack_read(opb,4)+1;
  
    if (order < 1)
        goto err_out;

    if (rate < 1)
        goto err_out;

    if (barkmap < 1)
        goto err_out;

    if (numbooks < 1)
        goto err_out;
    
    for(j = 0; j < numbooks; j++)
    {
        int books = oggpack_read(opb,8);
        if(books < 0 || books >= totalBooks)
            goto err_out;
    }

    return 1;

 err_out:
    return 0;
}

#define VIF_CLASS 16
#define VIF_PARTS 32

static int
floor1_unpack (oggpack_buffer *opb, int totalBooks)
{
    int j,k,count=0,maxclass=-1,rangebits;
    int mult;

    int partitionclass[VIF_PARTS];
    int class_dim[VIF_CLASS];

    /* read partitions */
    int partitions = oggpack_read(opb,5); /* only 0 to 31 legal */
    for(j = 0; j < partitions; j++)
    {
        partitionclass[j] = oggpack_read(opb,4); /* only 0 to 15 legal */
        if(maxclass < partitionclass[j])
            maxclass = partitionclass[j];
    }

    /* read partition classes */
    for(j = 0; j < maxclass + 1; j++)
    {
        int class_subs, class_book = 0;
        class_dim[j] = oggpack_read(opb,3) + 1; /* 1 to 8 */
        class_subs = oggpack_read(opb,2); /* 0,1,2,3 bits */

        if(class_subs < 0)
            goto err_out;

        if(class_subs)
            class_book = oggpack_read(opb,8);

        if(class_book < 0 || class_book >= totalBooks)
            goto err_out;

        for(k = 0; k < (1 << class_subs); k++)
        {
            int subbook = oggpack_read(opb,8) - 1;
            if(subbook < -1 || subbook >= totalBooks)
                goto err_out;
        }
    }

    /* read the post list */
    mult = oggpack_read(opb,2) + 1;     /* only 1,2,3,4 legal now */ 
    rangebits=oggpack_read(opb,4);

    for(j = 0, k = 0; j < partitions; j++)
    {
        count += class_dim[partitionclass[j]]; 
        for(; k < count; k++)
        {
            int t = oggpack_read(opb,rangebits);
            if (t < 0 || t >= (1<<rangebits))
                goto err_out;
        }
    }

    return 1;
  
 err_out:
    return 0;
}

static int 
res0_unpack(oggpack_buffer *opb, int totalBooks)
{
    int j,acc=0;

    int begin = oggpack_read(opb,24);
    int end = oggpack_read(opb,24);
    int grouping = oggpack_read(opb,24) + 1;
    int partitions = oggpack_read(opb,6) + 1;
    int groupbook = oggpack_read(opb,8);
    int booklist[256];

    for (j = 0; j < partitions; j++)
    {
        int cascade = oggpack_read(opb,3);

        if (oggpack_read(opb,1))
            cascade |= (oggpack_read(opb,5) << 3);

        acc += icount(cascade);
    }
    
    for(j = 0; j < acc; j++)
        booklist[j] = oggpack_read(opb,8);

    if(groupbook >= totalBooks)
        goto errout;

    for(j = 0; j < acc; j++)
        if(booklist[j] >= totalBooks)
            goto errout;

    return 1;

 errout:
    return 0;
}

static int
mapping0_unpack(oggpack_buffer *opb, int channels,
                int totalFloors, int totalResidues)
{
    int i;
    int submaps = 1;

    if(oggpack_read(opb,1))
        submaps = oggpack_read(opb,4) + 1;

    if(oggpack_read(opb,1))
    {
        int coupling_steps = oggpack_read(opb,8) + 1;

        for(i = 0; i < coupling_steps; i++)
        {
            int testM = oggpack_read(opb, ilog2(channels));
            int testA = oggpack_read(opb, ilog2(channels));

            if(testM < 0 || testA < 0 || testM == testA || 
               testM >= channels || testA >= channels) 
                goto err_out;
        }

    }

    if(oggpack_read(opb,2) > 0)
        goto err_out; /* 2,3:reserved */
    
    if(submaps > 1)
    {
        for(i = 0; i < channels; i++)
        {
            int chmuxlist = oggpack_read(opb,4);
            if(chmuxlist >= submaps)
                goto err_out;
        }
    }
    
    for(i = 0; i < submaps; i++)
    {
        int floorsubmap, residuesubmap;

        oggpack_read(opb,8); /* time submap unused */

        floorsubmap = oggpack_read(opb,8);
        if(floorsubmap >= totalFloors)
            goto err_out;

        residuesubmap = oggpack_read(opb,8);
        if(residuesubmap >= totalResidues)
            goto err_out;
    }

    return 1;

 err_out:
    return 0;
}


typedef int (*floor_unpack_func)(oggpack_buffer *, int totalBooks);
typedef int (*residue_unpack_func)(oggpack_buffer *, int totalBooks);
typedef int (*mapping_unpack_func)(oggpack_buffer *opb, int channels,
                                   int totalFloors, int totalResidues);

static floor_unpack_func _floor_P[] = {
    &floor0_unpack,
    &floor1_unpack
};

static residue_unpack_func _residue_P[] = {
    &res0_unpack,
    &res0_unpack,
    &res0_unpack
};

static mapping_unpack_func _mapping_P[] = {
  &mapping0_unpack
};

/* there might be a straightforward one-line way to do the below
   that's portable and totally safe against roundoff, but I haven't
   thought of it.  Therefore, we opt on the side of caution */
static long 
_book_maptype1_quantvals(int entries, int dim)
{
    long vals = floor(pow((float)entries, 1.f / dim));

    /* the above *should* be reliable, but we'll not assume that FP is
       ever reliable when bitstream sync is at stake; verify via integer
       means that vals really is the greatest value of dim for which
       vals^dim <= entries */
    /* treat the above as an initial guess */
    while(1)
    {
        long acc = 1;
        long acc1 = 1;
        int i;
        for(i = 0; i < dim; i++)
        {
            acc *= vals;
            acc1 *= vals + 1;
        }

        if (acc <= entries && acc1 > entries)
        {
            return (vals);
        }
        else
        {
            if(acc > entries)
            {
                vals--;
            }
            else
            {
                vals++;
            }
        }
    }
}


static int 
vorbis_staticbook_unpack(oggpack_buffer *opb)
{
    long i,j;
    int dim, entries;
    int maptype;

    /* make sure alignment is correct */
    if (oggpack_read(opb,24) != 0x564342)
        goto _eofout;

    /* first the basic parameters */
    dim = oggpack_read(opb, 16);
    entries = oggpack_read(opb,24);

    if (entries == -1)
        goto _eofout;

    /* codeword ordering.... length ordered or unordered? */
    switch((int)oggpack_read(opb, 1)) {
    case 0:
    {
        /* unordered */

        /* allocated but unused entries? */
        if(oggpack_read(opb, 1))
        {
            /* yes, unused entries */
            for(i = 0; i < entries; i++)
            {
                if(oggpack_read(opb, 1))
                {
                    long num = oggpack_read(opb,5);
                    if (num == -1)
                        goto _eofout;
                }
            }
        }
        else
        {
            /* all entries used; no tagging */
            for(i = 0; i < entries; i++)
            {
                long num = oggpack_read(opb,5);
                if (num == -1)
                    goto _eofout;
            }
        }
    
    }break;
    case 1:
    {
        /* ordered */
        long length = oggpack_read(opb,5) + 1;

        for(i = 0; i < entries;)
        {
            long num = oggpack_read(opb, ilog(entries - i));
            if (num == -1)
                goto _eofout;
            for(j = 0; j < num && i < entries; j++, i++)
                ;
        }
    }
    break;
    default:
        /* EOF */
        return(-1);
    }
  
    /* Do we have a mapping to unpack? */
    maptype = oggpack_read(opb,4);
    switch(maptype){
    case 0:
        /* no mapping */
        break;
    case 1: case 2:
    {        
        /* implicitly populated value mapping */
        /* explicitly populated value mapping */

        int q_min       = oggpack_read(opb,32);
        int q_delta     = oggpack_read(opb,32);
        int q_quant     = oggpack_read(opb,4) + 1;
        int q_sequencep = oggpack_read(opb,1);

        {
            int quantvals=0;
            int tmp = -1;

            switch(maptype){
            case 1:
                quantvals = _book_maptype1_quantvals(entries, dim);
                break;
            case 2:
                quantvals = entries * dim;
                break;
            }
      
            /* quantized values */
            for(i = 0; i < quantvals; i++)
                tmp = oggpack_read(opb, q_quant);
      
            if (quantvals && tmp == -1)
                goto _eofout;
        }
    }break;
    default:
        goto _errout;
    }

    /* all set */
    return(0);
  
 _errout:
 _eofout:
    return(-1); 
}


/* all of the real encoding details are here.  The modes, books,
   everything */
static int 
_vorbis_unpack_books(vorbis_mode_info* vmi, oggpack_buffer *opb)
{
    int i;
    /* codebooks */
    int books = oggpack_read(opb,8) + 1;
    int floors, residues, maps;

    for(i = 0;i < books; i++)
    {
        if(vorbis_staticbook_unpack(opb))
            goto err_out;
    }

    /* time backend settings; hooks are unused */
    {
        int times = oggpack_read(opb,6) + 1;
        for(i = 0; i < times; i++)
        {
            int test = oggpack_read(opb,16);
            if (test < 0 || test >= VI_TIMEB)
                goto err_out;
        }
    }

    /* floor backend settings */
    floors = oggpack_read(opb,6) + 1;

    for(i = 0; i < floors; i++)
    {
        int floor_type = oggpack_read(opb,16);

        if (floor_type < 0 || floor_type >= VI_FLOORB)
            goto err_out;

        if (!_floor_P[floor_type](opb, books))
            goto err_out;
    }

    /* residue backend settings */
    residues = oggpack_read(opb,6) + 1;

    for(i = 0; i < residues; i++)
    {
        int residue_type = oggpack_read(opb,16);

        if(residue_type < 0 || residue_type >= VI_RESB)
            goto err_out;

        if(!_residue_P[residue_type](opb, books))
           goto err_out;
    }

    /* map backend settings */
    maps = oggpack_read(opb,6) + 1;

    for(i = 0; i < maps; i++)
    {
        int map_type = oggpack_read(opb,16);

        if(map_type < 0 || map_type >= VI_MAPB)
            goto err_out;

        if(!_mapping_P[map_type](opb, vmi->channels,
                                 floors, residues))
            goto err_out;
    }
  
    /* mode settings */

    vmi->modecount = oggpack_read(opb,6) + 1;
    vmi->modebits = ilog2(vmi->modecount);
    vmi->blockflags = malloc(vmi->modecount);

    if (vmi->blockflags)
    {
        for(i = 0; i < vmi->modecount; i++)
        {
            int windowtype, transformtype, mapping;

            vmi->blockflags[i] = (char)oggpack_read(opb,1);
            windowtype    = oggpack_read(opb,16);
            transformtype = oggpack_read(opb,16);
            mapping       = oggpack_read(opb,8);

            if (windowtype >= VI_WINDOWB)
                goto err_out;

            if (transformtype >= VI_WINDOWB)
                goto err_out;

            if (mapping >= maps)
                goto err_out;
        }
    }
  
    if (oggpack_read(opb, 1) != 1)
        goto err_out; /* top level EOP check */

    return 1;
 err_out:

    return 0;
}

static int 
_vorbis_unpack_info(vorbis_mode_info* vmi, oggpack_buffer *opb)
{
    int version, rate, bitrate_upper, bitrate_nominal, bitrate_lower;

    if(!vmi)
        return 0;

    version = oggpack_read(opb,32);
    if(version != 0)
        return 0;

    vmi->channels = oggpack_read(opb,8);
    rate = oggpack_read(opb,32);

    bitrate_upper = oggpack_read(opb,32);
    bitrate_nominal = oggpack_read(opb,32);
    bitrate_lower = oggpack_read(opb,32);

    vmi->blocksizes[0] = 1 << oggpack_read(opb,4);
    vmi->blocksizes[1] = 1 << oggpack_read(opb,4);
  
    if (rate < 1)
        goto err_out;

    if (vmi->channels < 1)
        goto err_out;
    
    if (vmi->blocksizes[0] < 8)
        goto err_out; 

    if (vmi->blocksizes[1] < vmi->blocksizes[0])
        goto err_out;
  
    if (oggpack_read(opb,1) != 1)
        goto err_out; /* EOP check */

    return 1;

 err_out:
  return 0;
}

void vorbis_mode_info_init(vorbis_mode_info* vmi)
{
    if (vmi)
    {
        memset(vmi, 0, sizeof(vorbis_mode_info));
    }
}

void vorbis_mode_info_clear(vorbis_mode_info* vmi)
{
    if (vmi)
    {
        if (vmi->blockflags)
        {
            free(vmi->blockflags);
        }
        memset(vmi, 0, sizeof(vorbis_mode_info));
    }
}

int vorbis_mode_info_headerin(vorbis_mode_info* vmi, ogg_packet* op)
{
    int ret = 0;
    oggpack_buffer opb;
  
    if (vmi && op)
    {
        char buffer[6];
        int packtype;

        oggpack_readinit(&opb,op->packet,op->bytes);

        packtype = oggpack_read(&opb,8);
        memset(buffer,0,6);
        _v_readstring(&opb,buffer,6);

        if (!memcmp(buffer,"vorbis",6))
        {
            if (0x01 == packtype)
            {
                ret = _vorbis_unpack_info(vmi, &opb);
            }
            else if (0x03 == packtype)
            {
                ret = 1;
            }
            else if (0x05 == packtype)
            {
                ret = _vorbis_unpack_books(vmi, &opb);
            }
        }
    }
    
    return ret;
}

int vorbis_mode_info_getblocksize(vorbis_mode_info* vmi, ogg_packet* op)
{
    int ret = 0;

    if (vmi && op)
    {
        if (vmi->modebits)
        {
            oggpack_buffer  opb;
            oggpack_readinit(&opb, op->packet, op->bytes);
            
            if(oggpack_read(&opb,1) == 0)
            {
                int mode = oggpack_read(&opb, vmi->modebits);
                
                if ((mode >= 0) && (mode < vmi->modecount))
                {
                    ret = vmi->blocksizes[vmi->blockflags[mode]];
                }
            }
        }
        else if (vmi->modecount)
        {
            ret = vmi->blocksizes[0];
        }
    }

    return ret;
}

int vorbis_mode_info_copy(vorbis_mode_info* dest, 
                          const vorbis_mode_info* src)
{
    int ret = 0;

    if (dest && src && src->modecount && src->blockflags)
    {
        vorbis_mode_info_clear(dest);

        *dest = *src;
        dest->blockflags = malloc(src->modecount);
        
        if (dest->blockflags)
        {
            memcpy(dest->blockflags, src->blockflags, src->modecount);
            ret = 1;
        }
        else
        {
            vorbis_mode_info_clear(dest);
        }
    }

    return ret;
}
