#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <DataIO.h>
#include <ByteOrder.h>
#include <InterfaceDefs.h>
#include "RawFormats.h"
#include "aiff_reader.h"

#define TRACE_THIS 1
#if TRACE_THIS
  #define TRACE printf
#else
  #define TRACE(a...)
#endif

#define BUFFER_SIZE	16384

#define FOURCC(a,b,c,d)	((((uint32)(a)) << 24) | (((uint32)(b)) << 16) | (((uint32)(c)) << 8) | ((uint32)(d)))
#define UINT16(a) 		((uint16)B_BENDIAN_TO_HOST_INT16((a)))
#define UINT32(a) 		((uint32)B_BENDIAN_TO_HOST_INT32((a)))

aiffReader::aiffReader()
{
	TRACE("aiffReader::aiffReader\n");
	fBuffer = 0;
}


aiffReader::~aiffReader()
{
	if (fBuffer)
		free(fBuffer);
}

      
const char *
aiffReader::Copyright()
{
	return "AIFF & AIFF-C reader, " B_UTF8_COPYRIGHT " by Marcus Overhagen";
}

	
status_t
aiffReader::Sniff(int32 *streamCount)
{
	TRACE("aiffReader::Sniff\n");

	fSource = dynamic_cast<BPositionIO *>(Reader::Source());
	if (!fSource) {
		TRACE("aiffReader::Sniff: not a BPositionIO\n");
		return B_ERROR;
	}

	int64 filesize = Source()->Seek(0, SEEK_END);
	if (filesize < 42) {
		TRACE("aiffReader::Sniff: File too small\n");
		return B_ERROR;
	}
	
	aiff_chunk aiff;

	if (sizeof(aiff) != Source()->ReadAt(0, &aiff, sizeof(aiff))) {
		TRACE("aiffReader::Sniff: aiff_chunk reading failed\n");
		return B_ERROR;
	}

	if (UINT32(aiff.chunk_id) != FOURCC('F','O','R','M')
		|| (UINT32(aiff.aiff_id) != FOURCC('A','I','F','F')
			&& UINT32(aiff.aiff_id) != FOURCC('A','I','F','C'))) {
		TRACE("aiffReader::Sniff: aiff not recognized\n");
		return B_ERROR;
	}
	
	comm_chunk comm;
	ssnd_chunk ssnd;
	int64 pos = sizeof(aiff);

	fDataStart = 0;
	fDataSize = 0;
	
	// read all chunks and search for "COMM" and "SSND", everything else is ignored
	bool foundCOMM = false;
	bool foundSSND = false;
	while (pos + sizeof(chunk_struct) <= filesize) {
		chunk_struct chunk;
		if (sizeof(chunk) != Source()->ReadAt(pos, &chunk, sizeof(chunk))) {
			TRACE("aiffReader::Sniff: chunk header reading failed\n");
			return B_ERROR;
		}
		pos += sizeof(chunk);
		switch (UINT32(chunk.chunk_id)) {
			case FOURCC('C','O','M','M'):
			{
				if (foundCOMM)
					break;
				uint32 size = UINT32(chunk.chunk_size);
				if (size >= 18) {
					size = min_c(size, sizeof(comm));
					if (size != (uint32)Source()->ReadAt(pos, &comm, size)) {
						TRACE("aiffReader::Sniff: COMM chunk reading failed\n");
						break;
					}
					if (size < 22)
						comm.compression_id = B_HOST_TO_BENDIAN_INT32(FOURCC('N','O','N','E'));
					foundCOMM = true;
				} else {
					TRACE("aiffReader::Sniff: COMM chunk too small\n");
				}
				break;
			}
			case FOURCC('S','S','N','D'):
			{
				if (foundSSND)
					break;
				uint32 size = UINT32(chunk.chunk_size);
				if (size >= sizeof(ssnd)) {
					if (sizeof(ssnd) != Source()->ReadAt(pos, &ssnd, sizeof(ssnd))) {
						TRACE("aiffReader::Sniff: SSND chunk reading failed\n");
						break;
					}
					fDataStart = pos + sizeof(ssnd) + UINT32(ssnd.offset);
					fDataSize = UINT32(chunk.chunk_size) - sizeof(ssnd);
					if (pos + fDataSize + sizeof(ssnd) > filesize)
						fDataSize = filesize - pos - sizeof(ssnd);
					foundSSND = true;
				} else {
					TRACE("aiffReader::Sniff: SSND chunk too small\n");
				}
				break;
			}
			default:
				TRACE("aiffReader::Sniff: ignoring chunk 0x%08lx of %lu bytes\n", UINT32(chunk.chunk_id), UINT32(chunk.chunk_size));
				break;
		}
		pos += UINT32(chunk.chunk_size);
		pos += (pos & 1);
	}

	if (!foundCOMM) {
		TRACE("aiffReader::Sniff: couldn't find format chunk\n");
		return B_ERROR;
	}
	if (!foundSSND) {
		TRACE("aiffReader::Sniff: couldn't find data chunk\n");
		return B_ERROR;
	}

	TRACE("aiffReader::Sniff: we found something that looks like:\n");
	
	TRACE("  channel_count    %d\n", UINT16(comm.channel_count));
	TRACE("  frame_count      %ld\n", UINT32(comm.frame_count));
	TRACE("  bits_per_sample  %d\n", UINT16(comm.bits_per_sample));
	TRACE("  sample_rate      %ld\n", DecodeFrameRate(comm.sample_rate));
	TRACE("  compression_id   %s\n", string_for_compression(UINT32(comm.compression_id)));
	TRACE("  offset           %ld\n", UINT32(ssnd.offset));
	TRACE("  block_size       %ld\n", UINT32(ssnd.block_size));

	fChannelCount = UINT16(comm.channel_count);
	fFrameCount = UINT32(comm.frame_count);
	fFrameRate = DecodeFrameRate(comm.sample_rate);
	fDuration = (1000000LL * fFrameCount) / fFrameRate;
	fValidBitsPerSample = UINT16(comm.bits_per_sample);
	fBytesPerSample = (fValidBitsPerSample + 7) / 8;
	fBytesPerFrame = fBytesPerSample * fChannelCount;
	fFormatCode = UINT32(comm.compression_id);

	if (fChannelCount < 1)
		fChannelCount = 1;
	if (fFrameRate < 1)
		fFrameRate = 44100;

	if (fBytesPerSample == 0) {
		TRACE("aiffReader::Sniff: sample format not recognized\n");
		return B_ERROR;
	}
	
	switch (fFormatCode) {
		case FOURCC('N','O','N','E'):
		case FOURCC('F','L','3','2'):
		case FOURCC('f','l','3','2'):
		case FOURCC('f','l','6','4'):
			fRaw = true;
			break;
		default:
			fRaw = false;
			break;
	}

	fPosition = 0;
	
	fBufferSize = (BUFFER_SIZE / fBytesPerFrame) * fBytesPerFrame;
	fBuffer = malloc(fBufferSize);

	TRACE("  fDataStart     %Ld\n", fDataStart);
	TRACE("  fDataSize      %Ld\n", fDataSize);
	TRACE("  fFrameCount    %Ld\n", fFrameCount);
	TRACE("  fDuration      %Ld\n", fDuration);
	TRACE("  fChannelCount  %d\n", fChannelCount);
	TRACE("  fFrameRate     %ld\n", fFrameRate);
	TRACE("  fValidBitsPerSample %d\n", fValidBitsPerSample);
	TRACE("  fBytesPerSample     %d\n", fBytesPerSample);
	TRACE("  fBytesPerFrame %d\n", fBytesPerFrame);
	TRACE("  fFormatCode    %ld\n", fFormatCode);
	TRACE("  fRaw           %d\n", fRaw);
	
	BMediaFormats formats;
	if (fRaw) {
		// a raw PCM format
		media_format_description description;
		description.family = B_BEOS_FORMAT_FAMILY;
		description.u.beos.format = B_BEOS_FORMAT_RAW_AUDIO;
		formats.GetFormatFor(description, &fFormat);
		fFormat.u.raw_audio.frame_rate = fFrameRate;
		fFormat.u.raw_audio.channel_count = fChannelCount;
		switch (fFormatCode) {
			case FOURCC('N','O','N','E'):
				switch (fBytesPerSample) {
					case 1:
						fFormat.u.raw_audio.format = media_raw_audio_format::B_AUDIO_CHAR;
						break;
					case 2:
						fFormat.u.raw_audio.format = media_raw_audio_format::B_AUDIO_SHORT;
						break;
					case 3:
						fFormat.u.raw_audio.format = B_AUDIO_FORMAT_INT24;
						break;
					case 4:
						fFormat.u.raw_audio.format = media_raw_audio_format::B_AUDIO_INT;
						break;
					default:
						TRACE("aiffReader::Sniff: unknown bytes per sample for raw format\n");
						return B_ERROR;
				}
				break;
			case FOURCC('F','L','3','2'):
				fFormat.u.raw_audio.format = media_raw_audio_format::B_AUDIO_FLOAT;
				break;
			case FOURCC('f','l','3','2'):
				fFormat.u.raw_audio.format = media_raw_audio_format::B_AUDIO_FLOAT;
				break;
			case FOURCC('f','l','6','4'):
				fFormat.u.raw_audio.format = B_AUDIO_FORMAT_FLOAT64;
				break;
			default:
				TRACE("aiffReader::Sniff: unknown raw format\n");
				return B_ERROR;
		}
		fFormat.u.raw_audio.byte_order = B_MEDIA_BIG_ENDIAN;
		fFormat.u.raw_audio.buffer_size = fBufferSize;
	} else {
		// some encoded format
		media_format_description description;
		description.family = B_AIFF_FORMAT_FAMILY;
		description.u.aiff.codec = fFormatCode;
		formats.GetFormatFor(description, &fFormat);
		fFormat.u.encoded_audio.output.frame_rate = fFrameRate;
		fFormat.u.encoded_audio.output.channel_count = fChannelCount;
	}

	*streamCount = 1;
	return B_OK;
}

