/*
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * Copyright (c) 1999-2001 Apple Computer, Inc.  All Rights Reserved. The
 * contents of this file constitute Original Code as defined in and are
 * subject to the Apple Public Source License Version 1.2 (the 'License').
 * You may not use this file except in compliance with the License.  Please
 * obtain a copy of the License at http://www.apple.com/publicsource and
 * read it before using this file.
 *
 * This Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.  Please
 * see the License for the specific language governing rights and
 * limitations under the License.
 *
 *
 * @APPLE_LICENSE_HEADER_END@
 *
 */

#include "MP3FileBroadcaster.h"
#include <fcntl.h>
//#include <unistd.h>
#include <stdio.h>

#include "OS.h"
#include "OSThread.h"

int gBitRateArray[] = { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 };
int gFrequencyArray[] = { 44100, 48000, 32000, 0 };

int gBitRateArrayv2[] = { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 };
int gFrequencyArrayv2[] = { 22050, 24000, 16000, 0 };
int gFrequencyArrayv2_5[] = { 11025, 12000, 8000, 0 };

MP3FileBroadcaster::MP3FileBroadcaster(TCPSocket* socket, int bitrate, int frequency, int bufferSize) :
	mSocket(socket),
	mBitRate(bitrate),
	mBufferSize(bufferSize),
	mNumFramesSent(0),
	mBroadcastStartTime(0),
	mBuffer(NULL),
	mUpdater(NULL),
	mDesiredBitRate(bitrate),
	mDesiredFrequency(frequency)
{
	mBuffer = new unsigned char[bufferSize];
    mTitle[0] = 0;
    mArtist[0] = 0;
    mSong[0] = 0;
}

MP3FileBroadcaster::~MP3FileBroadcaster()
{
	delete [] mBuffer;
}

int MP3FileBroadcaster::PlaySong(char *fileName, char *currentFile, bool preflight, bool fastpreflight)
{
	UInt32 length, lengthSent;
	
	if (mBuffer == NULL)
		return -1;
	
	mFile.Set(fileName);
	if (!mFile.IsValid())
		return kCouldntOpenFile;
	
	CheckForTags();
	
	if (strlen(mTitle) == 0)
	{
		char* temp = fileName+strlen(fileName);
		while ((temp > fileName) && (*(temp-1) != kPathDelimiterChar))
			temp--;
			
	
        ::strncpy(mTitle, temp,sizeof(mTitle) -1);
        mTitle[sizeof(mTitle) -1] = 0;	
    }
	
	if (strlen(mArtist) == 0) // no artist set -- mTitle is always set
		::strcpy(mSong, mTitle); // mSong contains just the title
	else // have mTitle and mArtist
        ::sprintf(mSong, "%s - %s", mTitle, mArtist);// mSong has title - artist

	if (preflight)
		::printf("Preflighting %s\n", mSong);
//	else
//		::printf("Streaming %s\n", mSong);
	
	// skip all the padding at the beginning of the file
	mFile.Seek(mStartByte);
	bool done = false;

	OS_Error err = mFile.Read(mBuffer, mBufferSize, &length);
	if (err != OS_NoErr)
		return -1;

	for(short i = 0; i<length-1000; i++)
	{
		if ((mBuffer[i] == 0xff) && CheckHeaders(mBuffer + i) )
		{
			mStartByte += i;
			done = true;
			break;
		}
	}
		
	if (!done)
	{
		mFile.Close();
		return kBadFileFormat;
	}
	
//	::printf("Bitrate = %dkbits, frequency = %dKHz\n", mBitRate, mFrequency/1000);

	if (mDesiredBitRate && (mBitRate != mDesiredBitRate))
	{
		mFile.Close();
		return kWrongBitRate;
	}
		
	if (mDesiredFrequency == -1)
	{
		mDesiredFrequency = mFrequency;
		printf("Setting required frequency to %dKHz\n", mFrequency/1000);
	}
	
	if (mDesiredFrequency && (mFrequency != mDesiredFrequency))
	{
		mFile.Close();
		return kWrongFrequency;
	}
	
	if (mUpdater)
		mUpdater->RequestMetaInfoUpdate(mSong);

	mFile.Seek(mStartByte);
	
	if (mBroadcastStartTime == 0)
		mBroadcastStartTime = OS::Milliseconds();
	UInt64 startTime = OS::Milliseconds();
	int totalBytes = 0;
	int numFrames = 0;	// amount of play time this buffer represents
	int leftOver = 0;	// we may have a partial buffer left over from last read
	UInt64 properElapsedTime;
	while(true)
	{
		if (!preflight)
		{
			// the time length each frame represents depends on the frequency
			int numSamplesPerFrame = mIsMPEG2 ? 576 : 1152;	// these are MP3 standards
			properElapsedTime = mNumFramesSent * numSamplesPerFrame * 1000 / mFrequency; // frequency is samples per second
			UInt64 nextSendTime = mBroadcastStartTime + properElapsedTime;

			UInt64 currentTime = OS::Milliseconds();
			if (nextSendTime > currentTime)
				OSThread::Sleep(nextSendTime - currentTime);
		}
			
		err = mFile.Read(mBuffer + leftOver, mBufferSize - leftOver, &length);
		if ((err != OS_NoErr) || (length == 0))
			break;

		length += leftOver;
		mNumFramesSent += CountFrames(mBuffer, length, &leftOver);
		
		if (!preflight)
		{
			OS_Error err = mSocket->Send((char*)mBuffer, length - leftOver, &lengthSent);
			if (err != 0)
			{
				mFile.Close();
				return kConnectionError;
			}
		}
			
		totalBytes += length;
		if (leftOver > 0)
			::memcpy(mBuffer, mBuffer+length-leftOver, leftOver);
			
		if (preflight && fastpreflight && (totalBytes > 20 * 1024))
			break;	// just check first 20K of file
	}
	
//	UInt64 elapsed = OS::Milliseconds() - startTime;
//	UInt64 rate = (UInt64)totalBytes * 8 * 1000 / elapsed;
//	::printf("Sent %d bytes in %qd milliseconds = %qd bits per second\n", totalBytes, elapsed, rate);
	
	if ((mDesiredFrequency == 0) || preflight)
	{
		// if we aren't fixing the frequency, then we need to time each song seperately
		mBroadcastStartTime = OS::Milliseconds();
		mNumFramesSent = 0;
	}
	
	mFile.Close();
	
	return 0;
}

