#include "OggFile.h"

using namespace std;
using namespace ALWrapper;

//Closes the file if open.
OggFile::~OggFile() {
	close();
}

//Opens an Ogg Vorbis file.
void OggFile::open(const string& fname)
{
	close();	//Close any already-open files.
	
	//This can throw. The exception will pass to the caller.
	openfile(fname, "rb");	//Get a file stream pointer.

	//If we reach this point, we have a valid FILE* pointer. Now we need
	//an Ogg stream. First we should check for vorbisness.
	int res = ov_test(file, &fileogg, 0, 0);
	if (res != 0) {
		//An error occurred while opening an Ogg stream.
		//Probably not a Vorbis file that was passed in.
		fclose(file);
		throw OggFileError(res);
	}
	//Now finish opening the file.
	res = ov_test_open(&fileogg);
	if (res != 0) {
		//Same deal.
		fclose(file);
		throw OggFileError(res);
	}
	
	length = ov_time_total(&fileogg, -1);
	filesize = (unsigned long) ov_raw_total(&fileogg, -1);
	const vorbis_info* vinfo = ov_info(&fileogg, -1);
	assert(vinfo != 0);
	bitrate = vinfo->bitrate_nominal;
	frequency = vinfo->rate;
	channels = vinfo->channels;
	header.erase();	//Get ready to write the file comments into header.

	const vorbis_comment* vcomments = ov_comment(&fileogg, -1);
	if (vcomments != 0) {
		for (int comment = 0; comment < vcomments->comments; ++comment) {
			header.append(vcomments->user_comments[comment]);
			header += "\n";
		}
	}
}

//Closes the currently open Ogg Vorbis stream.
void OggFile::close()
{
	if (isOpen())
		ov_clear(&fileogg);

	file = 0;
	
	AudioFile::close();
}

//Reads the data using the Ogg Vorbis functions, rather than a C FILE stream.
//This introduces a bit more complexity into the program.
Buffer OggFile::readData(unsigned long bufsize)
{
	assert(isOpen());
	
	char* buf = new char[bufsize];	//Allocate space to read into.
	unsigned long sz = 0;		//The current amount of data read.
	
#ifdef HAVE_BIG_ENDIAN
	unsigned char endian = 1;
#else
	unsigned char endian = 0;
#endif

	int curstream;			//This will be filled by ov_read.
	while (sz < bufsize) {
		//Read signed 16-bit data from the stream into the buffer.
		long oneread = ov_read(&fileogg, buf + sz, bufsize - sz,
		endian, 2, 1, &curstream);
		
		if (oneread > 0)
			sz += oneread;
		else if (oneread < 0) {
			delete [] buf;
			throw OggFileError(oneread);
		}
		else
			break;	//We hit EOF.
	}
	
	//The buffer that will be filled and returned.
	Buffer ret(buf, sz);
	
	delete [] buf;	//Delete the old copy of the data.

	return ret;
}

//Seeks to the given location in the file, whether that location is in bytes
//or in seconds. Time seeking is specific to the dervied class.
void OggFile::seek(unsigned long pos)
{
	//Seek in bytes.
	assert(isOpen());

	int ret = ov_raw_seek_lap(&fileogg, pos);
	if (ret != 0)
		throw OggFileError(ret);
}

void OggFile::seek(double seconds)
{
	//Seek in seconds.
	assert(isOpen());

	int ret = ov_time_seek_lap(&fileogg, seconds);
	if (ret != 0)
		throw OggFileError(ret);
}

//Returns the current position in the stream in bytes.
unsigned long OggFile::tell()
{
	ogg_int64_t ret = ov_raw_tell(&fileogg);
	if (ret < 0)
		throw OggFileError(errno, strerror(errno));

	return static_cast<unsigned long>(ret);
}

//Or seconds.
double OggFile::tellTime()
{
	return ov_time_tell(&fileogg);
}

//Returns the bitrate of the currently playing logical stream.
unsigned int OggFile::getCurrentBitRate()
{
	long curbitrate = ov_bitrate_instant(&fileogg);
	if (curbitrate <= 0)
		return 0;
	
	return static_cast<unsigned int>(curbitrate);
}

