/*
 * Copyright (c) 2003-2004, Marcus Overhagen
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include <stdio.h>
#include <string.h>
#include <DataIO.h>
#include <OS.h>
#include <MediaRoster.h>
#include <ReaderPlugin.h>
 
#include "RawFormats.h"
#include "RawDecoderPlugin.h"
#include "AudioConversion.h"
 
//#define TRACE_RAW_DECODER
#ifdef TRACE_RAW_DECODER
  #define TRACE printf
#else
  #define TRACE(a...)
#endif
 
inline size_t
AudioBufferSize(int32 channel_count, uint32 sample_format, float frame_rate, bigtime_t buffer_duration = 50000 /* 50 ms */)
{
	return (sample_format & 0xf) * channel_count * (size_t)((frame_rate * buffer_duration) / 1000000.0);
}
 
 
void
RawDecoder::GetCodecInfo(media_codec_info *info)
{
	strcpy(info->short_name, "raw");
 
	if (fInputFormat.IsAudio())
		strcpy(info->pretty_name, "Raw audio decoder");
	else
		strcpy(info->pretty_name, "Raw video decoder");
}
 
 
status_t
RawDecoder::Setup(media_format *ioEncodedFormat,
				  const void *infoBuffer, size_t infoSize)
{
	char s[200];
	string_for_format(*ioEncodedFormat, s, sizeof(s));
	TRACE("RawDecoder::Setup: %s\n", s);
 
	if (ioEncodedFormat->type != B_MEDIA_RAW_AUDIO && ioEncodedFormat->type != B_MEDIA_RAW_VIDEO)
		return B_ERROR;
 
	fInputFormat = *ioEncodedFormat;
 
	if (ioEncodedFormat->type == B_MEDIA_RAW_VIDEO) {
		fInputSampleSize = ioEncodedFormat->u.raw_video.display.line_count * ioEncodedFormat->u.raw_video.display.bytes_per_row;
		fInputFrameSize = fInputSampleSize;
	} else {
		fInputSampleSize = (ioEncodedFormat->u.raw_audio.format & B_AUDIO_FORMAT_SIZE_MASK);
		fInputFrameSize = fInputSampleSize * ioEncodedFormat->u.raw_audio.channel_count;
	}
 
	// since ioEncodedFormat is later passed to the application by BMediaTrack::EncodedFormat()
	// we need to remove non public format specifications
 
	// remove non format bits, like channel order
	ioEncodedFormat->u.raw_audio.format &= B_AUDIO_FORMAT_MASK;
 
	switch (ioEncodedFormat->u.raw_audio.format) {
		case B_AUDIO_FORMAT_UINT8:
		case B_AUDIO_FORMAT_INT8:
		case B_AUDIO_FORMAT_INT16:
		case B_AUDIO_FORMAT_INT32:
		case B_AUDIO_FORMAT_FLOAT32:
			break; // ok to pass through
 
		case B_AUDIO_FORMAT_INT24:
			ioEncodedFormat->u.raw_audio.format = B_AUDIO_FORMAT_INT32;
			break;
 
		case B_AUDIO_FORMAT_FLOAT64:
			ioEncodedFormat->u.raw_audio.format = B_AUDIO_FORMAT_FLOAT32;
			break;
 
		default:
			TRACE("RawDecoder::Setup: unknown input format\n");
			return B_ERROR;
	}
 
	// since we can translate to a different buffer size,
	// suggest something nicer than delivered by the
	// file reader (perhaps we should even report wildcard?)
	// I don't believe we can negotiate the buffer size with the reader
//	ioEncodedFormat->u.raw_audio.buffer_size = AudioBufferSize(
//														ioEncodedFormat->u.raw_audio.channel_count,
//														ioEncodedFormat->u.raw_audio.format,
//														ioEncodedFormat->u.raw_audio.frame_rate);
	return B_OK;
}
 
 
status_t
RawDecoder::NegotiateOutputFormat(media_format *ioDecodedFormat)
{
	// BeBook says: The codec will find and return in ioFormat its best matching format
	// => This means, we never return an error, and always change the format values
	//    that we don't support to something more applicable
	if (fInputFormat.type == B_MEDIA_RAW_VIDEO)
		return NegotiateVideoOutputFormat(ioDecodedFormat);
	if (fInputFormat.type == B_MEDIA_RAW_AUDIO)
		return NegotiateAudioOutputFormat(ioDecodedFormat);
	debugger("RawDecoder::NegotiateOutputFormat: wrong encoded format type");
	return B_ERROR;
}
 
 
status_t
RawDecoder::NegotiateVideoOutputFormat(media_format *ioDecodedFormat)
{
	return B_ERROR;
}
 
 
status_t
RawDecoder::NegotiateAudioOutputFormat(media_format *ioDecodedFormat)
{
	char s[1024];
 
	TRACE("RawDecoder::NegotiateAudioOutputFormat enter:\n");
 
	ioDecodedFormat->type = B_MEDIA_RAW_AUDIO;
	switch (ioDecodedFormat->u.raw_audio.format) {
		case media_raw_audio_format::B_AUDIO_FLOAT:
		case media_raw_audio_format::B_AUDIO_SHORT:
		case media_raw_audio_format::B_AUDIO_UCHAR:
		case media_raw_audio_format::B_AUDIO_CHAR:
			ioDecodedFormat->u.raw_audio.valid_bits = 0;
			break; // we can produce this on request
 
		case media_raw_audio_format::B_AUDIO_INT:
			ioDecodedFormat->u.raw_audio.valid_bits = fInputFormat.u.raw_audio.valid_bits;
			break; // we can produce this on request
 
		default:
			switch (fInputFormat.u.raw_audio.format & B_AUDIO_FORMAT_MASK) {
				case media_raw_audio_format::B_AUDIO_SHORT:
				case media_raw_audio_format::B_AUDIO_UCHAR:
				case media_raw_audio_format::B_AUDIO_CHAR:
					ioDecodedFormat->u.raw_audio.format = fInputFormat.u.raw_audio.format & B_AUDIO_FORMAT_MASK;
					ioDecodedFormat->u.raw_audio.valid_bits = 0;
					break;
 
				case media_raw_audio_format::B_AUDIO_INT:
				case B_AUDIO_FORMAT_INT24:
					ioDecodedFormat->u.raw_audio.format = media_raw_audio_format::B_AUDIO_INT;
					ioDecodedFormat->u.raw_audio.valid_bits = fInputFormat.u.raw_audio.valid_bits;
					break;
 
				case media_raw_audio_format::B_AUDIO_FLOAT:
				case B_AUDIO_FORMAT_FLOAT64:
				default:
					ioDecodedFormat->u.raw_audio.format = media_raw_audio_format::B_AUDIO_FLOAT;
					ioDecodedFormat->u.raw_audio.valid_bits = 0;
					break;
			}
			break;
	}
 
	ioDecodedFormat->u.raw_audio.frame_rate = fInputFormat.u.raw_audio.frame_rate;
	ioDecodedFormat->u.raw_audio.channel_count = fInputFormat.u.raw_audio.channel_count;
 
	fFrameRate = (int32) ioDecodedFormat->u.raw_audio.frame_rate;
 
	fOutputSampleSize = (ioDecodedFormat->u.raw_audio.format & B_AUDIO_FORMAT_SIZE_MASK);
	fOutputFrameSize = fOutputSampleSize * ioDecodedFormat->u.raw_audio.channel_count;
 
	if (ioDecodedFormat->u.raw_audio.byte_order == 0)
		ioDecodedFormat->u.raw_audio.byte_order = B_MEDIA_HOST_ENDIAN;
 
	ioDecodedFormat->u.raw_audio.channel_mask = 0;
	ioDecodedFormat->u.raw_audio.matrix_mask = 0;
 
	ioDecodedFormat->u.raw_audio.buffer_size = fInputFormat.u.raw_audio.buffer_size;
 
// I don't believe we can negotiate the buffer size with the reader
// the decoder might use a different buffer for output but it must read all bytes given.
//	if (ioDecodedFormat->u.raw_audio.buffer_size < 128 || ioDecodedFormat->u.raw_audio.buffer_size > 65536) {
//		ioDecodedFormat->u.raw_audio.buffer_size = AudioBufferSize(
//														ioDecodedFormat->u.raw_audio.channel_count,
//														ioDecodedFormat->u.raw_audio.format,
//														ioDecodedFormat->u.raw_audio.frame_rate);
//	} else {
		// round down to exact multiple of output frame size
//		ioDecodedFormat->u.raw_audio.buffer_size = (ioDecodedFormat->u.raw_audio.buffer_size / fOutputFrameSize) * fOutputFrameSize;
//	}
 
	fOutputBufferFrameCount = ioDecodedFormat->u.raw_audio.buffer_size / fOutputFrameSize;
 
	// setup input swapping function
	if (fInputFormat.u.raw_audio.byte_order == B_MEDIA_HOST_ENDIAN) {
		fSwapInput = 0;
	} else {
		switch (fInputFormat.u.raw_audio.format & B_AUDIO_FORMAT_MASK) {
			case B_AUDIO_FORMAT_INT16:
				fSwapInput = &swap_int16;
				break;
			case B_AUDIO_FORMAT_INT24:
				fSwapInput = &swap_int24;
				break;
			case B_AUDIO_FORMAT_INT32:
				fSwapInput = &swap_int32;
				break;
			case B_AUDIO_FORMAT_FLOAT32:
				fSwapInput = &swap_float32;
				break;
			case B_AUDIO_FORMAT_FLOAT64:
				fSwapInput = &swap_float64;
				break;
			case B_AUDIO_FORMAT_UINT8:
			case B_AUDIO_FORMAT_INT8:
				fSwapInput = 0;
				break;
			default:
				debugger("RawDecoder::NegotiateAudioOutputFormat unknown input format\n");
				break;
		}
	}
 
	// setup output swapping function
	if (ioDecodedFormat->u.raw_audio.byte_order == B_MEDIA_HOST_ENDIAN) {
		fSwapOutput = 0;
	} else {
		switch (ioDecodedFormat->u.raw_audio.format) {
			case B_AUDIO_FORMAT_INT16:
				fSwapOutput = &swap_int16;
				break;
			case B_AUDIO_FORMAT_INT32:
				fSwapOutput = &swap_int32;
				break;
			case B_AUDIO_FORMAT_FLOAT32:
				fSwapOutput = &swap_float32;
				break;
			case B_AUDIO_FORMAT_UINT8:
			case B_AUDIO_FORMAT_INT8:
				fSwapOutput = 0;
				break;
			default:
				debugger("RawDecoder::NegotiateAudioOutputFormat unknown output format\n");
				break;
		}
	}
 
	// setup sample conversation function
	switch (fInputFormat.u.raw_audio.format & B_AUDIO_FORMAT_MASK) {
		case B_AUDIO_FORMAT_UINT8:
			switch (ioDecodedFormat->u.raw_audio.format) {
				case B_AUDIO_FORMAT_UINT8:
					fConvert = &uint8_to_uint8;
					break;
				case B_AUDIO_FORMAT_INT8:
					fConvert = &uint8_to_int8;
					break;
				case B_AUDIO_FORMAT_INT16:
					fConvert = &uint8_to_int16;
					break;
				case B_AUDIO_FORMAT_INT32:
					fConvert = &uint8_to_int32;
					break;
				case B_AUDIO_FORMAT_FLOAT32:
					fConvert = &uint8_to_float32;
					break;
				default:
					debugger("RawDecoder::NegotiateAudioOutputFormat unknown output format\n");
					break;
			}
			break;
 
		case B_AUDIO_FORMAT_INT8:
			switch (ioDecodedFormat->u.raw_audio.format) {
				case B_AUDIO_FORMAT_UINT8:
					fConvert = &int8_to_uint8;
					break;
				case B_AUDIO_FORMAT_INT8:
					fConvert = &int8_to_int8;
					break;
				case B_AUDIO_FORMAT_INT16:
					fConvert = &int8_to_int16;
					break;
				case B_AUDIO_FORMAT_INT32:
					fConvert = &int8_to_int32;
					break;
				case B_AUDIO_FORMAT_FLOAT32:
					fConvert = &int8_to_float32;
					break;
				default:
					debugger("RawDecoder::NegotiateAudioOutputFormat unknown output format\n");
					break;
			}
			break;
 
		case B_AUDIO_FORMAT_INT16:
			switch (ioDecodedFormat->u.raw_audio.format) {
				case B_AUDIO_FORMAT_UINT8:
					fConvert = &int16_to_uint8;
					break;
				case B_AUDIO_FORMAT_INT8:
					fConvert = &int16_to_int8;
					break;
				case B_AUDIO_FORMAT_INT16:
					fConvert = &int16_to_int16;
					break;
				case B_AUDIO_FORMAT_INT32:
					fConvert = &int16_to_int32;
					break;
				case B_AUDIO_FORMAT_FLOAT32:
					fConvert = &int16_to_float32;
					break;
				default:
					debugger("RawDecoder::NegotiateAudioOutputFormat unknown output format\n");
					break;
			}
			break;
 
		case B_AUDIO_FORMAT_INT24:
			switch (ioDecodedFormat->u.raw_audio.format) {
				case B_AUDIO_FORMAT_UINT8:
					fConvert = &int24_to_uint8;
					break;
				case B_AUDIO_FORMAT_INT8:
					fConvert = &int24_to_int8;
					break;
				case B_AUDIO_FORMAT_INT16:
					fConvert = &int24_to_int16;
					break;
				case B_AUDIO_FORMAT_INT32:
					fConvert = &int24_to_int32;
					break;
				case B_AUDIO_FORMAT_FLOAT32:
					fConvert = &int24_to_float32;
					break;
				default:
					debugger("RawDecoder::NegotiateAudioOutputFormat unknown output format\n");
					break;
			}
			break;
 
		case B_AUDIO_FORMAT_INT32:
			switch (ioDecodedFormat->u.raw_audio.format) {
				case B_AUDIO_FORMAT_UINT8:
					fConvert = &int32_to_uint8;
					break;
				case B_AUDIO_FORMAT_INT8:
					fConvert = &int32_to_int8;
					break;
				case B_AUDIO_FORMAT_INT16:
					fConvert = &int32_to_int16;
					break;
				case B_AUDIO_FORMAT_INT32:
					fConvert = &int32_to_int32;
					break;
				case B_AUDIO_FORMAT_FLOAT32:
					fConvert = &int32_to_float32;
					break;
				default:
					debugger("RawDecoder::NegotiateAudioOutputFormat unknown output format\n");
					break;
			}
			break;
 
		case B_AUDIO_FORMAT_FLOAT32:
			switch (ioDecodedFormat->u.raw_audio.format) {
				case B_AUDIO_FORMAT_UINT8:
					fConvert = &float32_to_uint8;
					break;
				case B_AUDIO_FORMAT_INT8:
					fConvert = &float32_to_int8;
					break;
				case B_AUDIO_FORMAT_INT16:
					fConvert = &float32_to_int16;
					break;
				case B_AUDIO_FORMAT_INT32:
					fConvert = &float32_to_int32;
					break;
				case B_AUDIO_FORMAT_FLOAT32:
					fConvert = &float32_to_float32;
					break;
				default:
					debugger("RawDecoder::NegotiateAudioOutputFormat unknown output format\n");
					break;
			}
			break;
 
		case B_AUDIO_FORMAT_FLOAT64:
			switch (ioDecodedFormat->u.raw_audio.format) {
				case B_AUDIO_FORMAT_UINT8:
					fConvert = &float64_to_uint8;
					break;
				case B_AUDIO_FORMAT_INT8:
					fConvert = &float64_to_int8;
					break;
				case B_AUDIO_FORMAT_INT16:
					fConvert = &float64_to_int16;
					break;
				case B_AUDIO_FORMAT_INT32:
					fConvert = &float64_to_int32;
					break;
				case B_AUDIO_FORMAT_FLOAT32:
					fConvert = &float64_to_float32;
					break;
				default:
					debugger("RawDecoder::NegotiateAudioOutputFormat unknown output format\n");
					break;
			}
			break;
 
		default:
			debugger("RawDecoder::NegotiateAudioOutputFormat unknown input format\n");
			break;
	}
 
	fChunkBuffer = 0;
	fChunkSize = 0;
	fStartTime = 0;
 
	string_for_format(*ioDecodedFormat, s, sizeof(s));
	TRACE("RawDecoder::NegotiateAudioOutputFormat leave: %s\n", s);
 
	if (ioDecodedFormat->type == 0)
		debugger("RawDecoder::NegotiateAudioOutputFormat ioDecodedFormat->type == 0");
/*
	TRACE("fFrameRate              %ld\n", fFrameRate);
	TRACE("fInputFrameSize         %ld\n", fInputFrameSize);
	TRACE("fOutputFrameSize        %ld\n", fOutputFrameSize);
	TRACE("fInputSampleSize        %ld\n", fInputSampleSize);
	TRACE("fOutputSampleSize       %ld\n", fOutputSampleSize);
	TRACE("fOutputBufferFrameCount %ld\n", fOutputBufferFrameCount);
	TRACE("fSwapInput              %p\n", fSwapInput);
	TRACE("fConvert                %p\n", fConvert);
	TRACE("fSwapOutput             %p\n", fSwapOutput);
*/
	return B_OK;
}
 
 
status_t
RawDecoder::SeekedTo(int64 frame, bigtime_t time)
{
	fChunkSize = 0;
	
	TRACE("RawDecoder::SeekedTo called\n");
 
	fStartTime = time;
 
	return B_OK;
}
 
 
status_t
RawDecoder::Decode(void *buffer, int64 *frameCount,
				   media_header *mediaHeader, media_decode_info *info /* = 0 */)
{
	char *output_buffer = (char *)buffer;
	mediaHeader->start_time = fStartTime;
	*frameCount = 0;
	while (*frameCount < fOutputBufferFrameCount) {
		if (fChunkSize == 0) {
			media_header mh;
			status_t err;
			err = GetNextChunk(&fChunkBuffer, &fChunkSize, &mh);
			if (err != B_OK || fChunkSize < fInputFrameSize) {
				fChunkSize = 0;
				break;
			}
			if (fSwapInput)
				fSwapInput(const_cast<void *>(fChunkBuffer), fChunkSize / fInputSampleSize); // XXX TODO! FIX THIS, we write to a const buffer!!!
			fStartTime = mh.start_time;
			continue;
		}
		int32 frames = min_c(fOutputBufferFrameCount - *frameCount, fChunkSize / fInputFrameSize);
		if (frames == 0)
			break;
 
		int32 samples = frames * fInputFormat.u.raw_audio.channel_count;
		fConvert(output_buffer, fChunkBuffer, samples);
		fChunkBuffer = (const char *)fChunkBuffer + frames * fInputFrameSize;
		fChunkSize -= frames * fInputFrameSize;
		output_buffer += frames * fOutputFrameSize;
		*frameCount += frames;
		fStartTime += (1000000LL * frames) / fFrameRate;
	}
	// XXX should change channel order here for
	// B_AUDIO_FORMAT_CHANNEL_ORDER_WAVE and B_AUDIO_FORMAT_CHANNEL_ORDER_AIFF
 
	if (fSwapOutput)
		fSwapOutput(buffer, *frameCount * fInputFormat.u.raw_audio.channel_count);
	
	TRACE("framecount %Ld, time %Ld\n",*frameCount, mediaHeader->start_time);
		
	return *frameCount ? B_OK : B_ERROR;
}
 
 
Decoder *
RawDecoderPlugin::NewDecoder(uint index)
{
	return new RawDecoder;
}
 
 
static media_format raw_formats[2];
 