void
aiffReader::GetFileFormatInfo(media_file_format *mff)
{
	mff->capabilities =   media_file_format::B_READABLE
						| media_file_format::B_KNOWS_ENCODED_AUDIO
						| media_file_format::B_IMPERFECTLY_SEEKABLE;
	mff->family = B_MISC_FORMAT_FAMILY;
	mff->version = 100;
	strcpy(mff->mime_type, "audio/x-aiff");
	strcpy(mff->file_extension, "aiff");
	strcpy(mff->short_name,  "AIFF / AIFF-C");
	strcpy(mff->pretty_name, "Audio Interchange File Format");
}

status_t
aiffReader::AllocateCookie(int32 streamNumber, void **cookie)
{
	return B_OK;
}

status_t
aiffReader::FreeCookie(void *cookie)
{
	return B_OK;
}


status_t
aiffReader::GetStreamInfo(void *cookie, int64 *frameCount, bigtime_t *duration,
						 media_format *format, void **infoBuffer, int32 *infoSize)
{
	*frameCount = fFrameCount;
	*duration = fDuration;
	*format = fFormat;
	*infoBuffer = 0;
	*infoSize = 0;
	return B_OK;
}


status_t
aiffReader::Seek(void *cookie,
				uint32 seekTo,
				int64 *frame, bigtime_t *time)
{
	int64 pos;

	if (seekTo & B_MEDIA_SEEK_TO_FRAME) {
		if (fRaw)
			pos = *frame * fBytesPerFrame;
		else
			pos = (*frame * fDataSize) / fFrameCount;
		pos = (pos / fBytesPerFrame) * fBytesPerFrame; // round down to a block start
		TRACE("aiffReader::Seek to frame %Ld, pos %Ld\n", *frame, pos);
	} else if (seekTo & B_MEDIA_SEEK_TO_TIME) {
		if (fRaw)
			pos = (*time * fFrameRate * fBytesPerFrame) / 1000000LL;
		else
			pos = (*time * fDataSize) / fDuration;
		pos = (pos / fBytesPerFrame) * fBytesPerFrame; // round down to a block start
		TRACE("aiffReader::Seek to time %Ld, pos %Ld\n", *time, pos);
	} else {
		return B_ERROR;
	}

	if (fRaw)
		*frame = pos / fBytesPerFrame;
	else
		*frame = (pos * fFrameCount) / fDataSize;
	*time = (*frame * 1000000LL) / fFrameRate;

	TRACE("aiffReader::Seek newtime %Ld\n", *time);
	TRACE("aiffReader::Seek newframe %Ld\n", *frame);
	
	if (pos < 0 || pos > fDataSize) {
		TRACE("aiffReader::Seek invalid position %Ld\n", pos);
		return B_ERROR;
	}
	
	fPosition = pos;
	return B_OK;
}


