/** 
 * @file llfloaterpreference.cpp
 * @brief Global preferences with and without persistence.
 *
 * $LicenseInfo:firstyear=2002&license=viewergpl$
 * 
 * Copyright (c) 2002-2009, Linden Research, Inc.
 * 
 * Second Life Viewer Source Code
 * The source code in this file ("Source Code") is provided by Linden Lab
 * to you under the terms of the GNU General Public License, version 2.0
 * ("GPL"), unless you have obtained a separate licensing agreement
 * ("Other License"), formally executed by you and Linden Lab.  Terms of
 * the GPL can be found in doc/GPL-license.txt in this distribution, or
 * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
 * 
 * There are special exceptions to the terms and conditions of the GPL as
 * it is applied to this Source Code. View the full text of the exception
 * in the file doc/FLOSS-exception.txt in this software distribution, or
 * online at
 * http://secondlifegrid.net/programs/open_source/licensing/flossexception
 * 
 * By copying, modifying or distributing this software, you acknowledge
 * that you have read and understood your obligations described above,
 * and agree to abide by those obligations.
 * 
 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
 * COMPLETENESS OR PERFORMANCE.
 * $/LicenseInfo$
 */

/*
 * App-wide preferences.  Note that these are not per-user,
 * because we need to load many preferences before we have
 * a login name.
 */

#include "llviewerprecompiledheaders.h"

#include "llfloaterpreference.h"

#include "llbutton.h"
#include "llcheckboxctrl.h"
#include "lldir.h"
#include "llfocusmgr.h"
#include "llscrollbar.h"
#include "llspinctrl.h"
#include "message.h"

#include "impprefsfonts.h"
#include "llcommandhandler.h"
#include "llfloaterpreference.h"
#include "llpanelnetwork.h"
#include "llpanelaudioprefs.h"
#include "llpaneldisplay.h"
#include "llpaneldebug.h"
#include "llpanelgeneral.h"
#include "llpanelinput.h"
#include "llpanellogin.h"
#include "llpanelLCD.h"
#include "llpanelmsgs.h"
#include "llpanelskins.h"
#include "llprefsadvanced.h"
#include "llprefschat.h"
#include "llprefscolors.h"
#include "llprefsvoice.h"
#include "llprefsim.h"
#include "llresizehandle.h"
#include "llresmgr.h"
#include "llassetstorage.h"
#include "llagent.h"
#include "llviewercontrol.h"
#include "llviewernetwork.h"
#include "lluictrlfactory.h"
#include "llviewerwindow.h"
#include "llkeyboard.h"
#include "llscrollcontainer.h"
#include "llfloaterhardwaresettings.h"

const S32 PREF_BORDER = 4;
const S32 PREF_PAD = 5;
const S32 PREF_BUTTON_WIDTH = 70;
const S32 PREF_CATEGORY_WIDTH = 150;

const S32 PREF_FLOATER_MIN_HEIGHT = 2 * SCROLLBAR_SIZE + 2 * LLPANEL_BORDER_WIDTH + 96;

LLFloaterPreference* LLFloaterPreference::sInstance = NULL;


class LLPreferencesHandler : public LLCommandHandler
{
public:
	// requires trusted browser
	LLPreferencesHandler() : LLCommandHandler("preferences", true) { }
	bool handle(const LLSD& tokens, const LLSD& query_map,
				LLMediaCtrl* web)
	{
		LLFloaterPreference::show(NULL);
		return true;
	}
};

LLPreferencesHandler gPreferencesHandler;


// Must be done at run time, not compile time. JC
S32 pref_min_width()
{
	return  
	2 * PREF_BORDER + 
	2 * PREF_BUTTON_WIDTH + 
	PREF_PAD + RESIZE_HANDLE_WIDTH +
	PREF_CATEGORY_WIDTH +
	PREF_PAD;
}

