/***************************************************************************
    Copyright          : (C) 2002 by Neoworks Limited. All rights reserved.
    Copyright          : Portions copyright the original author.
    URL                : http://www.neoworks.com
 ***************************************************************************/
/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

package com.neoworks.mpeg;

import java.io.IOException;

import org.apache.log4j.Category;

/**
 * This class represents an MPEG frame. MPEG frames contain a header, compressed audio
 * data and an optional checksum.
 *
 * @author Nigel Atkinson (<a href="mailto:nigel@neoworks.com">nigel@neoworks.com</a>)
 * @author     micah
 */
public final class MPEGFrame {
	private static final Category log = Category.getInstance(MPEGFrame.class.getName());
	private static final boolean logDebugEnabled = log.isDebugEnabled();
	private static final boolean logInfoEnabled = log.isInfoEnabled();

	/**
	 * Sample rate lookup table
	 */
	public final static int[] [] frequencies = {{22050, 24000, 16000, 1},
						{44100, 48000, 32000, 1}};

	/** Constant for MPEG-1 version */
	public final static int MPEG1 = 1;

	/** Constant for MPEG-2 version */
	public final static int MPEG2 = 2;

	/** Frame mode constant */
	public final static int STEREO = 0;

	/** Frame mode constant */
	public final static int JOINT_STEREO = 1;

	/** Frame mode constant */
	public final static int DUAL_CHANNEL = 2;

	/** Frame mode constant */
	public final static int SINGLE_CHANNEL = 3;

	/** Samole rate constant */
	public final static int FOURTYFOUR_POINT_ONE = 0;

	/** Sample rate constant */
	public final static int FOURTYEIGHT = 1;

	/** Sample rate constant */
	public final static int THIRTYTWO = 2;

	/**
	 * Bitrate lookup table
	 */
	private final static int bitrates[] [] [] = {
	{ 
	 
	// Version 2
	 
	 {0
		 // Layer 1
		, 32000, 48000, 56000, 64000, 80000, 96000,
		112000, 128000, 144000, 160000, 176000, 192000, 224000, 256000, 0},
		{0

		 // Layer 2
		, 8000, 16000, 24000, 32000, 40000, 48000,
		56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 0},
		{0

		 // Layer 3
		, 8000, 16000, 24000, 32000, 40000, 48000,
		56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 0}},
		{
			
	// Version 1
			
	{0

		 // Layer 1
		, 32000, 64000, 96000, 128000, 160000, 192000,
		224000, 256000, 288000, 320000, 352000, 384000, 416000, 448000, 0},
		{0

		 // Layer 2
		, 32000, 48000, 56000, 64000, 80000, 96000,
		112000, 128000, 160000, 192000, 224000, 256000, 320000, 384000, 0},
		{0

		 // Layer 3
		, 32000, 40000, 48000, 56000, 64000, 80000,
		96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 0}}
	};

