/**
 * Copyright (C) 2002 Neoworks Limited. All rights reserved.
 * Portions copyright (C) 2001 Jonathan Hilliker
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * Revsisions: 
 *  $Log: XingVBRHeader.java,v $
 *  Revision 1.4  2002/06/24 17:21:27  nigel
 *  Removed inaccurate javadoc
 *
 *  Revision 1.3  2002/06/19 14:48:36  nigel
 *  Added copyright and license statement Code clean up
 *
 *  Revision 1.2  2002/05/27 12:32:33  nigel
 *  Updated javadoc
 *
 *  Revision 1.1  2002/05/23 15:46:42  nigel
 *  Moved from javazoom package
 *
 *  Revision 1.3  2002/05/23 14:14:41  nigel
 *  Improved javadoc
 *
 *  Revision 1.2  2002/05/22 19:56:20  nigel
 *  Added debug, removed commented out code
 *
 *  Revision 1.1  2002/05/22 19:14:07  nigel
 *  Added MP3 frame support
 *
 *  Revision 1.1  2002/04/24 13:48:05  nick
 *  Checked in to stop Nigel monaing.
 *
 *  Revision 1.1  2002/01/21 05:06:56  helliker
 *  Initial version.
 *
 */

package com.neoworks.mpeg;

import java.io.IOException;
import java.io.ByteArrayInputStream;
import helliker.id3.*;
import org.apache.log4j.Category;

/**
 *  Reads information from a Xing variable bitrate info header.  Thanks to the Scilla project
 *  (<a href="http://www.xs4all.nl/~rwvtveer/scilla/">http://www.xs4all.nl/~rwvtveer/scilla/</a>) for code ideas.
 *
 * @author Nigel Atkinson (<a href="mailto:nigel@neoworks.com">nigel@neoworks.com</a>)
 * @author  Jonathan Hilliker
 * @version $Id: XingVBRHeader.java,v 1.4 2002/06/24 17:21:27 nigel Exp $
 */
public class XingVBRHeader  {
	private static final Category log = Category.getInstance(XingVBRHeader.class.getName());
	private static final boolean logDebugEnabled = log.isDebugEnabled();
	private static final boolean logInfoEnabled = log.isInfoEnabled();

	private final String HEAD_START = "Xing";
	private final int HEAD_SIZE = 4;
	private final int FLAGS_SIZE = 4;
	private final int TOC_SIZE = 100;
	private final double[] TIMEPERFRAME_TABLE = { -1, 1152, 1152, 384 };

	private int numFrames;
	private int headerOffset;
	private int numBytes;
	private int vbrScale;
	private byte[] toc;
	private int avgBitrate;
	private int playingTime;
	private int length;
	private boolean exists;

	/**
	 * Looks for a Xing VBR header in the file.  If it is found then that means
	 * this is a variable bit rate file and all the data in the header will be
	 * parsed.
	 *
	 * @param data The MPEG frame data to extract the header from
	 * @param offset the location of the first mpeg frame
	 * @param layer the layer value read by the MPEGAudioFrameHeader
	 * @param mpegVersion the version value read by the MPEGAudioFrameHeader
	 * @param sampleRate the sample rate read by the MPEGAudioFrameHeader
	 * @param channelMode the channel mode read by the MPEGAudioFrameHeader
	 */
	public XingVBRHeader( byte [] data, long offset, int layer, 
			  int mpegVersion, int sampleRate, int channelMode )
	{

		exists = false;
		numFrames = -1;
		numBytes = -1;
		vbrScale = -1;
		avgBitrate = -1;
		playingTime = -1;
		length = -1;

		exists = checkHeader( data, offset, channelMode, mpegVersion );

		if( exists ) {
			readHeader( data );
			calcValues( layer, mpegVersion, sampleRate );
		}
	}

	/**
	 * Checks to see if a xing header is in this file
	 *
	 * @param data The MPEG frame data
	 * @param offset the location of the first mpeg frame
	 * @param mpegVersion the version value read by the MPEGAudioFrameHeader
	 * @param channelMode the channel mode read by the MPEGAudioFrameHeader
	 * @return true if a xing header exists
	 * @exception IOException if an error occurs
	 */
	private boolean checkHeader(  byte [] data, long offset, 
				  int channelMode, int mpegVersion ) 
	{
		boolean b = false;
		byte head[] = new byte[HEAD_SIZE];
		int headerPos = (int)offset;

		// MPEG header
		headerPos += 4;

		if( mpegVersion == MPEGFrame.MPEG1 ) {
			if( channelMode == MPEGFrame.SINGLE_CHANNEL ) {
				headerOffset = 17;
			}
			else {
				headerOffset = 32 ;
			}
		}
		else {
			if( channelMode == MPEGFrame.SINGLE_CHANNEL ) {
				headerOffset = 19 ;
			}
			else {
				headerOffset = 17 ;
			}
		}
		headerPos += headerOffset;

		b = HEAD_START.equals( new String(data,headerPos,HEAD_SIZE) );

		return b;
	}