S32 pref_min_height()
{
	return
	2 * PREF_BORDER +
	3*(BTN_HEIGHT + PREF_PAD) +
	PREF_FLOATER_MIN_HEIGHT;
}


LLPreferenceCore::LLPreferenceCore(LLTabContainer* tab_container, LLButton * default_btn) :
	mTabContainer(tab_container),
	mGeneralPanel(NULL),
	mInputPanel(NULL),
	mNetworkPanel(NULL),
	mDisplayPanel(NULL),
	mAudioPanel(NULL),
	mMsgPanel(NULL),
	mSkinsPanel(NULL),
	mPrefsColors(NULL),
	mLCDPanel(NULL),
	mPrefsFonts(NULL),
	mPrefsAdvanced(NULL)
{
	mGeneralPanel = new LLPanelGeneral();
	mTabContainer->addTabPanel(mGeneralPanel, mGeneralPanel->getLabel(), FALSE, onTabChanged, mTabContainer);
	mGeneralPanel->setDefaultBtn(default_btn);

	mInputPanel = new LLPanelInput();
	mTabContainer->addTabPanel(mInputPanel, mInputPanel->getLabel(), FALSE, onTabChanged, mTabContainer);
	mInputPanel->setDefaultBtn(default_btn);

	mNetworkPanel = new LLPanelNetwork();
	mTabContainer->addTabPanel(mNetworkPanel, mNetworkPanel->getLabel(), FALSE, onTabChanged, mTabContainer);
	mNetworkPanel->setDefaultBtn(default_btn);

	mDisplayPanel = new LLPanelDisplay();
	mTabContainer->addTabPanel(mDisplayPanel, mDisplayPanel->getLabel(), FALSE, onTabChanged, mTabContainer);
	mDisplayPanel->setDefaultBtn(default_btn);

	mAudioPanel = new LLPanelAudioPrefs();
	mTabContainer->addTabPanel(mAudioPanel, mAudioPanel->getLabel(), FALSE, onTabChanged, mTabContainer);
	mAudioPanel->setDefaultBtn(default_btn);

	mPrefsChat = new LLPrefsChat();
	mTabContainer->addTabPanel(mPrefsChat->getPanel(), mPrefsChat->getPanel()->getLabel(), FALSE, onTabChanged, mTabContainer);
	mPrefsChat->getPanel()->setDefaultBtn(default_btn);

	mPrefsIM = new LLPrefsIM();
	mTabContainer->addTabPanel(mPrefsIM->getPanel(), mPrefsIM->getPanel()->getLabel(), FALSE, onTabChanged, mTabContainer);
	mPrefsIM->getPanel()->setDefaultBtn(default_btn);
	
	mPrefsVoice = new LLPrefsVoice();
	mTabContainer->addTabPanel(mPrefsVoice, mPrefsVoice->getLabel(), FALSE, onTabChanged, mTabContainer);
	mPrefsVoice->setDefaultBtn(default_btn);


#if LL_LCD_COMPILE

	// only add this option if we actually have a logitech keyboard / speaker set
	if (gLcdScreen->Enabled())
	{
		mLCDPanel = new LLPanelLCD();
		mTabContainer->addTabPanel(mLCDPanel, mLCDPanel->getLabel(), FALSE, onTabChanged, mTabContainer);
		mLCDPanel->setDefaultBtn(default_btn);
	}

#else
	mLCDPanel = NULL;
#endif

	mMsgPanel = new LLPanelMsgs();
	mTabContainer->addTabPanel(mMsgPanel, mMsgPanel->getLabel(), FALSE, onTabChanged, mTabContainer);
	mMsgPanel->setDefaultBtn(default_btn);

	mPrefsColors = new LLPrefsColors();
	mTabContainer->addTabPanel(mPrefsColors, mPrefsColors->getLabel(), FALSE, onTabChanged, mTabContainer);
	mPrefsColors->setDefaultBtn(default_btn);
	
	mSkinsPanel = new LLPanelSkins();
	mTabContainer->addTabPanel(mSkinsPanel, mSkinsPanel->getLabel(), FALSE, onTabChanged, mTabContainer);
	mSkinsPanel->setDefaultBtn(default_btn);

	mPrefsFonts = new ImpPrefsFonts();
	mTabContainer->addTabPanel(mPrefsFonts, mPrefsFonts->getLabel(), FALSE, onTabChanged, mTabContainer);
	mPrefsFonts->setDefaultBtn(default_btn);

	mPrefsAdvanced = new LLPrefsAdvanced();
	mTabContainer->addTabPanel(mPrefsAdvanced, mPrefsAdvanced->getLabel(), FALSE, onTabChanged, mTabContainer);
	mPrefsAdvanced->setDefaultBtn(default_btn);

	if (!mTabContainer->selectTab(gSavedSettings.getS32("LastPrefTab")))
	{
		mTabContainer->selectFirstTab();
	}
}