void MP3FileBroadcaster::CheckForTags()
{
	mArtist[0] = 0;
	mTitle[0] = 0;
	mStartByte = 0;
	
	if (ReadV2_3Tags())
		return;
		
	if (ReadV2_2Tags())
		return;
		
	if (ReadV1Tags())
		return;
	
	return;	
}

bool MP3FileBroadcaster::ReadV1Tags()
{
	char buffer[128];
	UInt32 length;
	int i;
	
	mFile.Seek(mFile.GetLength()-128);
	OS_Error err = mFile.Read(buffer, sizeof(buffer), &length);
	if ((err != OS_NoErr) || (length != 128))
		return false;
		
	if (strncmp(buffer, "TAG", 3))
		return false;
		
	// stored as space padded 30 byte buffer (no null termination)
	memcpy(mTitle, buffer+3, 30);
	for (i = 29; i>=0; i--)
		if (mTitle[i] != ' ')
		{
			mTitle[i+1] = 0;
			break;
		}
		
	// stored as space padded 30 byte buffer (no null termination)
	memcpy(mArtist, buffer+33, 30);
	for (i = 29; i>=0; i--)
		if (mArtist[i] != ' ')
		{
			mArtist[i+1] = 0;
			break;
		}

	return true;
}

bool MP3FileBroadcaster::ReadV2_2Tags()
{
	char buffer[1024];
	UInt32 length;
	
	mFile.Seek(0);
	OS_Error err = mFile.Read(buffer, sizeof(buffer), &length);

	if (strncmp(buffer, "ID3", 3))
		return false;
	
	if (buffer[3] != 2)
		return false;
		
	// we have a valid v2.2 tag header
	char* ptr = buffer + 10;
	
	// the total length of tags is encoded in this strange way to avoid being
	// interpreted as an MP3 "sync" flag (don't use the top bit of each byte).
	int totalTagLen = buffer[6]*2097152+buffer[7]*16384+buffer[8]*128+buffer[9];
	mStartByte = totalTagLen;	// skip tags when streaming
	
	// OK, I'm being lazy here, but if someone can't find a way to put the song
	// title and artist in the first 1K of header then they're just being plain mean.
	if (totalTagLen > 1024) totalTagLen = 1024;

	while (ptr-buffer < totalTagLen)
	{
		if (*ptr == 0)
			break;
			
		// next three bytes are length, so go two bytes, copy 4 and mask off one
		int fieldLen = ntohl(OS::GetUInt32FromMemory((UInt32*)(ptr+2))) & 0x00ffffff;
		
		if (!strncmp(ptr, "TP1", 3))	// Artist
		{
			int len = fieldLen;
			if (len > 255) len = 255;
			if (ptr[6] == 0)
			{
				::memcpy(mArtist, ptr+7, len-1);	// skip encoding byte
				mArtist[len-1] = 0;
			}
			else
				ConvertUTF16toASCII(ptr+7, len-1, mArtist, sizeof(mArtist));
		}

		if (!strncmp(ptr, "TT2", 3))	// Title
		{
			int len = fieldLen;
			if (len > 255) len = 255;
			if (ptr[6] == 0)
			{
				::memcpy(mTitle, ptr+7, len-1);	// skip encoding byte
				mTitle[len-1] = 0;
			}
			else
				ConvertUTF16toASCII(ptr+7, len-1, mTitle, sizeof(mTitle));
		}
		
		if ((strlen(mTitle) > 0) && (strlen(mArtist) > 0))
			break;	// we found the tags we need
		
		ptr += fieldLen + 6;	// skip field and header
	}
	
	return true;
}

