/** 
 *
 * Copyright (c) 2011-2012, Kitty Barnett
 * 
 * The source code in this file is provided to you under the terms of the 
 * GNU Lesser General Public License, version 2.1, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 
 * PARTICULAR PURPOSE. Terms of the LGPL can be found in doc/LGPL-licence.txt 
 * in this distribution, or online at http://www.gnu.org/licenses/lgpl-2.1.txt
 * 
 * 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.
 * 
 */

#include "llviewerprecompiledheaders.h"

#include "llagent.h"
#include "llcheckboxctrl.h"
#include "lldiriterator.h"
#include "llfloaterreg.h"
#include "llfolderview.h"
#include "llinventoryfunctions.h"
#include "llinventorymodel.h"
#include "llinventorypanel.h"
#include "llscrolllistctrl.h"
#include "lltrans.h"
#include "llviewerassettype.h"
#include "llviewerinventory.h"
#include "llviewerregion.h"

#include "llfloaterscriptrecover.h"

#include "llviewerassetupload.h"

const std::string NEW_LSL_NAME = "New Script";

std::string fixNewScriptDefaultName(const std::string& scriptName)
{
	std::string fixedScriptName = scriptName;
	if (scriptName == NEW_LSL_NAME)
	{
		LLStringUtil::replaceChar(fixedScriptName, ' ', '_');
	}
	return fixedScriptName;
}

// ============================================================================
// LLFloaterScriptRecover
//

LLFloaterScriptRecover::LLFloaterScriptRecover(const LLSD& sdKey)
	: LLFloater(sdKey)
{
}

void LLFloaterScriptRecover::onOpen(const LLSD& sdKey)
{
	LLScrollListCtrl* pListCtrl = findChild<LLScrollListCtrl>("script_list");

	LLSD sdBhvrRow; LLSD& sdBhvrColumns = sdBhvrRow["columns"];
	sdBhvrColumns[0] = LLSD().with("column", "script_check").with("type", "checkbox");
	sdBhvrColumns[1] = LLSD().with("column", "script_name").with("type", "text");

	pListCtrl->clearRows();
	for (LLSD::array_const_iterator itFile = sdKey["files"].beginArray(), endFile = sdKey["files"].endArray(); 
			itFile != endFile;  ++itFile)
	{
		const LLSD& sdFile = *itFile;

		sdBhvrRow["value"] = sdFile;
		sdBhvrColumns[0]["value"] = true;
		sdBhvrColumns[1]["value"] = sdFile["name"];

		pListCtrl->addElement(sdBhvrRow, ADD_BOTTOM);
	}
}

BOOL LLFloaterScriptRecover::postBuild()
{
	findChild<LLUICtrl>("recover_btn")->setCommitCallback(boost::bind(&LLFloaterScriptRecover::onBtnRecover, this));
	findChild<LLUICtrl>("cancel_btn")->setCommitCallback(boost::bind(&LLFloaterScriptRecover::onBtnCancel, this));

	return TRUE;
}

void LLFloaterScriptRecover::onBtnCancel()
{
	LLScrollListCtrl* pListCtrl = findChild<LLScrollListCtrl>("script_list");

	// Delete all listed files
	std::vector<LLScrollListItem*> items = pListCtrl->getAllData();
	for (std::vector<LLScrollListItem*>::const_iterator itItem = items.begin(); itItem != items.end(); ++itItem)
	{
		LLFile::remove((*itItem)->getValue().asString());
	}

	closeFloater();
}

void LLFloaterScriptRecover::onBtnRecover()
{
	LLScrollListCtrl* pListCtrl = findChild<LLScrollListCtrl>("script_list");

	// Recover all selected, delete any unselected
	std::vector<LLScrollListItem*> items = pListCtrl->getAllData(); LLSD sdFiles;
	for (std::vector<LLScrollListItem*>::const_iterator itItem = items.begin(); itItem != items.end(); ++itItem)
	{
		LLScrollListCheck* pCheckColumn = dynamic_cast<LLScrollListCheck*>((*itItem)->getColumn(0));
		if (!pCheckColumn)
			continue;

		const LLSD sdFile = (*itItem)->getValue();
		if (pCheckColumn->getCheckBox()->getValue().asBoolean())
			sdFiles.append(sdFile);
		else
			LLFile::remove(sdFile["path"]);
	}

	if (!sdFiles.emptyArray())
		new LLScriptRecoverQueue(sdFiles);

	closeFloater();
}

