/***********************************************************************
 * AUTHOR: David McPaul
 *   FILE: TimeCode.cpp
 *  DESCR: Handles all TimeCode functions.
 ***********************************************************************/
#include <TimeCode.h>
#include "debug.h"
#include <string.h>

status_t us_to_timecode(bigtime_t micros, int * hours, int * minutes, int * seconds, int * frames, const timecode_info * code = NULL)
{
	int32 l_frames;

	CALLED();
	
	if (code) {
		// We have a valid timecode_info
		switch (code->type) {
			case	B_TIMECODE_DEFAULT:		// NTSC
			case	B_TIMECODE_30_DROP_2:	// NTSC
				l_frames = int32((((micros % 1000000) * 29.97) / 1000000) + (micros / 1000000 * 29.97));
				break;
			case	B_TIMECODE_30_DROP_4:	// Brazil
				l_frames = int32((((micros % 1000000) * 29.95) / 1000000) + (micros / 1000000 * 29.95));
				break;
			default:
				l_frames = (((micros % 1000000) * code->fps_div) / 1000000) + (micros / 1000000 * code->fps_div);
				break;
		};			
	} else {
		// Convert us to frames
		l_frames = int32((((micros % 1000000) * 29.97) / 1000000) + (micros / 1000000 * 29.97));
	}

	return frames_to_timecode(l_frames, hours, minutes, seconds, frames, code);
}

status_t timecode_to_us(int hours, int minutes, int seconds, int frames, bigtime_t * micros, const timecode_info * code = NULL)
{
	int32 l_frames;
	
	CALLED();

	if (timecode_to_frames(hours, minutes, seconds, frames, &l_frames, code) == B_OK) {
	
		if (code) {
			// We have a valid timecode_info
			switch (code->type) {
				case	B_TIMECODE_DEFAULT:		// NTSC
				case	B_TIMECODE_30_DROP_2:	// NTSC
					*micros = bigtime_t(l_frames * ((1000000 / 29.97) + 0.5));
					break;
				case	B_TIMECODE_30_DROP_4:	// Brazil
					*micros = bigtime_t(l_frames * ((1000000 / 29.95) + 0.5));
					break;
				default:
					*micros = l_frames * 1000000 / code->fps_div;
					break;
			};			

		} else {
			*micros = bigtime_t(l_frames * ((1000000 / 29.97) + 0.5));
		}

		return B_OK;
	}
	return B_ERROR;
}

status_t frames_to_timecode(int32 l_frames, int * hours, int * minutes, int * seconds, int * frames, const timecode_info * code = NULL)
{
	int fps_div;
	int total_mins;
	int32 extra_frames;
	int32 extra_frames2;

	CALLED();

	if (code) {
		// We have a valid timecode_info so use it
		fps_div = code->fps_div;

		if (code->every_nth > 0) {
			// Handle Drop Frames format

			total_mins = l_frames / fps_div / 60;

			// "every_nth" minute we gain "drop_frames" "except_nth" minute
			extra_frames = code->drop_frames*(total_mins/code->every_nth) - code->drop_frames*(total_mins/code->except_nth);
			l_frames += extra_frames;

			total_mins = l_frames / fps_div / 60;
			extra_frames2 = code->drop_frames*(total_mins/code->every_nth) - code->drop_frames*(total_mins/code->except_nth);

			// Gaining frames may mean that we gain more frames so we keep adjusting until no more to adjust
			while (extra_frames != extra_frames2) {
				l_frames += extra_frames2 - extra_frames;
				extra_frames = extra_frames2;
			
				total_mins = l_frames / fps_div / 60;
				extra_frames2 = code->drop_frames*(total_mins/code->every_nth) - code->drop_frames*(total_mins/code->except_nth);
			}
			
			// l_frames should now include all adjusted frames
		}
	} else {
		// no timecode_info so set default NTSC :-(
		fps_div = 30;		// NTSC Default
		total_mins = l_frames / fps_div / 60;

		// "every_nth" minute we gain "drop_frames" "except_nth" minute
		extra_frames = 2*(total_mins) - 2*(total_mins/10);
		l_frames += extra_frames;

		total_mins = l_frames / fps_div / 60;
		extra_frames2 = 2*(total_mins) - 2*(total_mins/10);

		// Gaining frames may mean that we gain more frames so we keep adjusting until no more to adjust
		while (extra_frames != extra_frames2) {
			l_frames += extra_frames2 - extra_frames;
			extra_frames = extra_frames2;
			
			total_mins = l_frames / fps_div / 60;
			extra_frames2 = 2*(total_mins) - 2*(total_mins/10);
		}
	}
	
	// convert frames to seconds leaving the remainder as frames
	*seconds = l_frames / fps_div;		// DIV
	*frames = l_frames % fps_div;		// MOD

	// Normalise to Hours Minutes Seconds Frames
	*minutes = *seconds / 60;
	*seconds = *seconds % 60;

	*hours = *minutes / 60;
	*minutes = *minutes % 60;

	return B_OK;
}