bool MP3FileBroadcaster::ReadV2_3Tags()
{
	char buffer[1024];
	UInt32 length;
	
	mFile.Seek(0);
	OS_Error err = mFile.Read(buffer, sizeof(buffer), &length);

	if (strncmp(buffer, "ID3", 3))
		return false;
	
	if (buffer[3] != 3)
		return false;
		
	// we have a valid v2.3 tag header
	char* ptr = buffer + 10;
	
	// skip extended header if it exists
	if ((buffer[4] & 0x40) != 0)
		ptr += 10;
		
	// if any other flags are set (like "unsychronization"), bail.
	// these can be supported in the future.
	if (((buffer[4] & 0xbf) != 0) || (buffer[5] != 0))
		return false;
	
	// the total length of tags is encoded in this strange way to avoid being
	// interpreted as an MP3 "sync" flag (don't use the top bit of each byte).
	int totalTagLen = buffer[6]*2097152+buffer[7]*16384+buffer[8]*128+buffer[9];
	mStartByte = totalTagLen;	// skip tags when streaming
	
	// OK, I'm being lazy here, but if someone can't find a way to put the song
	// title and artist in the first 1K of header then they're just being plain mean.
	if (totalTagLen > 1024) totalTagLen = 1024;

	while (ptr-buffer < totalTagLen)
	{
		if (*ptr == 0)
			break;
			
		int fieldLen = ntohl(OS::GetUInt32FromMemory((UInt32*)(ptr+4)));
		
		// should check compression and encryption flags for these fields, but I
		// wouldn't really expect them to be set for title or artist
		
		if (!::strncmp(ptr, "TPE1", 4))	// Artist
		{
			int len = fieldLen;
			if (len > 255) len = 255;
			if (ptr[10] == 0)
			{
				::memcpy(mArtist, ptr+11, len-1);	// skip encoding byte
				mArtist[len-1] = 0;
			}
			else
				ConvertUTF16toASCII(ptr+11, len-1, mArtist, sizeof(mArtist));
		}

		if (!strncmp(ptr, "TIT2", 4))	// Title
		{
			int len = fieldLen;
			if (len > 255) len = 255;
			if (ptr[10] == 0)
			{
				::memcpy(mTitle, ptr+11, len-1);	// skip encoding byte
				mTitle[len-1] = 0;
			}
			else
				ConvertUTF16toASCII(ptr+11, len-1, mTitle, sizeof(mTitle));
		}
		
		if ((::strlen(mTitle) > 0) && (::strlen(mArtist) > 0))
			break;	// we found the tags we need
		
		ptr += fieldLen + 10;	// skip field and header
	}
	
	return true;
}

