#include <MediaDefs.h>
#include <Locker.h>
#include <Path.h>
#include <File.h>
#include <OS.h>

#include "MixerCore.h"
#include "MixerInput.h"
#include "MixerOutput.h"
#include "MixerSettings.h"
#include "debug.h"

#define SAVE_DELAY		5000000		// delay saving of settings for 5s
#define SAVE_RUNTIME	30000000	// stop save thread after 30s inactivity

#define SETTINGS_VERSION ((int32)0x94251601)

MixerSettings::MixerSettings()
 :	fLocker(new BLocker),
 	fSettingsFile(0),
	fSettingsDirty(false),
	fSettingsLastChange(0),
	fSaveThread(-1),
	fSaveThreadWaitSem(-1),
	fSaveThreadRunning(false)
{
	Load();
}

MixerSettings::~MixerSettings()
{
	StopDeferredSave();
	if (fSettingsDirty)
		Save();
	delete fLocker;
	delete fSettingsFile;
}

void
MixerSettings::SetSettingsFile(const char *file)
{
	fLocker->Lock();
	delete fSettingsFile;
	fSettingsFile = new BPath(file);
	fLocker->Unlock();
	Load();
}

bool
MixerSettings::AttenuateOutput()
{
	bool temp;
	fLocker->Lock();
	temp = fSettings.AttenuateOutput;
	fLocker->Unlock();
	return temp;
}

void
MixerSettings::SetAttenuateOutput(bool yesno)
{
	fLocker->Lock();
	fSettings.AttenuateOutput = yesno;
	fLocker->Unlock();
	StartDeferredSave();
}

bool
MixerSettings::NonLinearGainSlider()
{
	bool temp;
	fLocker->Lock();
	temp = fSettings.NonLinearGainSlider;
	fLocker->Unlock();
	return temp;
}

void
MixerSettings::SetNonLinearGainSlider(bool yesno)
{
	fLocker->Lock();
	fSettings.NonLinearGainSlider = yesno;
	fLocker->Unlock();
	StartDeferredSave();
}

bool
MixerSettings::UseBalanceControl()
{
	bool temp;
	fLocker->Lock();
	temp = fSettings.UseBalanceControl;
	fLocker->Unlock();
	return temp;
}

void
MixerSettings::SetUseBalanceControl(bool yesno)
{
	fLocker->Lock();
	fSettings.UseBalanceControl = yesno;
	fLocker->Unlock();
	StartDeferredSave();
}

bool
MixerSettings::AllowOutputChannelRemapping()
{
	bool temp;
	fLocker->Lock();
	temp = fSettings.AllowOutputChannelRemapping;
	fLocker->Unlock();
	return temp;
}

void
MixerSettings::SetAllowOutputChannelRemapping(bool yesno)
{
	fLocker->Lock();
	fSettings.AllowOutputChannelRemapping = yesno;
	fLocker->Unlock();
	StartDeferredSave();
}

bool
MixerSettings::AllowInputChannelRemapping()
{
	bool temp;
	fLocker->Lock();
	temp = fSettings.AllowInputChannelRemapping;
	fLocker->Unlock();
	return temp;
}

void
MixerSettings::SetAllowInputChannelRemapping(bool yesno)
{
	fLocker->Lock();
	fSettings.AllowInputChannelRemapping = yesno;
	fLocker->Unlock();
	StartDeferredSave();
}

int
MixerSettings::InputGainControls()
{
	int temp;
	fLocker->Lock();
	temp = fSettings.InputGainControls;
	fLocker->Unlock();
	return temp;
}

void
MixerSettings::SetInputGainControls(int value)
{
	fLocker->Lock();
	fSettings.InputGainControls = value;
	fLocker->Unlock();
	StartDeferredSave();
}

int
MixerSettings::ResamplingAlgorithm()
{
	int temp;
	fLocker->Lock();
	temp = fSettings.ResamplingAlgorithm;
	fLocker->Unlock();
	return temp;
}

void
MixerSettings::SetResamplingAlgorithm(int value)
{
	fLocker->Lock();
	fSettings.ResamplingAlgorithm = value;
	fLocker->Unlock();
	StartDeferredSave();
}

bool
MixerSettings::RefuseOutputFormatChange()
{
	bool temp;
	fLocker->Lock();
	temp = fSettings.RefuseOutputFormatChange;
	fLocker->Unlock();
	return temp;
}

void
MixerSettings::SetRefuseOutputFormatChange(bool yesno)
{
	fLocker->Lock();
	fSettings.RefuseOutputFormatChange = yesno;
	fLocker->Unlock();
	StartDeferredSave();
}

bool
MixerSettings::RefuseInputFormatChange()
{
	bool temp;
	fLocker->Lock();
	temp = fSettings.RefuseInputFormatChange;
	fLocker->Unlock();
	return temp;
}

void
MixerSettings::SetRefuseInputFormatChange(bool yesno)
{
	fLocker->Lock();
	fSettings.RefuseInputFormatChange = yesno;
	fLocker->Unlock();
	StartDeferredSave();
}