status_t timecode_to_frames(int hours, int minutes, int seconds, int frames, int32 * l_frames, const timecode_info * code = NULL)
{
	int fps_div;
	int total_mins;
	int32 extra_frames;

	CALLED();
	
	if (code) {
		// We have a valid timecode_info
		fps_div = code->fps_div;

		total_mins = (hours * 60) + minutes;
		*l_frames = (total_mins * 60) + seconds;
		*l_frames = (*l_frames * fps_div) + frames;

		// Adjust "every_nth" minute we lose "drop_frames" "except_nth" minute
		if (code->every_nth > 0) {
			extra_frames = code->drop_frames*(total_mins/code->every_nth) - code->drop_frames*(total_mins/code->except_nth);
			
			*l_frames = *l_frames - extra_frames;
		}
	} else {

		total_mins = (hours * 60) + minutes;
		*l_frames = (total_mins * 60) + seconds;
		*l_frames = (*l_frames * 30) + frames;

		// Adjust "every_nth" minute we lose "drop_frames" "except_nth" minute
		extra_frames = 2*(total_mins) - 2*(total_mins/10);

		*l_frames = *l_frames - extra_frames;
	}
	
	return B_OK;
}

status_t get_timecode_description(timecode_type type, timecode_info * out_timecode)
{
	CALLED();

	out_timecode->type = type;
	strncpy(out_timecode->format,"%.2ih:%.2im:%.2is.%.2i",31);
	out_timecode->every_nth = 0;
	out_timecode->except_nth = 0;

	switch (type) {
		case	B_TIMECODE_100:
				strncpy(out_timecode->name,"100 FPS",31);
				out_timecode->fps_div = 100;
				break;
		case	B_TIMECODE_75:			// CD 
				strncpy(out_timecode->name,"CD",31);
				out_timecode->fps_div = 75;
				break;
		case	B_TIMECODE_30:			// MIDI
				strncpy(out_timecode->name,"MIDI",31);
				out_timecode->fps_div = 30;
				break;
		case	B_TIMECODE_30_DROP_2:	// NTSC
				strncpy(out_timecode->name,"NTSC",31);
				out_timecode->fps_div = 30;	// This is supposed to be 29.97fps but can be simulated using the drop frame format below
				out_timecode->drop_frames = 2; // Drop 2 frames
				out_timecode->every_nth = 1;	// every 1 minutes
				out_timecode->except_nth = 10;	// except every 10 minutes
				break;
		case	B_TIMECODE_30_DROP_4:	// Brazil
				strncpy(out_timecode->name,"NTSC Brazil",31);
				out_timecode->fps_div = 30;
				out_timecode->drop_frames = 4; // Drop 4 frames
				out_timecode->every_nth = 1;	// every 1 minutes
				out_timecode->except_nth = 10;	// except every 10 minutes
				break;
		case	B_TIMECODE_25:			// PAL
				strncpy(out_timecode->name,"PAL",31);
				out_timecode->fps_div = 25;
				break;
		case	B_TIMECODE_24:			// Film
				strncpy(out_timecode->name,"FILM",31);
				out_timecode->fps_div = 24;
				break;
		case	B_TIMECODE_18:			// Super8
				strncpy(out_timecode->name,"Super 8",31);
				out_timecode->fps_div = 18;
				break;
		default:
				strncpy(out_timecode->name,"NTSC",31);
				out_timecode->fps_div = 30;	// This is supposed to be 29.97fps but can be simulated using the drop frame format below
				out_timecode->drop_frames = 2; // Drop 2 frames
				out_timecode->every_nth = 1;	// every 1 minutes
				out_timecode->except_nth = 10;	// except every 10 minutes
				break;
	}
	
	return B_OK;
}

status_t count_timecodes()
{
	CALLED();
	return 8;	// Is this right?
}

/*************************************************************
 * public BTimeCode
 *************************************************************/


BTimeCode::BTimeCode()
{
	CALLED();
}


