/*
 *	Info.java
 *
 *	This file is part of Tritonus: http://www.tritonus.org/
 */

/*
 *  Copyright (c) 2000 - 2005 by Matthias Pfisterer
 *
 *   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.
 */

/*
|<---            this code is formatted to fit into 80 columns             --->|
*/

package org.tritonus.lowlevel.pvorbis;

import org.tritonus.lowlevel.pogg.Ogg;
import org.tritonus.lowlevel.pogg.Packet;
import org.tritonus.lowlevel.pogg.Buffer;
import org.tritonus.share.TDebug;


/** Wrapper for vorbis_info.
 */
public class Info
implements VorbisConstants
{
        static
        {
                Ogg.loadNativeLibrary();
                if (TDebug.TraceVorbisNative)
                {
                        setTrace(true);
                }
        }



	/**
	 *	Holds the pointer to vorbis_info
	 *	for the native code.
	 *	This must be long to be 64bit-clean.
	 */
	private long	m_lNativeHandle;


	private int m_nVersion;
	private int m_nChannels;
	private int m_nRate;
	private int m_nBitrateUpper;
	private int m_nBitrateNominal;
	private int m_nBitrateLower;



	public Info()
	{
		if (TDebug.TraceVorbisNative) { TDebug.out("Info.<init>(): begin"); }
		int	nReturn = malloc();
		if (nReturn < 0)
		{
			throw new RuntimeException("malloc of vorbis_info failed");
		}
		if (TDebug.TraceVorbisNative) { TDebug.out("Info.<init>(): end"); }
	}



	public void finalize()
	{
		// TODO: call free()
		// call super.finalize() first or last?
		// and introduce a flag if free() has already been called?
	}



	private native int malloc();
	public native void free();


	/** Calls vorbis_info_init().
	 */
	public void init()
	{
		m_nVersion = 0;
		m_nChannels = 0;
		m_nRate = 0;
		m_nBitrateUpper = 0;
		m_nBitrateNominal = 0;
		m_nBitrateLower = 0;

		// TODO: allocate codec_setup_info
		init_native();
	}


	/** Calls vorbis_info_init().
	 */
	public native void init_native();


	/** Calls vorbis_info_clear().
	 */
	public void clear()
	{
		// TODO: lots of stuff in codec_setup_info

		m_nVersion = 0;
		m_nChannels = 0;
		m_nRate = 0;
		m_nBitrateUpper = 0;
		m_nBitrateNominal = 0;
		m_nBitrateLower = 0;

		clear_native();
	}


	/** Calls vorbis_info_clear().
	 */
	public native void clear_native();


	public int getVersion()
	{
		return getVersion_native();
	}


	private native int getVersion_native();


	public void setValues(int nVersion,
						  int nChannels,
						  int nRate,
						  int nBitrateUpper,
						  int nBitrateNominal,
						  int nBitrateLower,
						  int nBlocksize0,
						  int nBlocksize1)
	{
		m_nVersion = nVersion;
		m_nChannels = nChannels;
		m_nRate = nRate;
		m_nBitrateUpper = nBitrateUpper;
		m_nBitrateNominal = nBitrateNominal;
		m_nBitrateLower = nBitrateLower;

		setValues_native(nVersion,
						 nChannels,
						 nRate,
						 nBitrateUpper,
						 nBitrateNominal,
						 nBitrateLower,
						 nBlocksize0,
						 nBlocksize1);
	}


	private native void setValues_native(int nVersion,
										 int nChannels,
										 int nRate,
										 int nBitrateUpper,
										 int nBitrateNominal,
										 int nBitrateLower,
										 int nBlocksize0,
										 int nBlocksize1);


	public int getBlocksize0()
	{
		return getBlocksize_native(0);
	}


	public int getBlocksize1()
	{
		return getBlocksize_native(1);
	}


	/**
	   @param nIndex which blocksize is desired. Has to be either 0 or
	   1.
	 */
	private native int getBlocksize_native(int nIndex);


	/** Accesses channels.
	 */
	public int getChannels()
	{
		// return m_nChannels;
		return getChannels_native();
	}


	/** Accesses channels.
	 */
	private native int getChannels_native();



	/** Accesses rate.
	 */
	public int getRate()
	{
		//return m_nRate;
		return getRate_native();
	}


	/** Accesses rate.
	 */
	private native int getRate_native();


	public int getBitrateUpper()
	{
		//return m_nBitrateUpper;
		return getBitrateUpper_native();
	}


	private native int getBitrateUpper_native();


	public int getBitrateNominal()
	{
		//return m_nBitrateNominal;
		return getBitrateNominal_native();
	}


	private native int getBitrateNominal_native();


	public int getBitrateLower()
	{
		//return m_nBitrateLower;
		return getBitrateLower_native();
	}



	private native int getBitrateLower_native();


	/** Calls vorbis_encode_init().
	 */
	public int encodeInit(
		int nChannels,
		int nRate,
		int nMaxBitrate,
		int nNominalBitrate,
		int nMinBitrate)
	{
		return encodeInit_native(
			nChannels,
			nRate,
			nMaxBitrate,
			nNominalBitrate,
			nMinBitrate);
	}


	/** Calls vorbis_encode_init().
	 */
	private native int encodeInit_native(
		int nChannels,
		int nRate,
		int nMaxBitrate,
		int nNominalBitrate,
		int nMinBitrate);



	/** Calls vorbis_encode_init_vbr().
	 */
	public int encodeInitVBR(
		int nChannels,
		int nRate,
		float fQuality)
	{
		return encodeInitVBR_native(
			nChannels,
			nRate,
			fQuality);
	}


	/** Calls vorbis_encode_init_vbr().
	 */
	private native int encodeInitVBR_native(
		int nChannels,
		int nRate,
		float fQuality);


	/** Calls vorbis_synthesis_headerin().
	 */
	public int headerIn(Comment comment, Packet packet)
	{
		if(packet == null)
		{
			return OV_EBADHEADER;
		}
		Buffer buffer = new Buffer();
		byte[] abData = packet.getData();
		//Buffer.outBuffer(abData);
		buffer.readInit(abData, abData.length);

		/* Which of the three types of header is this? */
		/* Also verify header-ness, vorbis */
		int packtype = buffer.read(8);
		//TDebug.out("packtype: " + packtype);
		String s = buffer.readString(6);
		if(! "vorbis".equals(s))
		{
			/* not a vorbis header */
			buffer.free();
			return OV_ENOTVORBIS;
		}
		int r;
		switch (packtype)
		{
		case 0x01: /* least significant *bit* is read first */
			if(! packet.isBos())
			{
				/* Not the initial packet */
				buffer.free();
				return OV_EBADHEADER;
			}
			if (getRate() != 0)
			{
				/* previously initialized info header */
				buffer.free();
				return OV_EBADHEADER;
			}
			r = unpack(buffer);
			buffer.free();
			return r;

		case 0x03: /* least significant *bit* is read first */
			if (getRate() == 0)
			{
				/* um... we didn't get the initial header */
				buffer.free();
				return OV_EBADHEADER;
			}
			r = comment.unpack(buffer);
			buffer.free();
			return r;

		case 0x05: /* least significant *bit* is read first */
			if (getRate() == 0 || comment.getVendor() == null)
			{
				/* um... we didn;t get the initial header or comments yet */
				buffer.free();
				return OV_EBADHEADER;
			}
			r = headerIn_native(buffer, packtype, packet);
			buffer.free();
			return r;
			//return(_vorbis_unpack_books(vi,&buffer));

		default:
			/* Not a valid vorbis header type */
			buffer.free();
			return OV_EBADHEADER;
		}
	}


	/** Calls vorbis_synthesis_headerin().
	 */
	private native int headerIn_native(Buffer buffer, int nPacketType,
									  Packet packet);


	public int pack(Buffer buffer)
	{
		/* preamble */  
		buffer.write(0x01, 8);
		buffer.write("vorbis");

		buffer.write(0x00, 32); // version
		buffer.write(getChannels(), 8);
		buffer.write(getRate(), 32);

		buffer.write(getBitrateUpper(), 32);
		buffer.write(getBitrateNominal(), 32);
		buffer.write(getBitrateLower(), 32);

		buffer.write(ilog2(getBlocksize0()), 4);
		buffer.write(ilog2(getBlocksize1()), 4);

		/* end of packet */
		buffer.write(1, 1);

		return 0;
	}


	public int unpack(Buffer buffer)
	{
		int nVersion = buffer.read(32);
		if (nVersion != 0)
		{
			return OV_EVERSION;
		}
		int nChannels = buffer.read(8);
		int nRate = buffer.read(32);
		int nBitrateUpper = buffer.read(32);
		int nBitrateNominal = buffer.read(32);
		int nBitrateLower = buffer.read(32);
		int nBlocksize0 = 1 << buffer.read(4);
		int nBlocksize1 = 1 << buffer.read(4);

		if (nChannels < 1 || nRate < 1 ||
			nBlocksize0 < 8 || nBlocksize1 < nBlocksize0)
		{
			clear();
			return OV_EBADHEADER;
		}

		/* EOP check */
		if (buffer.read(1) != 1)
		{
			clear();
			return OV_EBADHEADER;
		}

		setValues(nVersion,
				  nChannels,
				  nRate,
				  nBitrateUpper,
				  nBitrateNominal,
				  nBitrateLower,
				  nBlocksize0,
				  nBlocksize1);
		// everything ok.
		return 0;
	}


	private static native void setTrace(boolean bTrace);


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





/*** Info.java ***/
