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

/*
 *  Copyright (c) 2000 by Florian Bomers <http://www.bomers.de>
 *  Copyright (c) 1999 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.sampled.file;

import java.io.DataInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;

import org.tritonus.share.sampled.file.TAudioFileFormat;
import org.tritonus.share.sampled.file.TAudioFileReader;
import org.tritonus.share.TDebug;


/** Class for reading AIFF and AIFF-C files.
 *
 * @author Florian Bomers
 * @author Matthias Pfisterer
 */
public class AiffAudioFileReader extends TAudioFileReader
{
	private static final int	READ_LIMIT = 1000;



	public AiffAudioFileReader()
	{
		super(READ_LIMIT);
	}



	private void skipChunk(DataInputStream dataInputStream, int chunkLength, int chunkRead)
	throws IOException {
		chunkLength-=chunkRead;
		if (chunkLength>0) {
			dataInputStream.skip(chunkLength + (chunkLength % 2));
		}
	}

	private AudioFormat readCommChunk(DataInputStream dataInputStream, int chunkLength)
	throws IOException, UnsupportedAudioFileException {

		int		nNumChannels = dataInputStream.readShort();
		if (nNumChannels <= 0) {
			throw new UnsupportedAudioFileException(
			    "not an AIFF file: number of channels must be positive");
		}
		if (TDebug.TraceAudioFileReader) {
			TDebug.out("Found "+nNumChannels+" channels.");
		}
		// ignored: frame count
		dataInputStream.readInt();
		int nSampleSize = dataInputStream.readShort();
		float fSampleRate = (float) readIeeeExtended(dataInputStream);
		if (fSampleRate <= 0.0) {
			throw new UnsupportedAudioFileException(
			    "not an AIFF file: sample rate must be positive");
		}
		if (TDebug.TraceAudioFileReader) {
			TDebug.out("Found framerate "+fSampleRate);
		}
		AudioFormat.Encoding encoding = AudioFormat.Encoding.PCM_SIGNED;
		int nRead=18;
		if (chunkLength>nRead) {
			int nEncoding=dataInputStream.readInt();
			nRead+=4;
			if (nEncoding==AiffTool.AIFF_COMM_PCM) {
				// PCM - nothing to do
			}
			else if (nEncoding==AiffTool.AIFF_COMM_ULAW) {
				// ULAW
				encoding=AudioFormat.Encoding.ULAW;
				nSampleSize=8;
			}
			else if (nEncoding==AiffTool.AIFF_COMM_IMA_ADPCM) {
				encoding = new AudioFormat.Encoding("IMA_ADPCM");
				nSampleSize=4;
			}
			else {
				throw new UnsupportedAudioFileException(
					"Encoding 0x"+Integer.toHexString(nEncoding)
					+" of AIFF file not supported");
			}
		}
		/* In case of IMA ADPCM, frame size is 0.5 bytes (since it is
		   always mono). A value of 1 as frame size would be wrong.
		   Handling of frame size 0 in defined nowhere. So the best
		   solution is to set the frame size to unspecified (-1).
		*/
		int nFrameSize = (nSampleSize == 4) ?
			AudioSystem.NOT_SPECIFIED :
			calculateFrameSize(nSampleSize, nNumChannels);
		if (TDebug.TraceAudioFileReader) { TDebug.out("calculated frame size: " + nFrameSize); }
		skipChunk(dataInputStream, chunkLength, nRead);
		AudioFormat format = new AudioFormat(encoding,
		                                     fSampleRate,
		                                     nSampleSize,
		                                     nNumChannels,
		                                     nFrameSize,
		                                     fSampleRate,
		                                     true);
		return format;
	}

