/* JOrbis
 * Copyright (C) 2000 ymnk, JCraft,Inc.
 *  
 * Written by: 2000 ymnk<ymnk@jcraft.com>
 *   
 * Many thanks to 
 *   Monty <monty@xiph.org> and 
 *   The XIPHOPHORUS Company http://www.xiph.org/ .
 * JOrbis has been based on their awesome works, Vorbis codec.
 *   
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public License
 * as published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
   
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Library General Public License for more details.
 * 
 * You should have received a copy of the GNU Library General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

package com.jcraft.jorbis;

import com.jcraft.jogg.*;

public class Info{
  private static final int OV_EBADPACKET=-136;
  private static final int OV_ENOTAUDIO=-135;

  private static byte[] _vorbis="vorbis".getBytes();
  private static final int VI_TIMEB=1;
//  private static final int VI_FLOORB=1;
  private static final int VI_FLOORB=2;
//  private static final int VI_RESB=1;
  private static final int VI_RESB=3;
  private static final int VI_MAPB=1;
  private static final int VI_WINDOWB=1;

  public int version;
  public int channels;
  public int rate;

  // The below bitrate declarations are *hints*.
  // Combinations of the three values carry the following implications:
  //     
  // all three set to the same value: 
  // implies a fixed rate bitstream
  // only nominal set: 
  // implies a VBR stream that averages the nominal bitrate.  No hard 
  // upper/lower limit
  // upper and or lower set: 
  // implies a VBR bitstream that obeys the bitrate limits. nominal 
  // may also be set to give a nominal rate.
  // none set:
  //  the coder does not care to speculate.

  int bitrate_upper;
  int bitrate_nominal;
  int bitrate_lower;
  
  // Vorbis supports only short and long blocks, but allows the
  // encoder to choose the sizes

  int[] blocksizes=new int[2];

  // modes are the primary means of supporting on-the-fly different
  // blocksizes, different channel mappings (LR or mid-side),
  // different residue backends, etc.  Each mode consists of a
  // blocksize flag and a mapping (along with the mapping setup

  int modes;
  int maps;
  int times;
  int floors;
  int residues;
  int books;
  int psys;     // encode only

  InfoMode[] mode_param=null;

  int[] map_type=null;
  Object[] map_param=null;

  int[] time_type=null;
  Object[] time_param=null;

  int[] floor_type=null;
  Object[] floor_param=null;

  int[] residue_type=null;
  Object[] residue_param=null;

  StaticCodeBook[] book_param=null;

  PsyInfo[] psy_param=new PsyInfo[64]; // encode only
  
  // for block long/sort tuning; encode only
  int envelopesa;
  float preecho_thresh;
  float preecho_clamp;

  // used by synthesis, which has a full, alloced vi
  public void init(){
    rate=0;
    //memset(vi,0,sizeof(vorbis_info));
  }

  public void clear(){
    for(int i=0;i<modes;i++){ mode_param[i]=null; }
    mode_param=null;

    for(int i=0;i<maps;i++){ // unpack does the range checking
      FuncMapping.mapping_P[map_type[i]].free_info(map_param[i]);
    }
    map_param=null;

    for(int i=0;i<times;i++){ // unpack does the range checking
      FuncTime.time_P[time_type[i]].free_info(time_param[i]);
    }
    time_param=null;

    for(int i=0;i<floors;i++){ // unpack does the range checking
      FuncFloor.floor_P[floor_type[i]].free_info(floor_param[i]);
    }
    floor_param=null;

    for(int i=0;i<residues;i++){ // unpack does the range checking
      FuncResidue.residue_P[residue_type[i]].free_info(residue_param[i]);
    }
    residue_param=null;

    // the static codebooks *are* freed if you call info_clear, because
    // decode side does alloc a 'static' codebook. Calling clear on the
    // full codebook does not clear the static codebook (that's our
    // responsibility)
    for(int i=0;i<books;i++){
      // just in case the decoder pre-cleared to save space
      if(book_param[i]!=null){
	book_param[i].clear();
	book_param[i]=null;
      }
    }
    //if(vi->book_param)free(vi->book_param);
    book_param=null;

    for(int i=0;i<psys;i++){
      psy_param[i].free();
    }
    //if(vi->psy_param)free(vi->psy_param);
    //memset(vi,0,sizeof(vorbis_info));
  }

  // Header packing/unpacking
  int unpack_info(Buffer opb){
    version=opb.read(32);
    if(version!=0)return(-1);

    channels=opb.read(8);
    rate=opb.read(32);

    bitrate_upper=opb.read(32);
    bitrate_nominal=opb.read(32);
    bitrate_lower=opb.read(32);

    blocksizes[0]=1<<opb.read(4);
    blocksizes[1]=1<<opb.read(4);
  
    if((rate<1) ||
       (channels<1)||
       (blocksizes[0]<8)||
       (blocksizes[1]<blocksizes[0]) ||
       (opb.read(1)!=1)){
      //goto err_out; // EOP check
      clear();
      return(-1);
    }
    return(0);
    // err_out:
    // vorbis_info_clear(vi);
    // return(-1);
  }

  // all of the real encoding details are here.  The modes, books,
  // everything
  int unpack_books(Buffer opb){

    //d* codebooks
    books=opb.read(8)+1;

    if(book_param==null || book_param.length!=books)
      book_param=new StaticCodeBook[books];
    for(int i=0;i<books;i++){
      book_param[i]=new StaticCodeBook();
      if(book_param[i].unpack(opb)!=0){
	//goto err_out;
	clear();
	return(-1);
      }
    }

    // time backend settings
    times=opb.read(6)+1;
    if(time_type==null || time_type.length!=times) time_type=new int[times];
    if(time_param==null || time_param.length!=times)
      time_param=new Object[times];
    for(int i=0;i<times;i++){
      time_type[i]=opb.read(16);
      if(time_type[i]<0 || time_type[i]>=VI_TIMEB){
	//goto err_out;
	clear();
	return(-1);
      }
      time_param[i]=FuncTime.time_P[time_type[i]].unpack(this, opb);
      if(time_param[i]==null){
	//goto err_out;
	clear();
	return(-1);
      }
    }

    // floor backend settings
    floors=opb.read(6)+1;
    if(floor_type==null || floor_type.length!=floors)
      floor_type=new int[floors];
    if(floor_param==null || floor_param.length!=floors)
      floor_param=new Object[floors];

    for(int i=0;i<floors;i++){
      floor_type[i]=opb.read(16);
      if(floor_type[i]<0 || floor_type[i]>=VI_FLOORB){
	//goto err_out;
	clear();
	return(-1);
      }

      floor_param[i]=FuncFloor.floor_P[floor_type[i]].unpack(this,opb);
      if(floor_param[i]==null){
	//goto err_out;
	clear();
	return(-1);
      }
    }

    // residue backend settings
    residues=opb.read(6)+1;

    if(residue_type==null || residue_type.length!=residues)
      residue_type=new int[residues];

    if(residue_param==null || residue_param.length!=residues)
      residue_param=new Object[residues];

    for(int i=0;i<residues;i++){
      residue_type[i]=opb.read(16);
      if(residue_type[i]<0 || residue_type[i]>=VI_RESB){
//	goto err_out;
	clear();
	return(-1);
      }
      residue_param[i]=FuncResidue.residue_P[residue_type[i]].unpack(this,opb);
      if(residue_param[i]==null){
//	goto err_out;
	clear();
	return(-1);
      }
    }

    // map backend settings
    maps=opb.read(6)+1;
    if(map_type==null || map_type.length!=maps)  map_type=new int[maps];
    if(map_param==null || map_param.length!=maps)  map_param=new Object[maps];
    for(int i=0;i<maps;i++){
      map_type[i]=opb.read(16);
      if(map_type[i]<0 || map_type[i]>=VI_MAPB){
//	goto err_out;
	clear();
	return(-1);
      }
      map_param[i]=FuncMapping.mapping_P[map_type[i]].unpack(this,opb);
      if(map_param[i]==null){
//    goto err_out;
	clear();
	return(-1);
      }
    }

  // mode settings
    modes=opb.read(6)+1;
    if(mode_param==null || mode_param.length!=modes)
      mode_param=new InfoMode[modes];
    for(int i=0;i<modes;i++){
      mode_param[i]=new InfoMode();
      mode_param[i].blockflag=opb.read(1);
      mode_param[i].windowtype=opb.read(16);
      mode_param[i].transformtype=opb.read(16);
      mode_param[i].mapping=opb.read(8);

      if((mode_param[i].windowtype>=VI_WINDOWB)||
	 (mode_param[i].transformtype>=VI_WINDOWB)||
	 (mode_param[i].mapping>=maps)){
//      goto err_out;
	clear();
	return(-1);
      }
    }

    if(opb.read(1)!=1){
    //goto err_out; // top level EOP check
      clear();
      return(-1);
    }

    return(0);
// err_out:
//  vorbis_info_clear(vi);
//  return(-1);
  }

  // The Vorbis header is in three packets; the initial small packet in
  // the first page that identifies basic parameters, a second packet
  // with bitstream comments and a third packet that holds the
  // codebook.

  public int synthesis_headerin(Comment vc, Packet op){
    Buffer opb=new Buffer();

    if(op!=null){  
      opb.readinit(op.packet_base, op.packet, op.bytes);

      // Which of the three types of header is this?
      // Also verify header-ness, vorbis
      {
        byte[] buffer=new byte[6];
        int packtype=opb.read(8);
        //memset(buffer,0,6);
        opb.read(buffer,6);
        if(buffer[0]!='v' || buffer[1]!='o' || buffer[2]!='r' ||
           buffer[3]!='b' || buffer[4]!='i' || buffer[5]!='s'){
  	// not a vorbis header
  	  return(-1);
        }
        switch(packtype){
        case 0x01: // least significant *bit* is read first
          if(op.b_o_s==0){
 	    // Not the initial packet
	    return(-1);
	  }
	  if(rate!=0){
	    // previously initialized info header
	    return(-1);
	  }
	  return(unpack_info(opb));
        case 0x03: // least significant *bit* is read first
	  if(rate==0){
	    // um... we didn't get the initial header
	    return(-1);
	  }
	  return(vc.unpack(opb));
        case 0x05: // least significant *bit* is read first
	  if(rate==0 || vc.vendor==null){
	    // um... we didn;t get the initial header or comments yet
            return(-1);
	  }
	  return(unpack_books(opb));
        default:
	  // Not a valid vorbis header type
	  //return(-1);
	  break;
        }
      }
    }
    return(-1);
  }

  // pack side
  int pack_info(Buffer opb){
    // preamble
    opb.write(0x01,8);
    opb.write(_vorbis);

    // basic information about the stream
    opb.write(0x00,32);
    opb.write(channels,8);
    opb.write(rate,32);

    opb.write(bitrate_upper,32);
    opb.write(bitrate_nominal,32);
    opb.write(bitrate_lower,32);

    opb.write(ilog2(blocksizes[0]),4);
    opb.write(ilog2(blocksizes[1]),4);
    opb.write(1,1);
    return(0);
  }

  int pack_books(Buffer opb){
    opb.write(0x05,8);
    opb.write(_vorbis);

    // books
    opb.write(books-1,8);
    for(int i=0;i<books;i++){
      if(book_param[i].pack(opb)!=0){
	//goto err_out;
	return(-1);
      }
    }

    // times
    opb.write(times-1,6);
    for(int i=0;i<times;i++){
      opb.write(time_type[i],16);
      FuncTime.time_P[time_type[i]].pack(this.time_param[i],opb);
    }

    // floors
    opb.write(floors-1,6);
    for(int i=0;i<floors;i++){
      opb.write(floor_type[i],16);
      FuncFloor.floor_P[floor_type[i]].pack(floor_param[i],opb);
    }

    // residues
    opb.write(residues-1,6);
    for(int i=0;i<residues;i++){
      opb.write(residue_type[i],16);
      FuncResidue.residue_P[residue_type[i]].pack(residue_param[i],opb);
    }
    
    // maps
    opb.write(maps-1,6);
    for(int i=0;i<maps;i++){
      opb.write(map_type[i],16);
      FuncMapping.mapping_P[map_type[i]].pack(this,map_param[i],opb);
    }

    // modes
    opb.write(modes-1,6);
    for(int i=0;i<modes;i++){
      opb.write(mode_param[i].blockflag,1);
      opb.write(mode_param[i].windowtype,16);
      opb.write(mode_param[i].transformtype,16);
      opb.write(mode_param[i].mapping,8);
    }
    opb.write(1,1);
    return(0);
  //err_out:
    //return(-1);
  } 

//  static void v_writestring(Buffer o, byte[] s){
//    int i=0;
//    while(s[i]!=0){
//      o.write(s[i++],8);
//    }
//  }

//  static void v_readstring(Buffer o, byte[] buf, int bytes){
//    int i=0
//    while(bytes--!=0){
//      buf[i++]=o.read(8);
//    }
//  }

//  private Buffer opb_blocksize=new Buffer();
  public int blocksize(Packet op){
    //codec_setup_info     *ci=vi->codec_setup;
    Buffer opb=new Buffer();
//    synchronized(opb_blocksize){
    int mode;
 
    opb.readinit(op.packet_base, op.packet, op.bytes);

  /* Check the packet type */
    if(opb.read(1)!=0){
      /* Oops.  This is not an audio data packet */
      return(OV_ENOTAUDIO);
    }
    {  
      int modebits=0;
      int v=modes;
      while(v>1){
        modebits++;
        v>>>=1;
      }

    /* read our mode and pre/post windowsize */
      mode=opb.read(modebits);
    }
    if(mode==-1)return(OV_EBADPACKET);
    return(blocksizes[mode_param[mode].blockflag]);
//    }
  }

  private static int ilog2(int v){
    int ret=0;
    while(v>1){
      ret++;
      v>>>=1;
    }
    return(ret);
  }

  public String toString(){
    return "version:"+new Integer(version)+
           ", channels:"+new Integer(channels)+
           ", rate:"+new Integer(rate)+
           ", bitrate:"+new Integer(bitrate_upper)+","+
                        new Integer(bitrate_nominal)+","+
                	new Integer(bitrate_lower);
  }
}