	/**
	 * Bitrate description lookup table
	 */
	private final static String bitrate_str[] [] [] = {
		// Version 2

		// Layer 1
		{{"free format", "32 kbit/s", "48 kbit/s", "56 kbit/s", "64 kbit/s",
		"80 kbit/s", "96 kbit/s", "112 kbit/s", "128 kbit/s", "144 kbit/s",
		"160 kbit/s", "176 kbit/s", "192 kbit/s", "224 kbit/s", "256 kbit/s",
		"forbidden"},
		// Layer 2
		{"free format", "8 kbit/s", "16 kbit/s", "24 kbit/s", "32 kbit/s",
		"40 kbit/s", "48 kbit/s", "56 kbit/s", "64 kbit/s", "80 kbit/s",
		"96 kbit/s", "112 kbit/s", "128 kbit/s", "144 kbit/s", "160 kbit/s",
		"forbidden"},
		// Layer 3
		{"free format", "8 kbit/s", "16 kbit/s", "24 kbit/s", "32 kbit/s",
		"40 kbit/s", "48 kbit/s", "56 kbit/s", "64 kbit/s", "80 kbit/s",
		"96 kbit/s", "112 kbit/s", "128 kbit/s", "144 kbit/s", "160 kbit/s",
		"forbidden"}},

		// Version 1

		// Layer 1
		{{"free format", "32 kbit/s", "64 kbit/s", "96 kbit/s", "128 kbit/s",
		"160 kbit/s", "192 kbit/s", "224 kbit/s", "256 kbit/s", "288 kbit/s",
		"320 kbit/s", "352 kbit/s", "384 kbit/s", "416 kbit/s", "448 kbit/s",
		"forbidden"},

		// Layer 2
		{"free format", "32 kbit/s", "48 kbit/s", "56 kbit/s", "64 kbit/s",
		"80 kbit/s", "96 kbit/s", "112 kbit/s", "128 kbit/s", "160 kbit/s",
		"192 kbit/s", "224 kbit/s", "256 kbit/s", "320 kbit/s", "384 kbit/s",
		"forbidden"},

		// Layer 3
		{"free format", "32 kbit/s", "40 kbit/s", "48 kbit/s", "56 kbit/s",
		"64 kbit/s", "80 kbit/s", "96 kbit/s", "112 kbit/s", "128 kbit/s",
		"160 kbit/s", "192 kbit/s", "224 kbit/s", "256 kbit/s", "320 kbit/s",
		"forbidden"}}
	};

	/**
	 * Layer description lookup table
	 */
	private static final String layerStr[] = {"Layer I","Layer II", "Layer III"};

	// Total size in bytes of the frame
	private int framesize = 0;
	// Number of bytes of actual encoded audio
	private int nSlots = 0;
	private int h_layer = -1;
	private int h_protection_bit = -1;
	private int h_bitrate_index = -1;
	private int h_padding_bit = -1;
	private int h_mode_extension = -1;
	private int h_version = -1;
	private int h_mode = -1;
	private int h_sample_frequency = -1;
	//private int h_samples;
	private int h_number_of_subbands = -1;
	private int h_intensity_stereo_bound = -1;
	private byte syncmode = MPEGStream.INITIAL_SYNC;
	private byte[] frameData = null;
	private short crcChecksum = 0;

	/**
	 * Public constructor
	 *
	 * @param headerstring The four header bytes
	 * @exception CorruptMPEGHeaderException If the header can not be parsed
	 */
	public MPEGFrame(int headerstring) throws CorruptMPEGHeaderException
	{
		if (parse_header(headerstring))
		{
			framesize = calculateFrameSize();
			nSlots = calculateNSlots(framesize);
		} else {
			throw new CorruptMPEGHeaderException(headerstring);
		}
	}

	/**
	 * Get the MPEG version
	 *
	 * @return 0 = version 2 (or 2.5), 1 = version 1
	 */
	public int getMPEGVersion() {
		return h_version;
	}

	/**
	 * Get the MPEG layer
	 *
	 * @return An int between 1 and 3 indicating the layer
	 */
	public int getMPEGLayer() {
		return h_layer;
	}

	/**
	 * Get the smaple frequency of the frame data
	 *
	 * @return The sampling frequency of the frmae data
	 */
	public int getFrequency() {
		return frequencies[h_version] [h_sample_frequency];
	}

	//public int getSamplesPerFrame()
	//{
	//	return h_samples;
	//}

	/**
	 * Is the frame padded?
	 *
	 * @return Whether the frame is padded
	 */
	public boolean hasPadding() {
		return (h_padding_bit != 0);
	}

	/**
	 * Get the number of audio data slots in this frame
	 *
	 * @return The number of data slots in the frame
	 */
	public int getSlots() {
		return nSlots;
	}

	/**
	 * Get the stereo mode extension if any
	 *
	 * @return Stereo mode extension
	 */
	public int getModeExtension() {
		return h_mode_extension;
	}

	/**
	 * Get a description of the MPEG layer
	 *
	 * @return A String description of the MPEG layer
	 */
	public String getLayerString() {
		return layerStr[h_layer - 1];
	}

	/**
	 * Get a description of the frame bitrate
	 *
	 * @return A String description of the bitrate
	 */
	public String getBitrateString() {
		return bitrate_str[h_version] [h_layer - 1] [h_bitrate_index];
	}