// ============================================================================
// LLCreateRecoverScriptCallback
//

class LLCreateRecoverScriptCallback : public LLInventoryCallback
{
public:
	LLCreateRecoverScriptCallback(LLScriptRecoverQueue* pRecoverQueue)
		: LLInventoryCallback(), mRecoverQueue(pRecoverQueue)
	{
	}

	void fire(const LLUUID& idItem)
	{
		mRecoverQueue->onCreateScript(idItem);
	}

protected:
	LLScriptRecoverQueue* mRecoverQueue;
};

// ============================================================================
// LLScriptRecoverQueue
//

// static
void LLScriptRecoverQueue::recoverIfNeeded()
{
	const std::string strTempPath = LLFile::tmpdir();
	LLSD sdData, &sdFiles = sdData["files"];

	LLDirIterator itFiles(strTempPath, "*.lslbackup"); std::string strFilename;
	while (itFiles.next(strFilename))
	{
		// Build a friendly name for the file
		std::string strName = gDirUtilp->getBaseFileName(strFilename, true);

		std::string agent_id_str = gAgentID.asString();
		LLStringUtil::replaceString(agent_id_str, "-", "");
		std::string::size_type agent_delim_offset = strName.find_first_of("_");
		if (agent_delim_offset != std::string::npos && agent_delim_offset != 0)
		{
			std::string file_agent_id_str = strName.substr(0, agent_delim_offset);
			if (file_agent_id_str != agent_id_str)
			{
				continue;
			}
			else
			{
				strName.erase(0, agent_delim_offset + 1);
			}
		}

		std::string::size_type offset = strName.find_last_of("-");
		if ( (std::string::npos != offset) && (offset != 0) && (offset == strName.length() - 9))
			strName.erase(strName.length() - 9);

		LLStringUtil::trim(strName);
		if (0 == strName.length())
			strName = LLTrans::getString("unknown_script");

		sdFiles.append(LLSD().with("path", strTempPath + strFilename).with("name", strName));
	}

	LL_INFOS() << "Checking for recoverable scripts. Found: " << sdFiles.size() << LL_ENDL;

	if (sdFiles.size())
		LLFloaterReg::showInstance("script_recover", sdData);
}

LLScriptRecoverQueue::LLScriptRecoverQueue(const LLSD& sdFiles)
{
	for (LLSD::array_const_iterator itFile = sdFiles.beginArray(), endFile = sdFiles.endArray(); itFile != endFile;  ++itFile)
	{
		const LLSD& sdFile = *itFile;
		if (LLFile::isfile(sdFile["path"]))
			m_FileQueue.insert(std::pair<std::string, LLSD>(sdFile["path"], sdFile));
	}
	recoverNext();
}

bool LLScriptRecoverQueue::recoverNext()
{
	/**
	 * Steps:
	 *  (1) create a script inventory item under Lost and Found
	 *  (2) once we have the item's UUID we can upload the script
	 *  (3) once the script is uploaded we move on to the next item
	 */
	const LLUUID idFNF = gInventory.findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND);

	// Sanity check - if the associated UUID is non-null then this file is already being processed
	filename_queue_t::const_iterator itFile = m_FileQueue.begin();
	while ( (itFile != m_FileQueue.end()) && (itFile->second.has("item")) && (itFile->second["item"].asUUID().notNull()) )
		++itFile;

	if (m_FileQueue.end() == itFile) 
	{
		LLInventoryPanel* pInvPanel = LLInventoryPanel::getActiveInventoryPanel(TRUE);
		LLFolderViewFolder* pFVF = dynamic_cast<LLFolderViewFolder*>(pInvPanel ? pInvPanel->getItemByID(idFNF) : NULL);
		if (pFVF)
		{
			pFVF->setOpenArrangeRecursively(TRUE, LLFolderViewFolder::RECURSE_UP);
			pInvPanel->setSelection(idFNF, TRUE);
		}

		delete this;
		return false;
	}

	std::string strItemDescr;
	LLViewerAssetType::generateDescriptionFor(LLAssetType::AT_LSL_TEXT, strItemDescr);

	// Ansariel: Need to work around a bug preventing us from creating a
	//           script with the default name for new scripts
	std::string strScriptName = fixNewScriptDefaultName(itFile->second["name"].asString());

	create_inventory_item(gAgent.getID(), gAgent.getSessionID(), idFNF, LLTransactionID::tnull, 
	                      strScriptName, strItemDescr, LLAssetType::AT_LSL_TEXT, LLInventoryType::IT_LSL,
	                      NO_INV_SUBTYPE, PERM_MOVE | PERM_TRANSFER, new LLCreateRecoverScriptCallback(this));
	return true;
}

