/** 
 * @file llpanelgroupnotices.cpp
 * @brief A panel to display group notices.
 *
 * $LicenseInfo:firstyear=2006&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "llviewerprecompiledheaders.h"

#include "llpanelgroupnotices.h"

#include "llview.h"

#include "llavatarnamecache.h"
#include "llinventory.h"
#include "llviewerinventory.h"
#include "llinventorydefines.h"
#include "llinventoryfunctions.h"
#include "llinventoryicon.h"
#include "llinventorymodel.h"
#include "llagent.h"
#include "llagentui.h"

#include "lllineeditor.h"
#include "lltexteditor.h"
#include "llbutton.h"
#include "lliconctrl.h"
#include "llcheckboxctrl.h"
#include "llscrolllistctrl.h"
#include "llscrolllistitem.h"
#include "lltextbox.h"
#include "lltrans.h"

#include "roles_constants.h"
#include "llviewerwindow.h"
#include "llviewermessage.h"
#include "llnotificationsutil.h"
#include "llgiveinventory.h"

#include "llviewercontrol.h"	// <FS:CR> FIRE-11247 - gSavedSettings

static LLPanelInjector<LLPanelGroupNotices> t_panel_group_notices("panel_group_notices");


/////////////////////////
// LLPanelGroupNotices //
/////////////////////////
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Class LLDropTarget
//
// This handy class is a simple way to drop something on another
// view. It handles drop events, always setting itself to the size of
// its parent.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class LLGroupDropTarget : public LLView
{
public:
	struct Params : public LLInitParam::Block<Params, LLView::Params>
	{
		// *NOTE: These parameters logically Mandatory, but are not
		// specified in XML files, hence Optional
		Optional<LLPanelGroupNotices*> panel;
		Optional<LLUUID>	group_id;
		Params()
		:	panel("panel"),
			group_id("group_id")
		{
			changeDefault(mouse_opaque, false);
			changeDefault(follows.flags, FOLLOWS_ALL);
		}
	};
	LLGroupDropTarget(const Params&);
	~LLGroupDropTarget() {};

	void doDrop(EDragAndDropType cargo_type, void* cargo_data);

	//
	// LLView functionality
	virtual BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop,
								   EDragAndDropType cargo_type,
								   void* cargo_data,
								   EAcceptance* accept,
								   std::string& tooltip_msg);
	void setPanel (LLPanelGroupNotices* panel) {mGroupNoticesPanel = panel;};
	void setGroup (LLUUID group) {mGroupID = group;};

protected:
	LLPanelGroupNotices* mGroupNoticesPanel;
	LLUUID	mGroupID;
};

static LLDefaultChildRegistry::Register<LLGroupDropTarget> r("group_drop_target");

LLGroupDropTarget::LLGroupDropTarget(const LLGroupDropTarget::Params& p) 
:	LLView(p),
	mGroupNoticesPanel(p.panel),
	mGroupID(p.group_id)
{}

void LLGroupDropTarget::doDrop(EDragAndDropType cargo_type, void* cargo_data)
{
	LL_INFOS() << "LLGroupDropTarget::doDrop()" << LL_ENDL;
}

BOOL LLGroupDropTarget::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop,
									 EDragAndDropType cargo_type,
									 void* cargo_data,
									 EAcceptance* accept,
									 std::string& tooltip_msg)
{
	BOOL handled = FALSE;

	if (!gAgent.hasPowerInGroup(mGroupID,GP_NOTICES_SEND))
	{
		*accept = ACCEPT_NO;
		return TRUE;
	}

	if(getParent())
	{
		// check if inside
		//LLRect parent_rect = mParentView->getRect();
		//getRect().set(0, parent_rect.getHeight(), parent_rect.getWidth(), 0);
		handled = TRUE;

		// check the type
		switch(cargo_type)
		{
		case DAD_TEXTURE:
		case DAD_SOUND:
		case DAD_LANDMARK:
		case DAD_SCRIPT:
		case DAD_OBJECT:
		case DAD_NOTECARD:
		case DAD_CLOTHING:
		case DAD_BODYPART:
		case DAD_ANIMATION:
		case DAD_GESTURE:
		case DAD_CALLINGCARD:
		case DAD_MESH:
        case DAD_SETTINGS:
		{
			LLViewerInventoryItem* inv_item = (LLViewerInventoryItem*)cargo_data;
			if(gInventory.getItem(inv_item->getUUID())
				&& LLGiveInventory::isInventoryGroupGiveAcceptable(inv_item))
			{
				// *TODO: get multiple object transfers working
				*accept = ACCEPT_YES_COPY_SINGLE;
				if(drop)
				{
					mGroupNoticesPanel->setItem(inv_item);
				}
			}
			else
			{
				// It's not in the user's inventory (it's probably
				// in an object's contents), so disallow dragging
				// it here.  You can't give something you don't
				// yet have.
				*accept = ACCEPT_NO;
			}
			break;
		}
		case DAD_CATEGORY:
		default:
			*accept = ACCEPT_NO;
			break;
		}
	}
	return handled;
}

//-----------------------------------------------------------------------------
// LLPanelGroupNotices
//-----------------------------------------------------------------------------
std::string build_notice_date(const U32& the_time)
{
	// ISO 8601 date format

	time_t t = (time_t)the_time;
	
	if (!t)
	{
		time(&t);
	}
	
	// <FS:Ansariel> FIRE-17649: Localizable date formats for group notices
	//std::string dateStr = "["+ LLTrans::getString("LTimeYear") + "]/["
	//							+ LLTrans::getString("LTimeMthNum") + "]/["
	//							+ LLTrans::getString("LTimeDay") + "] ["
	//							+ LLTrans::getString("LTimeHour") + "]:["
	//							+ LLTrans::getString("LTimeMin") + "]:["
	//							+ LLTrans::getString("LTimeSec") + "]";
	std::string dateStr = LLTrans::getString("GroupNoticesPanelDateString");
	// </FS:Ansariel>

	LLSD substitution;
	substitution["datetime"] = (S32) t;
	LLStringUtil::format (dateStr, substitution);
	return dateStr;
}

LLPanelGroupNotices::LLPanelGroupNotices() :
	LLPanelGroupTab(),
	mInventoryItem(NULL),
	mInventoryOffer(NULL)
{
	
	
}

LLPanelGroupNotices::~LLPanelGroupNotices()
{
	sInstances.erase(mGroupID);

	if (mInventoryOffer)
	{
		// Cancel the inventory offer.
		mInventoryOffer->forceResponse(IOR_DECLINE);

		mInventoryOffer = NULL;
	}
}


BOOL LLPanelGroupNotices::isVisibleByAgent(LLAgent* agentp)
{
	return mAllowEdit &&
		agentp->hasPowerInGroup(mGroupID, GP_NOTICES_SEND | GP_NOTICES_RECEIVE);
}

BOOL LLPanelGroupNotices::postBuild()
{
	bool recurse = true;

	mNoticesList = getChild<LLScrollListCtrl>("notice_list",recurse);
	mNoticesList->setCommitOnSelectionChange(TRUE);
	mNoticesList->setCommitCallback(onSelectNotice, this);

	mBtnNewMessage = getChild<LLButton>("create_new_notice",recurse);
	mBtnNewMessage->setClickedCallback(onClickNewMessage, this);
	mBtnNewMessage->setEnabled(gAgent.hasPowerInGroup(mGroupID, GP_NOTICES_SEND));

	mBtnGetPastNotices = getChild<LLButton>("refresh_notices",recurse);
	mBtnGetPastNotices->setClickedCallback(onClickRefreshNotices, this);

	// Create
	mCreateSubject = getChild<LLLineEditor>("create_subject",recurse);
	mCreateMessage = getChild<LLTextEditor>("create_message",recurse);

	mCreateInventoryName =  getChild<LLLineEditor>("create_inventory_name",recurse);
	mCreateInventoryName->setTabStop(FALSE);
	mCreateInventoryName->setEnabled(FALSE);

	// <FS:Ansariel> Doesn't exist as of 2015-11-27
	//mCreateInventoryIcon = getChild<LLIconCtrl>("create_inv_icon",recurse);
	//mCreateInventoryIcon->setVisible(FALSE);

	mBtnSendMessage = getChild<LLButton>("send_notice",recurse);
	mBtnSendMessage->setClickedCallback(onClickSendMessage, this);

	mBtnRemoveAttachment = getChild<LLButton>("remove_attachment",recurse);
	mBtnRemoveAttachment->setClickedCallback(onClickRemoveAttachment, this);
	mBtnRemoveAttachment->setEnabled(FALSE);

	// View
	mViewSubject = getChild<LLLineEditor>("view_subject",recurse);
	mViewMessage = getChild<LLTextEditor>("view_message",recurse);

	mViewInventoryName =  getChild<LLLineEditor>("view_inventory_name",recurse);
	mViewInventoryName->setTabStop(FALSE);
	mViewInventoryName->setEnabled(FALSE);

	// <FS:Ansariel> Doesn't exist as of 2015-11-27
	//mViewInventoryIcon = getChild<LLIconCtrl>("view_inv_icon",recurse);
	//mViewInventoryIcon->setVisible(FALSE);

	mBtnOpenAttachment = getChild<LLButton>("open_attachment",recurse);
	mBtnOpenAttachment->setClickedCallback(onClickOpenAttachment, this);

	mNoNoticesStr = getString("no_notices_text");

	mPanelCreateNotice = getChild<LLPanel>("panel_create_new_notice",recurse);
	mPanelViewNotice = getChild<LLPanel>("panel_view_past_notice",recurse);

	LLGroupDropTarget* target = getChild<LLGroupDropTarget> ("drop_target");
	target->setPanel (this);
	target->setGroup (mGroupID);

	arrangeNoticeView(VIEW_PAST_NOTICE);

	return LLPanelGroupTab::postBuild();
}

void LLPanelGroupNotices::activate()
{
    if (mNoticesList)
    {
        mNoticesList->deleteAllItems();
        mKnownNoticeIds.clear();
    }

	mPrevSelectedNotice = LLUUID();
	
	BOOL can_send = gAgent.hasPowerInGroup(mGroupID,GP_NOTICES_SEND);
	BOOL can_receive = gAgent.hasPowerInGroup(mGroupID,GP_NOTICES_RECEIVE);

	mPanelViewNotice->setEnabled(can_receive);
	mPanelCreateNotice->setEnabled(can_send);

	// Always disabled to stop direct editing of attachment names
	mCreateInventoryName->setEnabled(FALSE);
	mViewInventoryName->setEnabled(FALSE);

	// If we can receive notices, grab them right away.
	if (can_receive)
	{
		onClickRefreshNotices(this);
	}
}

void LLPanelGroupNotices::setItem(LLPointer<LLInventoryItem> inv_item)
{
	mInventoryItem = inv_item;

	BOOL item_is_multi = FALSE;
	if ( inv_item->getFlags() & LLInventoryItemFlags::II_FLAGS_OBJECT_HAS_MULTIPLE_ITEMS )
	{
		item_is_multi = TRUE;
	};

	std::string icon_name = LLInventoryIcon::getIconName(inv_item->getType(),
										inv_item->getInventoryType(),
										inv_item->getFlags(),
										item_is_multi );

	// <FS:Ansariel> Doesn't exist as of 2015-11-27
	//mCreateInventoryIcon->setValue(icon_name);
	//mCreateInventoryIcon->setVisible(TRUE);

	std::stringstream ss;
	ss << "        " << mInventoryItem->getName();

	mCreateInventoryName->setText(ss.str());
	mBtnRemoveAttachment->setEnabled(TRUE);
}

void LLPanelGroupNotices::onClickRemoveAttachment(void* data)
{
	LLPanelGroupNotices* self = (LLPanelGroupNotices*)data;
	self->mInventoryItem = NULL;
	self->mCreateInventoryName->clear();
	//self->mCreateInventoryIcon->setVisible(FALSE); // <FS:Ansariel> Doesn't exist as of 2015-11-27
	self->mBtnRemoveAttachment->setEnabled(FALSE);
}

//static 
void LLPanelGroupNotices::onClickOpenAttachment(void* data)
{
	LLPanelGroupNotices* self = (LLPanelGroupNotices*)data;

	self->mInventoryOffer->forceResponse(IOR_ACCEPT);
	self->mInventoryOffer = NULL;
	self->mBtnOpenAttachment->setEnabled(FALSE);
}

void LLPanelGroupNotices::onClickSendMessage(void* data)
{
	LLPanelGroupNotices* self = (LLPanelGroupNotices*)data;
	
	if (self->mCreateSubject->getText().empty())
	{
		// Must supply a subject
		LLNotificationsUtil::add("MustSpecifyGroupNoticeSubject");
		return;
	}
	send_group_notice(
			self->mGroupID,
			self->mCreateSubject->getText(),
			self->mCreateMessage->getText(),
			self->mInventoryItem);


	//instantly add new notice. actual notice will be added after ferreshNotices call
	LLUUID id = LLUUID::generateNewID();
	std::string subj = self->mCreateSubject->getText();
	std::string name ;
	LLAgentUI::buildFullname(name);
	U32 timestamp = 0;

	LLSD row;
	row["id"] = id;
	
	row["columns"][0]["column"] = "icon";

	row["columns"][1]["column"] = "subject";
	row["columns"][1]["value"] = subj;

	row["columns"][2]["column"] = "from";
	row["columns"][2]["value"] = name;

	row["columns"][3]["column"] = "date";
	row["columns"][3]["value"] = build_notice_date(timestamp);

	row["columns"][4]["column"] = "sort";
	row["columns"][4]["value"] = llformat( "%u", timestamp);

	self->mNoticesList->addElement(row, ADD_BOTTOM);
	self->mKnownNoticeIds.insert(id);

	self->mCreateMessage->clear();
	self->mCreateSubject->clear();
	onClickRemoveAttachment(data);

	self->arrangeNoticeView(VIEW_PAST_NOTICE);
}

//static 
void LLPanelGroupNotices::onClickNewMessage(void* data)
{
	LLPanelGroupNotices* self = (LLPanelGroupNotices*)data;

	self->arrangeNoticeView(CREATE_NEW_NOTICE);

	if (self->mInventoryOffer)
	{
		self->mInventoryOffer->forceResponse(IOR_DECLINE);
		self->mInventoryOffer = NULL;
	}

	self->mCreateSubject->clear();
	self->mCreateMessage->clear();
	if (self->mInventoryItem) onClickRemoveAttachment(self);
	self->mNoticesList->deselectAllItems(TRUE); // TRUE == don't commit on chnage
}

void LLPanelGroupNotices::refreshNotices()
{
	onClickRefreshNotices(this);
}

void LLPanelGroupNotices::clearNoticeList()
{
	mPrevSelectedNotice = mNoticesList->getStringUUIDSelectedItem();
	mNoticesList->deleteAllItems();
	mKnownNoticeIds.clear();
}

void LLPanelGroupNotices::onClickRefreshNotices(void* data)
{
	LL_DEBUGS() << "LLPanelGroupNotices::onClickGetPastNotices" << LL_ENDL;
	LLPanelGroupNotices* self = (LLPanelGroupNotices*)data;
	
	self->clearNoticeList();

	LLMessageSystem* msg = gMessageSystem;
	msg->newMessage("GroupNoticesListRequest");
	msg->nextBlock("AgentData");
	msg->addUUID("AgentID",gAgent.getID());
	msg->addUUID("SessionID",gAgent.getSessionID());
	msg->nextBlock("Data");
	msg->addUUID("GroupID",self->mGroupID);
	gAgent.sendReliableMessage();
}

//static
std::map<LLUUID,LLPanelGroupNotices*> LLPanelGroupNotices::sInstances;

// static
void LLPanelGroupNotices::processGroupNoticesListReply(LLMessageSystem* msg, void** data)
{
	LLUUID group_id;
	msg->getUUID("AgentData", "GroupID", group_id);

	std::map<LLUUID,LLPanelGroupNotices*>::iterator it = sInstances.find(group_id);
	if (it == sInstances.end())
	{
		LL_INFOS() << "Group Panel Notices " << group_id << " no longer in existence."
				<< LL_ENDL;
		return;
	}
	
	LLPanelGroupNotices* selfp = it->second;
	if(!selfp)
	{
		LL_INFOS() << "Group Panel Notices " << group_id << " no longer in existence."
				<< LL_ENDL;
		return;
	}

	selfp->processNotices(msg);
}

void LLPanelGroupNotices::processNotices(LLMessageSystem* msg)
{
	LLUUID id;
	std::string subj;
	std::string name;
	U32 timestamp;
	BOOL has_attachment;
	U8 asset_type;

	S32 i=0;
	S32 count = msg->getNumberOfBlocks("Data");

	mNoticesList->setEnabled(TRUE);

	//save sort state and set unsorted state to prevent unnecessary 
	//sorting while adding notices
	// <FS:Beq> FIRE-30667 et al. we will need sorting at the end
	// bool save_sort = mNoticesList->isSorted();
	mNoticesList->setNeedsSort(false);

	for (;i<count;++i)
	{
		msg->getUUID("Data","NoticeID",id,i);
		if (1 == count && id.isNull())
		{
			// Only one entry, the dummy entry.
			mNoticesList->setCommentText(mNoNoticesStr);
			mNoticesList->setEnabled(FALSE);
			return;
		}

        // Due to some network delays we can receive notice list more than once...
        // So add only unique notices
        if (mKnownNoticeIds.find(id) != mKnownNoticeIds.end())
        {
            // If items with this ID already in the list - skip it
            continue;
        }

		msg->getString("Data","Subject",subj,i);
		msg->getString("Data","FromName",name,i);
		msg->getBOOL("Data","HasAttachment",has_attachment,i);
		msg->getU8("Data","AssetType",asset_type,i);
		msg->getU32("Data","Timestamp",timestamp,i);

		// we only have the legacy name here, convert it to a username
		// <FS:CR> FIRE-11247 - Let the user decide how they want to see names
		//name = LLCacheName::buildUsername(name);
		name = gSavedSettings.getBOOL("FSNameTagShowLegacyUsernames") ? LLCacheName::buildLegacyName(name) : LLCacheName::buildUsername(name);
		// </FS:CR>

		LLSD row;
		row["id"] = id;
		row["columns"][0]["column"] = "icon";
		if (has_attachment)
		{
			std::string icon_name = LLInventoryIcon::getIconName(
									(LLAssetType::EType)asset_type,
									LLInventoryType::IT_NONE);
			row["columns"][0]["type"] = "icon";
			row["columns"][0]["value"] = icon_name;
		}

		row["columns"][1]["column"] = "subject";
		row["columns"][1]["value"] = subj;

		row["columns"][2]["column"] = "from";
		row["columns"][2]["value"] = name;

		row["columns"][3]["column"] = "date";
		row["columns"][3]["value"] = build_notice_date(timestamp);

		row["columns"][4]["column"] = "sort";
		row["columns"][4]["value"] = llformat( "%u", timestamp);

		mNoticesList->addElement(row, ADD_BOTTOM);
		mKnownNoticeIds.insert(id);
	}

	mNoticesList->setNeedsSort(true);
	// <FS:Beq> FIRE-30667 et al. defer sorting and updating selection
	// mNoticesList->updateSort();
	// if (mPanelViewNotice->getVisible())
	// {
	// 	if (!mNoticesList->selectByID(mPrevSelectedNotice))
	// 	{
	// 		mNoticesList->selectFirstItem();
	// 	}
	// }
}

void LLPanelGroupNotices::updateSelected()
{
	if( mNoticesList->mLastUpdateFrame == 0 )
	{
		if (mPanelViewNotice->getVisible())
		{
			if (!mNoticesList->selectByID(mPrevSelectedNotice))
			{
				mNoticesList->selectFirstItem();
			}
		}
		mNoticesList->mLastUpdateFrame = 1;
	}
	// </FS:Beq>
}

void LLPanelGroupNotices::onSelectNotice(LLUICtrl* ctrl, void* data)
{
	LLPanelGroupNotices* self = (LLPanelGroupNotices*)data;

	if(!self) return;
	LLScrollListItem* item = self->mNoticesList->getFirstSelected();
	if (!item) return;
	
	LLMessageSystem* msg = gMessageSystem;
	msg->newMessage("GroupNoticeRequest");
	msg->nextBlock("AgentData");
	msg->addUUID("AgentID",gAgent.getID());
	msg->addUUID("SessionID",gAgent.getSessionID());
	msg->nextBlock("Data");
	msg->addUUID("GroupNoticeID",item->getUUID());
	gAgent.sendReliableMessage();

	LL_DEBUGS() << "Item " << item->getUUID() << " selected." << LL_ENDL;
}

void LLPanelGroupNotices::showNotice(const std::string& subject,
									 const std::string& message,
									 const bool& has_inventory,
									 const std::string& inventory_name,
									 LLOfferInfo* inventory_offer)
{
	arrangeNoticeView(VIEW_PAST_NOTICE);

	if(mViewSubject) mViewSubject->setText(subject);
	if(mViewMessage) mViewMessage->setText(message);
	
	if (mInventoryOffer)
	{
		// Cancel the inventory offer for the previously viewed notice
		mInventoryOffer->forceResponse(IOR_DECLINE); 
		mInventoryOffer = NULL;
	}

	if (inventory_offer)
	{
		mInventoryOffer = inventory_offer;

		std::string icon_name = LLInventoryIcon::getIconName(mInventoryOffer->mType,
												LLInventoryType::IT_TEXTURE);

		// <FS:Ansariel> Doesn't exist as of 2015-11-27
		//mViewInventoryIcon->setValue(icon_name);
		//mViewInventoryIcon->setVisible(TRUE);

		std::stringstream ss;
		ss << "        " << inventory_name;

		mViewInventoryName->setText(ss.str());
		mBtnOpenAttachment->setEnabled(TRUE);
	}
	else
	{
		mViewInventoryName->clear();
		//mViewInventoryIcon->setVisible(FALSE); // <FS:Ansariel> Doesn't exist as of 2015-11-27
		mBtnOpenAttachment->setEnabled(FALSE);
	}
}

void LLPanelGroupNotices::arrangeNoticeView(ENoticeView view_type)
{
	if (CREATE_NEW_NOTICE == view_type)
	{
        mPanelCreateNotice->setVisible(TRUE);
		mPanelViewNotice->setVisible(FALSE);
	}
	else
	{
		mPanelCreateNotice->setVisible(FALSE);
		mPanelViewNotice->setVisible(TRUE);
		mBtnOpenAttachment->setEnabled(FALSE);
	}
}
void LLPanelGroupNotices::setGroupID(const LLUUID& id)
{
	sInstances.erase(mGroupID);
	LLPanelGroupTab::setGroupID(id);
	sInstances[mGroupID] = this;

	mBtnNewMessage->setEnabled(gAgent.hasPowerInGroup(mGroupID, GP_NOTICES_SEND));

	LLGroupDropTarget* target = getChild<LLGroupDropTarget> ("drop_target");
	target->setPanel (this);
	target->setGroup (mGroupID);

	if(mViewMessage) 
		mViewMessage->clear();

	if(mViewInventoryName)
		mViewInventoryName->clear();
	
	activate();
}