bool MP3FileBroadcaster::CheckHeaders(unsigned char * buffer)
{
	int bitRate, bitRate2;
	int frequency, frequency2;
	int recordSize;
	
	if (!ParseHeader(buffer, &bitRate, &frequency, &recordSize))
		return false;
	
	if (!ParseHeader(buffer + recordSize, &bitRate2, &frequency2, &recordSize))
		return false;
		
	if (frequency != frequency2)
		return false;
		
	mBitRate = bitRate;
	mFrequency = frequency;

	return true;
}

bool MP3FileBroadcaster::ParseHeader(unsigned char* buffer, int* bitRate, int* frequency, int* recordSize)
{
	if ((buffer[0] != 0xff) || ((buffer[1] & 0xe6) != 0xe2))
		return false;		// not a valid MP3 header (not valid or not layer 3)
		
	int version = (buffer[1] & 0x18) >> 3;
	
	mIsMPEG2 = (version != 3);
	
	if (mIsMPEG2)
		*bitRate = gBitRateArrayv2[buffer[2] >> 4];		// MPEG2 
	else
		*bitRate = gBitRateArray[buffer[2] >> 4];		// MPEG 1

	if (version == 3)
		*frequency = gFrequencyArray[(buffer[2] & 0x0a) >> 2];
	else if (version == 2)
		*frequency = gFrequencyArrayv2[(buffer[2] & 0x0a) >> 2];
	else
		*frequency = gFrequencyArrayv2_5[(buffer[2] & 0x0a) >> 2];
	
	if ((*bitRate == 0) || (*frequency == 0))
		return false;
		
	int pad = (buffer[2] & 0x02) >> 1;
	
	*recordSize = 144000 * *bitRate / *frequency;	// standard MP3 calculation

	// MPEG 2 (and 2.5) frames encode half the number of samples
	if (mIsMPEG2)
		*recordSize /= 2;
		
	*recordSize += pad;
	
	return true;
}

int MP3FileBroadcaster::CountFrames(unsigned char* buffer, UInt32 length, int* leftOver)
{
	int bitRate;
	int frequency;
	int recordSize;

	int offset = 0;
	int numFrames = 0;
	while (offset < length)
	{
		if (length - offset < 4)
			break;	// we don't have a whole header left, so move on
		
		if (!ParseHeader(buffer + offset, &bitRate, &frequency, &recordSize))
		{
			// Oops, we lost our stream, so advance byte by byte looking for a frame header
			offset++;
			continue;
		}
		
		if (recordSize + offset > length)
			break;	// we don't have the whole frame in this buffer.
		
		numFrames++;
		
		offset += recordSize;
	}
	
	// usually there will be a partial frame left over.  We leave this for next time.
	*leftOver = length - offset;
	
	return numFrames;
}

// We really should be converting from Unicode to Latin-1, but the conversion of the high byte characters isn't
// easy.  If I find some code or tables to do this I can include it later.
bool MP3FileBroadcaster::ConvertUTF16toASCII(char* sourceStr,int sourceSize, char* dest, int destSize)
{
	unsigned short *sourceStart = (unsigned short *)sourceStr;
	unsigned short *sourceEnd = (unsigned short *)(sourceStart + sourceSize);
	unsigned char *targetStart = (unsigned char *)dest;
	unsigned char *targetEnd = targetStart + destSize;
	
	bool result = true;
	unsigned short *source = sourceStart;
	unsigned char *target = targetStart;
	unsigned short ch;
	bool doSwap = false;
	ch = *source++;
	Assert((ch == 0xfffe) || (ch == 0xfeff));
	if (ch != 0xfeff)
		doSwap = true;

	while ((source < sourceEnd) && (target <= targetEnd))
	{
		ch = *source++;
		if (doSwap)
		{
			unsigned short low = (ch & 0xff) << 8;
			ch = (ch >> 8) | low;
		}

		if (ch < 0x80)
		{
			*target = ch;
			target++;
		}
	}

	if (target <= targetEnd)
		*target = 0;
	else
		result = false;

	return result;
}