status_t
aiffReader::GetNextChunk(void *cookie,
						void **chunkBuffer, int32 *chunkSize,
						media_header *mediaHeader)
{
	// XXX it might be much better to not return any start_time information for encoded formats here,
	// XXX and instead use the last time returned from seek and count forward after decoding.
	mediaHeader->start_time = ((fPosition / fBytesPerFrame) * 1000000LL) / fFrameRate;
	mediaHeader->file_pos = fDataStart + fPosition;

	int64 maxreadsize = fDataSize - fPosition;
	int32 readsize = fBufferSize;
	if (maxreadsize < readsize)
		readsize = maxreadsize;
	if (readsize == 0)
		return B_LAST_BUFFER_ERROR;
		
	if (readsize != Source()->ReadAt(fDataStart + fPosition, fBuffer, readsize)) {
		TRACE("aiffReader::GetNextChunk: unexpected read error\n");
		return B_ERROR;
	}
	
	// XXX if the stream has more than two channels, we need to reorder channel data here
	
	fPosition += readsize;
	*chunkBuffer = fBuffer;
	*chunkSize = readsize;
	return B_OK;
}

uint32
aiffReader::DecodeFrameRate(void *_80bit_float)
{
	// algorithm from http://www.borg.com/~jglatt/tech/aiff.htm
	uint32 mantissa;
	uint32 last;
	uint32 exp;
	
	mantissa = (uint32)B_BENDIAN_TO_HOST_INT32(*(uint32 *)((char *)_80bit_float + 2));
	exp = 30 - *(uint8 *)((char *)_80bit_float + 1);
	if (exp > 32)
		return 0;
	last = 0;
	while (exp--) {
		last = mantissa;
		mantissa >>= 1;
	}
	if (last & 1)
		mantissa++; 
   return mantissa; 
}


Reader *
aiffReaderPlugin::NewReader()
{
	return new aiffReader;
}


MediaPlugin *instantiate_plugin()
{
	return new aiffReaderPlugin;
}