void LLScriptRecoverQueue::onCreateScript(const LLUUID& idItem)
{
	const LLViewerInventoryItem* pItem = gInventory.getItem(idItem);
	if (!pItem)
	{
		// TODO: error handling
		return;
	}

	std::string strFileName;
	std::string strFilePath;
	for (filename_queue_t::iterator itFile = m_FileQueue.begin(); itFile != m_FileQueue.end(); ++itFile)
	{
		if (fixNewScriptDefaultName(itFile->second["name"].asString()) != pItem->getName())
			continue;
		strFileName = itFile->second["path"].asString();
		itFile->second["item"] = idItem;
		strFilePath = itFile->first;
		break;
	}

	std::string strCapsUrl = gAgent.getRegionCapability("UpdateScriptAgent");

	if (!strCapsUrl.empty())
	{
		std::string buffer;
		llstat stat;
		if (0 == LLFile::stat(strFilePath, &stat) && stat.st_size > 0)
		{
			buffer.resize(stat.st_size);
			LLFILE *pFile = LLFile::fopen(strFileName, "rb");

			if (pFile)
			{
				size_t act_read = fread(&buffer[0], 1, stat.st_size, pFile);
				if (act_read != stat.st_size)
				{
					LL_WARNS() << "Incomplete read of " << strFilePath << ": File size = " << stat.st_size << " - bytes read: " << act_read << LL_ENDL;
					buffer = "";
				}
				LLFile::close(pFile);
			}
			else
			{
				buffer = "";
				LL_WARNS() << "Cannot open " << strFilePath << LL_ENDL;
			}
		}
		else
		{
			LL_WARNS() << "No access to " << strFilePath << LL_ENDL;
		}

		LLBufferedAssetUploadInfo::taskUploadFinish_f proc = boost::bind(&LLScriptRecoverQueue::onSavedScript, this, _1, _2, _3, _4);
		LLResourceUploadInfo::ptr_t uploadInfo(new LLScriptAssetUpload(idItem, buffer, proc));
		LLViewerAssetUpload::EnqueueInventoryUpload(strCapsUrl, uploadInfo);
	}
	else
	{
		LL_WARNS() << "Cannot recover script - no UpdateScriptAgent cap!" << LL_ENDL;
	}
}

void LLScriptRecoverQueue::onSavedScript(LLUUID itemId, LLUUID newAssetId, LLUUID newItemId, LLSD response)
{
	LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD( response[ LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS ] );

	filename_queue_t::iterator itFile = m_FileQueue.begin();
	while ( (itFile != m_FileQueue.end()) && ((!itFile->second.has("item")) || (itFile->second["item"].asUUID() != itemId)) )
		++itFile;

	if (itFile == m_FileQueue.end())
	{
		LL_WARNS() << "Unknown file" << LL_ENDL;
		return;
	}

	if( HTTP_OK == status.getType() )
	{
		LL_INFOS() << "Recovered script uploaded successfully" << LL_ENDL;

		LLPointer<LLViewerInventoryItem> pItem = gInventory.getItem(itemId);
		if (pItem.notNull())
		{
			// Ansariel: Rename back scripts with default name
			std::string strScriptName = itFile->second["name"].asString();
			if (strScriptName == NEW_LSL_NAME)
			{
				LLPointer<LLViewerInventoryItem> pNewItem = new LLViewerInventoryItem(pItem);
				pNewItem->rename(strScriptName);
				pNewItem->updateServer(FALSE);
				gInventory.updateItem(pNewItem);
				gInventory.notifyObservers();
			}
			
			LLFile::remove(itFile->first);
			m_FileQueue.erase(itFile);
		}
	}
	else
	{
		LL_WARNS() << "Recovered script upload FAILED" << LL_ENDL;

		LLViewerInventoryItem* pItem = gInventory.getItem( itemId );
		if (pItem)
			gInventory.changeItemParent(pItem, gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH), FALSE);
		m_FileQueue.erase(itFile);
	}
	recoverNext();
}

// ============================================================================