void
MixerSettings::SaveConnectionSettings(MixerInput *input)
{
	fLocker->Lock();
	int index = -1;
	
	// try to find matching name first
	for (int i = 0; i < MAX_INPUT_SETTINGS; i++) {
		if (fInputSetting[i].IsEmpty())
			continue;
		if (0 == strcmp(fInputSetting[i].FindString("name"), input->MediaInput().name)) {
			index = i;
			break;
		}
	}
	if (index == -1) {
		// try to find empty location
		for (int i = 0; i < MAX_INPUT_SETTINGS; i++) {
			if (fInputSetting[i].IsEmpty()) {
				index = i;
				break;
			}
		}
	}
	if (index == -1) {
		// find lru location
		index = 0;
		for (int i = 0; i < MAX_INPUT_SETTINGS; i++) {
			if (fInputSetting[i].FindInt64("lru") < fInputSetting[index].FindInt64("lru"))
				index = i;
		}
	}

	TRACE("SaveConnectionSettings: using entry %d\n", index);

	fInputSetting[index].MakeEmpty();
	fInputSetting[index].AddInt64("lru", system_time());
	fInputSetting[index].AddString("name", input->MediaInput().name);

	int count = input->GetInputChannelCount();
	fInputSetting[index].AddInt32("InputChannelCount", count);
	fInputSetting[index].AddBool("InputIsEnabled", input->IsEnabled());
	
	for (int i = 0; i < count; i++)
		fInputSetting[index].AddFloat("InputChannelGain", input->GetInputChannelGain(i));

	// XXX should save channel destinations and mixer channels

	fLocker->Unlock();

	StartDeferredSave();
}

void
MixerSettings::LoadConnectionSettings(MixerInput *input)
{
	fLocker->Lock();
	int index;
	for (index = 0; index < MAX_INPUT_SETTINGS; index++) {
		if (fInputSetting[index].IsEmpty())
			continue;
		if (0 == strcmp(fInputSetting[index].FindString("name"), input->MediaInput().name))
			break;
	}
	if (index == MAX_INPUT_SETTINGS) {
		TRACE("LoadConnectionSettings: entry not found\n");
		fLocker->Unlock();
		return;
	}
	
	TRACE("LoadConnectionSettings: found entry %d\n", index);
	
	int count = input->GetInputChannelCount();
	if (fInputSetting[index].FindInt32("InputChannelCount") == count) {
		for (int i = 0; i < count; i++)
			input->SetInputChannelGain(i, fInputSetting[index].FindFloat("InputChannelGain", i));
		input->SetEnabled(fInputSetting[index].FindBool("InputIsEnabled"));
	}
	
	// XXX should load channel destinations and mixer channels
		
	fInputSetting[index].ReplaceInt64("lru", system_time());
	
	fLocker->Unlock();
	
	StartDeferredSave();
}

void
MixerSettings::SaveConnectionSettings(MixerOutput *output)
{
	fLocker->Lock();
	
	fOutputSetting.MakeEmpty();
	
	int count = output->GetOutputChannelCount();
	fOutputSetting.AddInt32("OutputChannelCount", count);
	for (int i = 0; i < count; i++)
		fOutputSetting.AddFloat("OutputChannelGain", output->GetOutputChannelGain(i));
	fOutputSetting.AddBool("OutputIsMuted", output->IsMuted());
	
	// XXX should save channel sources and source gains
	
	fLocker->Unlock();
	
	StartDeferredSave();
}

void
MixerSettings::LoadConnectionSettings(MixerOutput *output)
{
	fLocker->Lock();
	
	int count = output->GetOutputChannelCount();
	if (fOutputSetting.FindInt32("OutputChannelCount") == count) {
		for (int i = 0; i < count; i++)
			output->SetOutputChannelGain(i, fOutputSetting.FindFloat("OutputChannelGain", i));
		output->SetMuted(fOutputSetting.FindBool("OutputIsMuted"));
	}
	
	// XXX should load channel sources and source gains
		
	fLocker->Unlock();
}

void
MixerSettings::Save()
{
	fLocker->Lock();
	// if we don't have a settings file, don't continue
	if (!fSettingsFile) {
		fLocker->Unlock();
		return;
	}
	TRACE("MixerSettings: SAVE!\n");
	
	BMessage msg;
	msg.AddInt32("version", SETTINGS_VERSION);
	msg.AddData("settings", B_RAW_TYPE, (void *)&fSettings, sizeof(fSettings));
	msg.AddMessage("output", &fOutputSetting);
	for (int i = 0; i < MAX_INPUT_SETTINGS; i++)
		msg.AddMessage("input", &fInputSetting[i]);
		
	char *buffer;
    size_t length; 
    
    length = msg.FlattenedSize();
    buffer = new char [length];
    msg.Flatten(buffer, length);

	BFile file(fSettingsFile->Path(), B_READ_WRITE | B_CREATE_FILE);
	file.Write(buffer, length);
	
   	delete [] buffer;
		
	fSettingsDirty = false;
	fLocker->Unlock();
}