BTimeCode::BTimeCode(bigtime_t us,
					 timecode_type type)
{
	CALLED();
	if (SetType(type) == B_OK) {
		SetMicroseconds(us);
	}
}


BTimeCode::BTimeCode(const BTimeCode &clone)
{
	CALLED();
	if (SetType(clone.Type()) == B_OK) {
		SetData(clone.Hours(),clone.Minutes(),clone.Seconds(),clone.Frames());
	}
}


BTimeCode::BTimeCode(int hours,
					 int minutes,
					 int seconds,
					 int frames,
					 timecode_type type)
{
	CALLED();
	if (SetType(type) == B_OK) {
		SetData(hours,minutes,seconds,frames);
	}
}


BTimeCode::~BTimeCode()
{
	CALLED();
}


void
BTimeCode::SetData(int hours,
				   int minutes,
				   int seconds,
				   int frames)
{
	CALLED();
	m_hours = hours;
	m_minutes = minutes;
	m_seconds = seconds;
	m_frames = frames;
}


status_t
BTimeCode::SetType(timecode_type type)
{
	CALLED();
	
	return get_timecode_description(type, &m_info);
}


void
BTimeCode::SetMicroseconds(bigtime_t us)
{
	CALLED();
	
	us_to_timecode(us, &m_hours, &m_minutes, &m_seconds, &m_frames, &m_info);
}


void
BTimeCode::SetLinearFrames(int32 linear_frames)
{
	CALLED();
	
	frames_to_timecode(linear_frames, &m_hours, &m_minutes, &m_seconds, &m_frames, &m_info);
}


BTimeCode &
BTimeCode::operator=(const BTimeCode &clone)
{
	CALLED();
	m_hours = clone.Hours();
	m_minutes = clone.Minutes();
	m_seconds = clone.Seconds();
	m_frames = clone.Frames();
	SetType(clone.Type());
	
	return *this;
}


bool
BTimeCode::operator==(const BTimeCode &other) const
{
	CALLED();

	return ((this->LinearFrames()) == (other.LinearFrames()));
}


bool
BTimeCode::operator<(const BTimeCode &other) const
{
	CALLED();

	return ((this->LinearFrames()) < (other.LinearFrames()));
}


BTimeCode &
BTimeCode::operator+=(const BTimeCode &other)
{
	CALLED();
	
	this->SetLinearFrames(this->LinearFrames() + other.LinearFrames());
	
	return *this;
}


BTimeCode &
BTimeCode::operator-=(const BTimeCode &other)
{
	CALLED();
	
	this->SetLinearFrames(this->LinearFrames() - other.LinearFrames());
	
	return *this;
}


BTimeCode
BTimeCode::operator+(const BTimeCode &other) const
{
	CALLED();
	BTimeCode tc(*this);
	tc += other;
	return tc;
}


BTimeCode
BTimeCode::operator-(const BTimeCode &other) const
{
	CALLED();
	BTimeCode tc(*this);
	tc -= other;
	return tc;
}


int
BTimeCode::Hours() const
{
	CALLED();

	return m_hours;
}


int
BTimeCode::Minutes() const
{
	CALLED();

	return m_minutes;
}


int
BTimeCode::Seconds() const
{
	CALLED();

	return m_seconds;
}


int
BTimeCode::Frames() const
{
	CALLED();

	return m_frames;
}


timecode_type
BTimeCode::Type() const
{
	CALLED();

	return m_info.type;
}


void
BTimeCode::GetData(int *out_hours,
				   int *out_minutes,
				   int *out_seconds,
				   int *out_frames,
				   timecode_type *out_type) const
{
	CALLED();
	*out_hours = m_hours;
	*out_minutes = m_minutes;
	*out_seconds = m_seconds;
	*out_frames = m_frames;
	*out_type = m_info.type;
}


bigtime_t
BTimeCode::Microseconds() const
{
	bigtime_t us;

	CALLED();

	if (timecode_to_us(m_hours,m_minutes,m_seconds,m_frames, &us, &m_info) == B_OK) {
		return us;
	}
	
	return 0;
}


int32
BTimeCode::LinearFrames() const
{
	int32 linear_frames;

	CALLED();
	
	if (timecode_to_frames(m_hours,m_minutes,m_seconds,m_frames,&linear_frames,&m_info) == B_OK) {
		return linear_frames;
	}

	return 0;	
}


void
BTimeCode::GetString(char *str) const
{
	CALLED();
	sprintf(str,m_info.format, m_hours, m_minutes, m_seconds, m_frames);
}