	/**
	 * Get a description of the frame sample rate
	 *
	 * @return A String description of the frame sample rate
	 */
	public String getSampleFrequencyString() {
		switch (h_sample_frequency) {
			case THIRTYTWO:
				return "32 kHz";
			case FOURTYFOUR_POINT_ONE:
				return "44.1 kHz";
			case FOURTYEIGHT:
				return "48 kHz";
		}
		return "not set";
	}

	/**
	 * Get a description of the mode
	 *
	 * @return A String description of the mode
	 */
	public String getModeString() {
		switch (h_mode) {
			case STEREO:
				return "Stereo";
			case JOINT_STEREO:
				return "Joint stereo";
			case DUAL_CHANNEL:
				return "Dual channel";
			case SINGLE_CHANNEL:
				return "Single channel";
		}
		return "not set";
	}

	/**
	 * Get the number of sub bands
	 *
	 * @return the number of sub bands
	 */
	public int getNumberOfSubbands() {
		return h_number_of_subbands;
	}

	/**
	 * Get the intensity stereo bound
	 *
	 * @return int
	 */
	public int getIntensityStereoBound() {
		return h_intensity_stereo_bound;
	}

	/**
	 * Get the CRC protection indicator bit.
	 *
	 * @return 0 - Protected by CRC | 1 - Not protected
	 */
	public boolean isCRCProtected()
	{
		return (h_protection_bit == 0);
	}

	/**
	 * Get the frame bitrate in bytes/second
	 *
	 * @return The bitrate of the frame
	 */
	public int getBitrate()
	{
		return bitrates[h_version] [h_layer - 1] [h_bitrate_index];
	}

	/**
	 * Get the frame size in bytes (including the header bytes)
	 *
	 * @return the size in bytes of the frame
	 */
	public int getFrameSize()
	{
		return framesize;
	}

	/**
	 * Set the frame size. This is required because VBR header frames cannot be sized in the usual fashion.
	 * Package private because it should only be set in the frame is a VBR header
	 *
	 * @param size The frame size
	 */
	void setFrameSize(int size)
	{
		framesize = size;
	}

	/**
	 * Set the data for this frame. This is the entire frame including header, crc, etc.
	 *
	 * @param data The frame data
	 */
	void setFrameData(byte [] data)
	{
		frameData = data;
	}

	/**
	 * Get the entire frame as a byte array
	 *
	 * @return The frame data
	 */
	public byte[] getFrameData()
	{
		return frameData;
	}

	/**
	 * Set the CRC checksum for this frame
	 *
	 * @param sum The checksum for this frame
	 */
	void setCRCChecksum(short sum)
	{
		crcChecksum = sum;
	}

	/**
	 * Get the CRC checksum for this frame
	 *
	 * @return The CRC checksum as a short
	 */
	public short getCRCChecksum()
	{
		return crcChecksum;
	}

	/**
	 * Package private because MPEGStream uses this
	 */
	int sample_frequency_index() {
		return h_sample_frequency;
	}

	/**
	 * Package private because MPEGStream uses this
	 */
	int mode() {
		return h_mode;
	}

	/**
	 * Get the bitrate index
	 *
	 * @return the bitrate index
	 */
	private int bitrate_index() {
		return h_bitrate_index;
	}