	/**
	 * Parses the header data.
	 *
	 * @param header The byte array to read the header from
	 * @exception IOException if an error occurs
	 * @exception CorruptHeaderException if an error occurs
	 */
	private void readHeader(byte [] header)
	{
		length = HEAD_SIZE;
		byte flags[] = new byte[FLAGS_SIZE];
		ByteArrayInputStream bais = new ByteArrayInputStream(header);

		bais.read(flags,0,FLAGS_SIZE);
		length += flags.length;

		if( BinaryParser.bitSet( flags[3], 0 ) ) {
			if (logDebugEnabled) log.debug("Got frame count");
			byte[] numFramesArray = new byte[4];
			bais.read(numFramesArray,0,4);
			numFrames = BinaryParser.convertToInt(numFramesArray);
			length += 4;
		}
	
	
		if( BinaryParser.bitSet( flags[3], 1 ) ) {
			if (logDebugEnabled) log.debug("Got byte count");
			byte[] numBytesArray = new byte[4];
			bais.read(numBytesArray,0,4);
			numBytes = BinaryParser.convertToInt(numBytesArray);
			length += 4;
		}
		if( BinaryParser.bitSet( flags[3], 2 ) ) {
			toc = new byte[TOC_SIZE];
			
			bais.read(toc,0,TOC_SIZE);
			
			length += TOC_SIZE;
		}
	
		
		if( BinaryParser.bitSet( flags[3], 3 ) ) {
			if (logDebugEnabled) log.debug("Got VBR scale");
			byte [] VBRScaleArray = new byte[4];
			bais.read(VBRScaleArray,0,4);
			vbrScale = BinaryParser.convertToInt(VBRScaleArray);
			length += 4;
		}
	}

	/**
	 * Calculates the playing time and the average bitrate.
	 *
	 * @param layer the layer value read by the MPEGAudioFrameHeader
	 * @param mpegVersion the version value read by the MPEGAudioFrameHeader
	 * @param sampleRate the sample rate read by the MPEGAudioFrameHeader
	 */
	private void calcValues( int layer, int mpegVersion, int sampleRate )
	{
		double tpf = TIMEPERFRAME_TABLE[layer] / sampleRate;

		if(mpegVersion == MPEGFrame.MPEG2) {
		
			tpf /= 2;
		}

		playingTime = (int)( tpf * numFrames );
		avgBitrate = (int)( (numBytes * 8) / (tpf * numFrames * 1000) );
	}

	/**
	 * Returns true if a Xing VBR header was found in the file passed to the 
	 * constructor.
	 *
	 * @return true if a Xinb VBR header was found
	 */
	public boolean headerExists()
	{
		return exists;
	}

	/**
	 * Returns the number of MPEG frames in the file passed to the constructor.
	 * If a header is not found, -1 is returned.
	 *
	 * @return the number of MPEG frames
	 */
	public int getNumFrames()
	{
		return numFrames;
	}

	/**
	 * Returns the number of data bytes in the mpeg frames.  If a header is not
	 * found, -1 is returned.
	 *
	 * @return the number of data bytes in the mpeg frames
	 */
	public int getNumBytes()
	{
		return numBytes;
	}

	/**
	 * Returns the VBR scale used to generate this VBR file.  If a header is not
	 * found, -1 is returned.
	 *
	 * @return the VBR scale used to generate this VBR file
	 */
	public int getVBRScale()
	{
		return vbrScale;
	}

	/**
	 * Returns the toc used to seek to an area of this VBR file.  If a header
	 * is not found an empty array is returned.
	 *
	 * @return the toc used to seek to an area of this VBR file
	 */
	public byte[] getTOC()
	{
		return toc;
	}

	/**
	 * Returns the average bit rate of the mpeg file if a Xing header exists
	 * and -1 otherwise.
	 *
	 * @return the average bit rate (in kbps)
	 */
	public int getAvgBitrate()
	{
		return avgBitrate;
	}

	/**
	 * Get the offset from the end of the MPEG header to the beginning of the VBR header in bytes
	 *
	 * @return The offset in bytes
	 */
	public int getHeaderOffset()
	{
		return headerOffset;
	}

	/**
	 * Returns the calculated playing time of the mpeg file if a Xing header
	 * exists and -1 otherwise.
	 *
	 * @return the playing time (in seconds) of the mpeg file
	 */
	public int getPlayingTime()
	{
		return playingTime;
	}

	/**
	 * Return a string representation of this object.  Includes all information
	 * read in and computed.
	 *
	 * @return a string representation of this object
	 */
	public String toString()
	{
		return "NumFrames:\t\t\t" + getNumFrames() + "\nNumBytes:\t\t\t" +
		getNumBytes() + " bytes\nVBRScale:\t\t\t" + getVBRScale() + 
		"\nLength:\t\t\t\t" + getLength() + " bytes";
	}

	/**
	 * Returns the length (in bytes) of this Xing VBR header.
	 *
	 * @return the length of this Xing VBR header
	 */
	public int getLength()
	{
		return length;
	}
	
}