status_t
RawDecoderPlugin::GetSupportedFormats(media_format ** formats, size_t * count)
{
	BMediaFormats mediaFormats;
	media_format_description description;
	media_format format;
 
	// audio decoder
 
	description.family = B_BEOS_FORMAT_FAMILY;
	description.u.beos.format = B_BEOS_FORMAT_RAW_AUDIO;
	format.type = B_MEDIA_RAW_AUDIO;
	format.u.raw_audio = media_multi_audio_format::wildcard;
 
	status_t status = mediaFormats.MakeFormatFor(&description, 1, &format);
	if (status < B_OK)
		return status;
	raw_formats[0] = format;
 
	// video decoder
 
	description.u.beos.format = B_BEOS_FORMAT_RAW_VIDEO;
	format.type = B_MEDIA_RAW_VIDEO;
	format.u.raw_video = media_raw_video_format::wildcard;
 
	status = mediaFormats.MakeFormatFor(&description, 1, &format);
	if (status < B_OK)
		return status;
	raw_formats[1] = format;
 
	*formats = raw_formats;
	*count = 2;
 
	return B_OK;
}
 
MediaPlugin *instantiate_plugin()
{
	return new RawDecoderPlugin;
}

V547 Expression 'ioDecodedFormat->type == 0' is always false.