	private void readVerChunk(DataInputStream dataInputStream, int chunkLength)
	throws IOException, UnsupportedAudioFileException {
		if (chunkLength<4) {
			throw new UnsupportedAudioFileException(
			    "Corrput AIFF file: FVER chunk too small.");
		}
		int nVer=dataInputStream.readInt();
		if (nVer!=AiffTool.AIFF_FVER_TIME_STAMP) {
			throw new UnsupportedAudioFileException(
			    "Unsupported AIFF file: version not known.");
		}
		skipChunk(dataInputStream, chunkLength, 4);
	}



	protected AudioFileFormat getAudioFileFormat(InputStream inputStream, long lFileSizeInBytes)
	throws UnsupportedAudioFileException, IOException
	{
		if (TDebug.TraceAudioFileReader) {TDebug.out("AiffAudioFileReader.getAudioFileFormat(InputStream, long): begin"); }
		DataInputStream	dataInputStream = new DataInputStream(inputStream);
		int	nMagic = dataInputStream.readInt();
		if (nMagic != AiffTool.AIFF_FORM_MAGIC) {
			throw new UnsupportedAudioFileException(
			    "not an AIFF file: header magic is not FORM");
		}
		int nTotalLength = dataInputStream.readInt();
		nMagic = dataInputStream.readInt();
		boolean	bIsAifc;
		if (nMagic == AiffTool.AIFF_AIFF_MAGIC) {
			bIsAifc = false;
		} else if (nMagic == AiffTool.AIFF_AIFC_MAGIC) {
			bIsAifc = true;
		} else {
			throw new UnsupportedAudioFileException(
			    "unsupported IFF file: header magic neither AIFF nor AIFC");
		}
		boolean bFVerFound=!bIsAifc;
		boolean bCommFound=false;
		boolean bSSndFound=false;
		AudioFormat format=null;
		int nDataChunkLength=0;

		// walk through the chunks
		// chunks may be in any order. However, in this implementation, SSND must be last
		while (!bFVerFound || !bCommFound || !bSSndFound) {
			nMagic = dataInputStream.readInt();
			int nChunkLength = dataInputStream.readInt();
			switch (nMagic) {
			case AiffTool.AIFF_COMM_MAGIC:
				format=readCommChunk(dataInputStream, nChunkLength);
				if (TDebug.TraceAudioFileReader) {
					TDebug.out("Read COMM chunk with length "+nChunkLength);
				}
				bCommFound=true;
				break;
			case AiffTool.AIFF_FVER_MAGIC:
				if (!bFVerFound) {
					readVerChunk(dataInputStream, nChunkLength);
					if (TDebug.TraceAudioFileReader) {
						TDebug.out("Read FVER chunk with length "+nChunkLength);
					}
					bFVerFound=true;
				} else {
					skipChunk(dataInputStream, nChunkLength, 0);
				}
				break;
			case AiffTool.AIFF_SSND_MAGIC:
				if (!bCommFound || !bFVerFound) {
					throw new UnsupportedAudioFileException(
					    "cannot handle AIFF file: SSND not last chunk");
				}
				bSSndFound=true;
				nDataChunkLength=nChunkLength-8;
				// 8 information bytes of no interest
				dataInputStream.skip(8);
				if (TDebug.TraceAudioFileReader) {
					TDebug.out("Found SSND chunk with length "+nChunkLength);
				}
				break;
			default:
				if (TDebug.TraceAudioFileReader) {
					TDebug.out("Skipping unknown chunk: "
					           +Integer.toHexString(nMagic));
				}
				skipChunk(dataInputStream, nChunkLength, 0);
				break;
			}
		}

		// TODO: length argument has to be in frames
		AudioFileFormat	audioFileFormat = new TAudioFileFormat(
			bIsAifc ? AudioFileFormat.Type.AIFC : AudioFileFormat.Type.AIFF,
			format,
			nDataChunkLength / format.getFrameSize(),
			nTotalLength + 8);
		if (TDebug.TraceAudioFileReader) {TDebug.out("AiffAudioFileReader.getAudioFileFormat(InputStream, long): end"); }
		return audioFileFormat;
	}
}



/*** AiffAudioFileReader.java ***/