LLPreferenceCore::~LLPreferenceCore()
{
	if (mGeneralPanel)
	{
		delete mGeneralPanel;
		mGeneralPanel = NULL;
	}
	if (mInputPanel)
	{
		delete mInputPanel;
		mInputPanel = NULL;
	}
	if (mNetworkPanel)
	{
		delete mNetworkPanel;
		mNetworkPanel = NULL;
	}
	if (mDisplayPanel)
	{
		delete mDisplayPanel;
		mDisplayPanel = NULL;
	}

	if (mAudioPanel)
	{
		delete mAudioPanel;
		mAudioPanel = NULL;
	}
	if (mPrefsChat)
	{
		delete mPrefsChat;
		mPrefsChat = NULL;
	}
	if (mPrefsIM)
	{
		delete mPrefsIM;
		mPrefsIM = NULL;
	}
	if (mMsgPanel)
	{
		delete mMsgPanel;
		mMsgPanel = NULL;
	}
	if (mSkinsPanel)
	{
		delete mSkinsPanel;
		mSkinsPanel = NULL;
	}
	if (mPrefsAdvanced)
	{
		delete mPrefsAdvanced;
		mPrefsAdvanced = NULL;
	}
	if (mPrefsFonts)
	{
		delete mPrefsFonts;
		mPrefsFonts = NULL;
	}
	if (mPrefsColors)
	{
		delete mPrefsColors;
		mPrefsColors = NULL;
	}
}


void LLPreferenceCore::apply()
{
	mGeneralPanel->apply();
	mInputPanel->apply();
	mNetworkPanel->apply();
	mDisplayPanel->apply();
	mAudioPanel->apply();
	mPrefsChat->apply();
	mPrefsVoice->apply();
	mPrefsIM->apply();
	mMsgPanel->apply();
	mSkinsPanel->apply();
	mPrefsAdvanced->apply();
	mPrefsFonts->apply();
	mPrefsColors->apply();

	// hardware menu apply
	LLFloaterHardwareSettings::instance()->apply();

#if LL_LCD_COMPILE
	// only add this option if we actually have a logitech keyboard / speaker set
	if (gLcdScreen->Enabled())
	{
		mLCDPanel->apply();
	}
#endif

	// Sims always wants us to send IMViaEMail and DirectoryVisible in the same msg or we crash, they're evil like that
	// We only know both these values after mPrefsChat and mPrefsIM have been applied -- MC
	if (mPrefsChat->getUpdateUserInfo() || mPrefsIM->getUpdateUserInfo())
	{
		bool new_im_via_email = mPrefsChat->getIMViaEmail();
		std::string directory_visibility = mPrefsIM->getDirectoryVis();

		LLMessageSystem* msg = gMessageSystem;
		msg->newMessageFast(_PREHASH_UpdateUserInfo);
		msg->nextBlockFast(_PREHASH_AgentData);
		msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
		msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
		msg->nextBlockFast(_PREHASH_UserData);
		msg->addBOOLFast(_PREHASH_IMViaEMail, new_im_via_email);	 	 
		msg->addString("DirectoryVisibility", directory_visibility);
		gAgent.sendReliableMessage();
	}
}