void
MixerSettings::Load()
{
	fLocker->Lock();
	// setup defaults
	fSettings.AttenuateOutput = true;
	fSettings.NonLinearGainSlider = true;
	fSettings.UseBalanceControl = false;
	fSettings.AllowOutputChannelRemapping = false;
	fSettings.AllowInputChannelRemapping = false;
	fSettings.InputGainControls = 0;
	fSettings.ResamplingAlgorithm = 0;
	fSettings.RefuseOutputFormatChange = true;
	fSettings.RefuseInputFormatChange = true;
	
	// if we don't have a settings file, don't continue
	if (!fSettingsFile) {
		fLocker->Unlock();
		return;
	}
	
	BFile file(fSettingsFile->Path(), B_READ_WRITE);
	off_t size = 0;
	file.GetSize(&size);
	if (size == 0) {
		fLocker->Unlock();
		TRACE("MixerSettings: no settings file\n");
		return;
	}

	char * buffer = new char[size];
	if (size != file.Read(buffer, size)) {
		delete [] buffer;
		fLocker->Unlock();
		TRACE("MixerSettings: can't read settings file\n");
		return;
	}

	BMessage msg;
	if (B_OK != msg.Unflatten(buffer)) {
		delete [] buffer;
		fLocker->Unlock();
		TRACE("MixerSettings: can't unflatten settings\n");
		return;
	}

	delete [] buffer;

	if (msg.FindInt32("version") != SETTINGS_VERSION) {
		fLocker->Unlock();
		TRACE("MixerSettings: settings have wrong version\n");
		return;
	}
	
	const void *data;
	ssize_t datasize = 0;
	
	msg.FindData("settings", B_RAW_TYPE, &data, &datasize);
	if (datasize != sizeof(fSettings)) {
		fLocker->Unlock();
		TRACE("MixerSettings: settings have wrong size\n");
		return;
	}
	memcpy((void *)&fSettings, data, sizeof(fSettings));
	
	msg.FindMessage("output", &fOutputSetting);
	for (int i = 0; i < MAX_INPUT_SETTINGS; i++)
		msg.FindMessage("input", i, &fInputSetting[i]);
	
	fLocker->Unlock();
}

void
MixerSettings::StartDeferredSave()
{
	fLocker->Lock();
	
	// if we don't have a settings file, don't save the settings
	if (!fSettingsFile) {
		fLocker->Unlock();
		return;
	}

	fSettingsDirty = true;
	fSettingsLastChange = system_time();
	
	if (fSaveThreadRunning) {
		fLocker->Unlock();
		return;
	}
	
	StopDeferredSave();
	
	ASSERT(fSaveThreadWaitSem < 0);
	fSaveThreadWaitSem = create_sem(0, "save thread wait");
	if (fSaveThreadWaitSem < B_OK) {
		ERROR("MixerSettings: can't create semaphore\n");
		Save();
		fLocker->Unlock();
		return;
	}
	ASSERT(fSaveThread < 0);
	fSaveThread = spawn_thread(_save_thread_, "Attack of the Killer Tomatoes", 7, this);
	if (fSaveThread < B_OK) {
		ERROR("MixerSettings: can't spawn thread\n");
		delete_sem(fSaveThreadWaitSem);
		fSaveThreadWaitSem = -1;
		Save();
		fLocker->Unlock();
		return;
	}
	resume_thread(fSaveThread);
	
	fSaveThreadRunning = true;

	fLocker->Unlock();
}

void
MixerSettings::StopDeferredSave()
{
	fLocker->Lock();

	if (fSaveThread > 0) {
		ASSERT(fSaveThreadWaitSem > 0);

		status_t unused;
		delete_sem(fSaveThreadWaitSem);
		wait_for_thread(fSaveThread, &unused);
		
		fSaveThread = -1;
		fSaveThreadWaitSem = -1;
	}
	
	fLocker->Unlock();
}

void
MixerSettings::SaveThread()
{
	bigtime_t timeout;
	status_t rv;
	
	TRACE("MixerSettings: save thread started\n");

	fLocker->Lock();
	timeout = fSettingsLastChange + SAVE_DELAY;
	fLocker->Unlock();
	
	for (;;) {
		rv = acquire_sem_etc(fSaveThreadWaitSem, 1, B_ABSOLUTE_TIMEOUT, timeout);
		if (rv == B_INTERRUPTED)
			continue;
		if (rv != B_TIMED_OUT && rv < B_OK)
			break;
		if (B_OK != fLocker->LockWithTimeout(200000))
			continue;
			
		TRACE("MixerSettings: save thread running\n");
			
		bigtime_t delta = system_time() - fSettingsLastChange;
			
		if (fSettingsDirty && delta > SAVE_DELAY) {
			Save();
		}

		if (delta > SAVE_RUNTIME) {
			fSaveThreadRunning = false;
			fLocker->Unlock();
			break;
		}

		timeout = system_time() + SAVE_DELAY;
		fLocker->Unlock();
	}
	
	TRACE("MixerSettings: save thread ended\n");
}
	
int32
MixerSettings::_save_thread_(void *arg)
{
	static_cast<MixerSettings *>(arg)->SaveThread();
	return 0;
}