	/**
	 * Read the frame header. This assumes that headerstring is valid
	 *
	 * @param headerstring The header bytes (as an int)
	 * @return Success
	 */
	private boolean parse_header(int headerstring)
	{
		int channel_bitrate;

		if (syncmode == MPEGStream.INITIAL_SYNC) {
			h_version = ((headerstring >>> 19) & 1);
			if ((h_sample_frequency = ((headerstring >>> 10) & 3)) == 3) {
				return false;
			}
		}
		h_layer = 4 - (headerstring >>> 17) & 3;
		h_protection_bit = (headerstring >>> 16) & 1;
		h_bitrate_index = (headerstring >>> 12) & 0xF;
		h_padding_bit = (headerstring >>> 9) & 1;
		h_mode = ((headerstring >>> 6) & 3);
		h_mode_extension = (headerstring >>> 4) & 3;
		if (h_mode == JOINT_STEREO) {
			h_intensity_stereo_bound = (h_mode_extension << 2) + 4;
		} else {
			h_intensity_stereo_bound = 0;
		}
		if (h_layer == 1) {
			h_number_of_subbands = 32;
		} else {
			channel_bitrate = h_bitrate_index;
			// calculate bitrate per channel:
			if (h_mode != SINGLE_CHANNEL) {
				if (channel_bitrate == 4) {
					channel_bitrate = 1;
				} else {
					channel_bitrate -= 4;
				}
			}
			if ((channel_bitrate == 1) || (channel_bitrate == 2)) {
				if (h_sample_frequency == THIRTYTWO) {
					h_number_of_subbands = 12;
				} else {
					h_number_of_subbands = 8;
				}
			} else if ((h_sample_frequency == FOURTYEIGHT) || ((channel_bitrate >= 3) && (channel_bitrate <= 5))) {
				h_number_of_subbands = 27;
			} else {
				h_number_of_subbands = 30;
			}
		}
		if (h_intensity_stereo_bound > h_number_of_subbands) {
			h_intensity_stereo_bound = h_number_of_subbands;
		}
		return true;
	}

	/**
	 * Get a human readable representation of this header
	 *
	 * @return A String description of this header
	 */
	public String toString()
	{
		StringBuffer retVal = new StringBuffer();

		retVal.append("\nMPEG version ");
		retVal.append(getMPEGVersion());
		retVal.append(", ");
		retVal.append(getLayerString());
		retVal.append("\n");
		retVal.append("Frame size: ");
		retVal.append(framesize);
		retVal.append("\n");
		retVal.append("Bitrate: ");
		retVal.append(getBitrateString());
		retVal.append("\n");
		retVal.append("Sample rate: ");
		retVal.append(getSampleFrequencyString());
		retVal.append("\n");
		retVal.append("Mode: ");
		retVal.append(getModeString());
		retVal.append("\n");
		if (hasPadding())
		{
			retVal.append("Padded\n");
		} else {
			retVal.append("Not Padded\n");
		}
		if (isCRCProtected())
		{
			retVal.append("CRC protected\n");
		} else {
			retVal.append("Not CRC protected\n");
		}

		return retVal.toString();
	}

	/**
	 * Calculate the frame length in bytes. Only deals with MPEG I apparently
	 * Layer I: bytelength = (12 * bitrate/samplerate + padding) *4
	 * Layer II &amp; III: bylelength = 144 * bitrate/samplerate + padding
	 *
	 * @return The frame length in bytes including the header bytes
	 */
	private final int calculateFrameSize() {
		int lengthMultiplier = 1;
		int postMultiplier = 1;
		int retVal = 0;

		if (h_layer == 1)
		{
			lengthMultiplier = 12;
			postMultiplier = 4;
		} else {
			lengthMultiplier = 144;
		}
		
		retVal = (lengthMultiplier * bitrates[h_version] [h_layer - 1] [h_bitrate_index]) / frequencies[h_version] [h_sample_frequency];
		//padding
		if(h_padding_bit != 0)
		{
			retVal++;
		}

		retVal = retVal * postMultiplier;

		// Sanity check
		if (retVal < 0)
		{
			retVal = 0;
		}

		return retVal;
	}

	/**
	 * Calculate the number of audio data slots
	 *
	 * @return The number of audio data slots
	 */
	private int calculateNSlots(int frameSize)
	{
		int retVal = frameSize;
		if(h_version == MPEG1)
		{
			 // E.B Fix
			 retVal = retVal - ((h_mode == SINGLE_CHANNEL) ? 17 : 32) - ((h_protection_bit!=0) ? 0 : 2) - 4;
			 //nSlots = framesize - ((h_mode == SINGLE_CHANNEL) ? 17 : 32) - 4;
			 // End.

		} else {
			// TODO: Add support for this at some point
		}
		return retVal;
	}
}