void LLPreferenceCore::cancel()
{
	mGeneralPanel->cancel();
	mInputPanel->cancel();
	mNetworkPanel->cancel();
	mDisplayPanel->cancel();
	mAudioPanel->cancel();
	mPrefsChat->cancel();
	mPrefsVoice->cancel();
	mPrefsIM->cancel();
	mMsgPanel->cancel();
	mSkinsPanel->cancel();
	mPrefsAdvanced->cancel();
	mPrefsFonts->cancel();
	mPrefsColors->cancel();

	// cancel hardware menu
	LLFloaterHardwareSettings::instance()->cancel();

#if LL_LCD_COMPILE
	// only add this option if we actually have a logitech keyboard / speaker set
	if (gLcdScreen->Enabled())
	{
		mLCDPanel->cancel();
	}
#endif
}

// static
void LLPreferenceCore::onTabChanged(void* user_data, bool from_click)
{
	LLTabContainer* self = (LLTabContainer*)user_data;

	gSavedSettings.setS32("LastPrefTab", self->getCurrentPanelIndex());
}


void LLPreferenceCore::setPersonalInfo(const std::string& visibility, bool im_via_email, const std::string& email)
{
	mPrefsIM->setPersonalInfo(visibility);
	mPrefsChat->setPersonalInfo(im_via_email, email);
}

void LLPreferenceCore::updateIsLoggedIn(bool enable)
{
	mPrefsIM->preparePerAccountPrefs(enable);
	mAudioPanel->updateIsLoggedIn(enable);
}

void LLPreferenceCore::refreshEnabledGraphics()
{
	LLFloaterHardwareSettings::instance()->refreshEnabledState();
	mDisplayPanel->refreshEnabledState();
}

//////////////////////////////////////////////
// LLFloaterPreference

LLFloaterPreference::LLFloaterPreference()
{
	LLUICtrlFactory::getInstance()->buildFloater(this, "floater_preferences.xml");
}

BOOL LLFloaterPreference::postBuild()
{
	requires<LLButton>("OK");
	requires<LLButton>("Cancel");
	requires<LLButton>("Apply");
	requires<LLTabContainer>("pref core");

	if (!checkRequirements())
	{
		return FALSE;
	}
	
	mApplyBtn = getChild<LLButton>("Apply");
	mApplyBtn->setClickedCallback(onBtnApply, this);
		
	mCancelBtn = getChild<LLButton>("Cancel");
	mCancelBtn->setClickedCallback(onBtnCancel, this);

	mOKBtn = getChild<LLButton>("OK");
	mOKBtn->setClickedCallback(onBtnOK, this);

	childSetAction("reset_btn", onClickResetPrefs, this);
			
	mPreferenceCore = new LLPreferenceCore(
		getChild<LLTabContainer>("pref core"),
		getChild<LLButton>("OK")
		);
	
	sInstance = this;

	return TRUE;
}


LLFloaterPreference::~LLFloaterPreference()
{
	sInstance = NULL;
	delete mPreferenceCore;
}

void LLFloaterPreference::apply()
{
	this->mPreferenceCore->apply();
}


void LLFloaterPreference::cancel()
{
	this->mPreferenceCore->cancel();
}


// static
void LLFloaterPreference::show(void*)
{
	if (!sInstance)
	{
		new LLFloaterPreference();
		sInstance->center();
	}

	sInstance->open();		/* Flawfinder: ignore */

	if(!gAgent.getID().isNull())
	{
		// we're logged in, so we can get this info.
		gMessageSystem->newMessageFast(_PREHASH_UserInfoRequest);
		gMessageSystem->nextBlockFast(_PREHASH_AgentData);
		gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
		gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
		gAgent.sendReliableMessage();
	}

	LLPanelLogin::setAlwaysRefresh(true);
}


// static
void LLFloaterPreference::onClickResetPrefs(void* user_data)
{
	LLFloaterPreference* self = (LLFloaterPreference*)user_data;
	LLNotifications::instance().add("ConfirmResetAllPreferences", LLSD(), LLSD(), boost::bind(callbackReset, _1, _2, self));
}

// static
bool LLFloaterPreference::callbackReset(const LLSD& notification, const LLSD& response, LLFloaterPreference *self)
{
	S32 option = LLNotification::getSelectedOption(notification, response);
	if ( option == 0 )
	{
		gSavedSettings.setBOOL("ResetAllPreferences", TRUE);
	}
	return false;
}


// static 
void LLFloaterPreference::onBtnOK( void* userdata )
{
	LLFloaterPreference *fp =(LLFloaterPreference *)userdata;
	if (fp)
	{
		// commit any outstanding text entry
		if (fp->hasFocus())
		{
			LLUICtrl* cur_focus = dynamic_cast<LLUICtrl*>(gFocusMgr.getKeyboardFocus());
			if (cur_focus->acceptsTextInput())
			{
				cur_focus->onCommit();
			}
		}

		if (fp->canClose())
		{
			fp->apply();
			fp->close(false);

			gSavedSettings.saveToFile( gSavedSettings.getString("ClientSettingsFile"), TRUE );
			
			std::string crash_settings_filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, CRASH_SETTINGS_FILE);
			// save all settings, even if equals defaults
			gCrashSettings.saveToFile(crash_settings_filename, FALSE);
		}
		else
		{
			// Show beep, pop up dialog, etc.
			llinfos << "Can't close preferences!" << llendl;
		}

		LLPanelLogin::refreshLocation( false );
	}
}


// static 
void LLFloaterPreference::onBtnApply( void* userdata )
{
	LLFloaterPreference *fp =(LLFloaterPreference *)userdata;
	if (fp)
	{
		if (fp->hasFocus())
		{
			LLUICtrl* cur_focus = dynamic_cast<LLUICtrl*>(gFocusMgr.getKeyboardFocus());
			if (cur_focus->acceptsTextInput())
			{
				cur_focus->onCommit();
			}
		}
		fp->apply();

		LLPanelLogin::refreshLocation( false );
	}
}


void LLFloaterPreference::onClose(bool app_quitting)
{
	LLPanelLogin::setAlwaysRefresh(false);
	cancel(); // will be a no-op if OK or apply was performed just prior.
	LLFloater::onClose(app_quitting);
}


// static 
void LLFloaterPreference::onBtnCancel( void* userdata )
{
	LLFloaterPreference *fp =(LLFloaterPreference *)userdata;
	if (fp)
	{
		if (fp->hasFocus())
		{
			LLUICtrl* cur_focus = dynamic_cast<LLUICtrl*>(gFocusMgr.getKeyboardFocus());
			if (cur_focus->acceptsTextInput())
			{
				cur_focus->onCommit();
			}
		}
		fp->close(); // side effect will also cancel any unsaved changes.
	}
}


// static
void LLFloaterPreference::updateUserInfo(const std::string& visibility, bool im_via_email, const std::string& email)
{
	if(sInstance && sInstance->mPreferenceCore)
	{
		sInstance->mPreferenceCore->setPersonalInfo(visibility, im_via_email, email);
	}
}

// static
void LLFloaterPreference::updateIsLoggedIn(bool enable)
{
	if(sInstance && sInstance->mPreferenceCore)
	{
		sInstance->mPreferenceCore->updateIsLoggedIn(enable);
	}
}

void LLFloaterPreference::refreshEnabledGraphics()
{
	sInstance->mPreferenceCore->refreshEnabledGraphics();
}