/** 
 * @file llpanelclassified.cpp
 * @brief LLPanelClassified class implementation
 *
 * $LicenseInfo:firstyear=2005&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$
 */

// Display of a classified used both for the global view in the
// Find directory, and also for each individual user's classified in their
// profile.

#include "llviewerprecompiledheaders.h"

#include "llpanelclassified.h"

#include "lldir.h"
#include "lldispatcher.h"
#include "llfloaterreg.h"
#include "llhttpclient.h"
#include "llnotifications.h"
#include "llnotificationsutil.h"
#include "llparcel.h"
#include "lltabcontainer.h"
#include "message.h"

#include "llagent.h"
#include "llavataractions.h"
#include "llbutton.h"
#include "llcheckboxctrl.h"
#include "llclassifiedflags.h"
#include "llclassifiedstatsresponder.h"
#include "llcommandhandler.h" // for classified HTML detail page click tracking
#include "llviewercontrol.h"
#include "lllineeditor.h"
#include "lltextbox.h"
#include "llcombobox.h"
#include "lltexturectrl.h"
#include "lltexteditor.h"
#include "lluiconstants.h"
#include "llurldispatcher.h"	// for classified HTML detail click teleports
#include "lluictrlfactory.h"
#include "llviewerparcelmgr.h"
#include "llviewerwindow.h"
#include "llworldmap.h"
#include "llfloaterworldmap.h"
#include "llviewergenericmessage.h"	// send_generic_message
#include "llviewerregion.h"
#include "llviewerwindow.h"	// for window width, height
#include "llappviewer.h"	// abortQuit()
#include "lltrans.h"
#include "llscrollcontainer.h"
#include "llstatusbar.h"

const S32 MINIMUM_PRICE_FOR_LISTING = 50;	// L$
const S32 MATURE_UNDEFINED = -1;
const S32 MATURE_CONTENT = 1;
const S32 PG_CONTENT = 2;
const S32 DECLINE_TO_STATE = 0;

//static
std::list<LLPanelClassified*> LLPanelClassified::sAllPanels;
std::list<LLPanelClassifiedInfo*> LLPanelClassifiedInfo::sAllPanels;

// "classifiedclickthrough"
// strings[0] = classified_id
// strings[1] = teleport_clicks
// strings[2] = map_clicks
// strings[3] = profile_clicks
class LLDispatchClassifiedClickThrough : public LLDispatchHandler
{
public:
	virtual bool operator()(
		const LLDispatcher* dispatcher,
		const std::string& key,
		const LLUUID& invoice,
		const sparam_t& strings)
	{
		if (strings.size() != 4) return false;
		LLUUID classified_id(strings[0]);
		S32 teleport_clicks = atoi(strings[1].c_str());
		S32 map_clicks = atoi(strings[2].c_str());
		S32 profile_clicks = atoi(strings[3].c_str());

		LLPanelClassifiedInfo::setClickThrough(
			classified_id, teleport_clicks, map_clicks, profile_clicks, false);

		return true;
	}
};
static LLDispatchClassifiedClickThrough sClassifiedClickThrough;

// Just to debug errors. Can be thrown away later.
class LLClassifiedClickMessageResponder : public LLHTTPClient::Responder
{
	LOG_CLASS(LLClassifiedClickMessageResponder);

public:
	// If we get back an error (not found, etc...), handle it here
	virtual void errorWithContent(
		U32 status,
		const std::string& reason,
		const LLSD& content)
	{
		llwarns << "Sending click message failed (" << status << "): [" << reason << "]" << llendl;
		llwarns << "Content: [" << content << "]" << llendl;
	}
};


/* Re-expose this if we need to have classified ad HTML detail
   pages.  JC

// We need to count classified teleport clicks from the search HTML detail pages,
// so we need have a teleport that also sends a click count message.
class LLClassifiedTeleportHandler : public LLCommandHandler
{
public:
	// don't allow from external browsers because it moves you immediately
	LLClassifiedTeleportHandler() : LLCommandHandler("classifiedteleport", UNTRUSTED_BLOCK) { }

	bool handle(const LLSD& tokens, const LLSD& queryMap)
	{
		// Need at least classified id and region name, so 2 params
		if (tokens.size() < 2) return false;
		LLUUID classified_id = tokens[0].asUUID();
		if (classified_id.isNull()) return false;
		// *HACK: construct a SLURL to do the teleport
		std::string url("secondlife:///app/teleport/");
		// skip the uuid we took off above, rebuild URL
		// separated by slashes.
		for (S32 i = 1; i < tokens.size(); ++i)
		{
			url += tokens[i].asString();
			url += "/";
		}
		llinfos << "classified teleport to " << url << llendl;
		// *TODO: separately track old search, sidebar, and new search
		// Right now detail HTML pages count as new search.
		const bool from_search = true;
		LLPanelClassified::sendClassifiedClickMessage(classified_id, "teleport", from_search);
		// Invoke teleport
		LLMediaCtrl* web = NULL;
		const bool trusted_browser = true;
		return LLURLDispatcher::dispatch(url, web, trusted_browser);
	}
};
// Creating the object registers with the dispatcher.
LLClassifiedTeleportHandler gClassifiedTeleportHandler;
*/

LLPanelClassified::LLPanelClassified(bool in_finder, bool from_search)
:	LLPanel(),
	mInFinder(in_finder),
	mFromSearch(from_search),
	mDirty(false),
	mForceClose(false),
	mLocationChanged(false),
	mClassifiedID(),
	mCreatorID(),
	mPriceForListing(0),
	mDataRequested(FALSE),
	mPaidFor(FALSE),
	mPosGlobal(),
	mSnapshotCtrl(NULL),
	mNameEditor(NULL),
	mDescEditor(NULL),
	mLocationEditor(NULL),
	mCategoryCombo(NULL),
	mMatureCombo(NULL),
	mAutoRenewCheck(NULL),
	mUpdateBtn(NULL),
	mTeleportBtn(NULL),
	mMapBtn(NULL),
	mProfileBtn(NULL),
	mInfoText(NULL),
	mSetBtn(NULL),
	mClickThroughText(NULL),
	mTeleportClicksOld(0),
	mMapClicksOld(0),
	mProfileClicksOld(0),
	mTeleportClicksNew(0),
	mMapClicksNew(0),
	mProfileClicksNew(0)

{
    sAllPanels.push_back(this);

	std::string classified_def_file;
	if (mInFinder)
	{
		LLUICtrlFactory::getInstance()->buildPanel(this, "panel_classified.xml");
	}
	else
	{
		LLUICtrlFactory::getInstance()->buildPanel(this, "panel_avatar_classified.xml");
	}

	// Register dispatcher
	gGenericDispatcher.addHandler("classifiedclickthrough", 
								  &sClassifiedClickThrough);
}


LLPanelClassified::~LLPanelClassified()
{
    sAllPanels.remove(this);
}


void LLPanelClassified::reset()
{
	mClassifiedID.setNull();
	mCreatorID.setNull();
	mParcelID.setNull();

	// Don't request data, this isn't valid
	mDataRequested = TRUE;

	mDirty = false;
	mPaidFor = FALSE;

	mPosGlobal.clearVec();

	clearCtrls();
	resetDirty();
}


BOOL LLPanelClassified::postBuild()
{
    mSnapshotCtrl = getChild<LLTextureCtrl>("snapshot_ctrl");
	mSnapshotCtrl->setCommitCallback(onCommitAny, this);
	mSnapshotSize = mSnapshotCtrl->getRect();

    mNameEditor = getChild<LLLineEditor>("given_name_editor");
	mNameEditor->setMaxTextLength(DB_PARCEL_NAME_LEN);
	mNameEditor->setCommitOnFocusLost(TRUE);
	mNameEditor->setFocusReceivedCallback(boost::bind(focusReceived, _1, this));
	mNameEditor->setCommitCallback(onCommitAny, this);
	mNameEditor->setPrevalidate( LLTextValidate::validateASCII );

    mDescEditor = getChild<LLTextEditor>("desc_editor");
	mDescEditor->setCommitOnFocusLost(TRUE);
	mDescEditor->setFocusReceivedCallback(boost::bind(focusReceived, _1, this));
	mDescEditor->setCommitCallback(onCommitAny, this);
	
    mLocationEditor = getChild<LLLineEditor>("location_editor");

    mSetBtn = getChild<LLButton>( "set_location_btn");
    mSetBtn->setClickedCallback(onClickSet, this);

    mTeleportBtn = getChild<LLButton>( "classified_teleport_btn");
    mTeleportBtn->setClickedCallback(onClickTeleport, this);

    mMapBtn = getChild<LLButton>( "classified_map_btn");
    mMapBtn->setClickedCallback(onClickMap, this);

	if(mInFinder)
	{
		mProfileBtn  = getChild<LLButton>( "classified_profile_btn");
		mProfileBtn->setClickedCallback(onClickProfile, this);
	}

	mCategoryCombo = getChild<LLComboBox>( "classified_category_combo");
	LLClassifiedInfo::cat_map::iterator iter;
	for (iter = LLClassifiedInfo::sCategories.begin();
		iter != LLClassifiedInfo::sCategories.end();
		iter++)
	{
		mCategoryCombo->add(LLTrans::getString(iter->second), (void *)((intptr_t)iter->first), ADD_BOTTOM);
	}
	mCategoryCombo->setCurrentByIndex(0);
	mCategoryCombo->setCommitCallback(onCommitAny, this);

	mMatureCombo = getChild<LLComboBox>( "classified_mature_check");
	mMatureCombo->setCurrentByIndex(0);
	mMatureCombo->setCommitCallback(onCommitAny, this);
	if (gAgent.wantsPGOnly())
	{
		// Teens don't get to set mature flag. JC
		mMatureCombo->setVisible(FALSE);
		mMatureCombo->setCurrentByIndex(PG_CONTENT);
	}

	if (!mInFinder)
	{
		mAutoRenewCheck = getChild<LLCheckBoxCtrl>( "auto_renew_check");
		mAutoRenewCheck->setCommitCallback(onCommitAny, this);
	}

	mUpdateBtn = getChild<LLButton>("classified_update_btn");
    mUpdateBtn->setClickedCallback(onClickUpdate, this);

	if (!mInFinder)
	{
		mClickThroughText = getChild<LLTextBox>("click_through_text");
	}
	
	resetDirty();
    return TRUE;
}

BOOL LLPanelClassified::titleIsValid()
{
	// Disallow leading spaces, punctuation, etc. that screw up
	// sort order.
	const std::string& name = mNameEditor->getText();
	if (name.empty())
	{
		LLNotificationsUtil::add("BlankClassifiedName");
		return FALSE;
	}
	if (!isalnum(name[0]))
	{
		LLNotificationsUtil::add("ClassifiedMustBeAlphanumeric");
		return FALSE;
	}

	return TRUE;
}

void LLPanelClassified::apply()
{
	// Apply is used for automatically saving results, so only
	// do that if there is a difference, and this is a save not create.
	if (checkDirty() && mPaidFor)
	{
		sendClassifiedInfoUpdate();
	}
}

bool LLPanelClassified::saveCallback(const LLSD& notification, const LLSD& response)
{
	S32 option = LLNotificationsUtil::getSelectedOption(notification, response);

	switch(option)
	{
		case 0: // Save
			sendClassifiedInfoUpdate();
			// fall through to close

		case 1: // Don't Save
			{
				mForceClose = true;
				// Close containing floater
				LLFloater* parent_floater = gFloaterView->getParentFloater(this);
				if (parent_floater)
				{
					parent_floater->closeFloater();
				}
			}
			break;

		case 2: // Cancel
		default:
            LLAppViewer::instance()->abortQuit();
			break;
	}
	return false;
}


BOOL LLPanelClassified::canClose()
{
	if (mForceClose || !checkDirty()) 
		return TRUE;

	LLSD args;
	args["NAME"] = mNameEditor->getText();
	LLNotificationsUtil::add("ClassifiedSave", args, LLSD(), boost::bind(&LLPanelClassified::saveCallback, this, _1, _2));
	return FALSE;
}

// Fill in some reasonable defaults for a new classified.
void LLPanelClassified::initNewClassified()
{
	// TODO:  Don't generate this on the client.
	mClassifiedID.generate();

	mCreatorID = gAgent.getID();

	mPosGlobal = gAgent.getPositionGlobal();

	mPaidFor = FALSE;

	// Try to fill in the current parcel
	LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel();
	if (parcel)
	{
		mNameEditor->setText(parcel->getName());
		//mDescEditor->setText(parcel->getDesc());
		mSnapshotCtrl->setImageAssetID(parcel->getSnapshotID());
		//mPriceEditor->setText("0");
		mCategoryCombo->setCurrentByIndex(0);
	}

	mUpdateBtn->setLabel(getString("publish_txt"));
	
	// simulate clicking the "location" button
	LLPanelClassified::onClickSet(this);
}


void LLPanelClassified::setClassifiedID(const LLUUID& id)
{
	mClassifiedID = id;
}

//static
void LLPanelClassified::setClickThrough(const LLUUID& classified_id,
										S32 teleport,
										S32 map,
										S32 profile,
										bool from_new_table)
{
	for (panel_list_t::iterator iter = sAllPanels.begin(); iter != sAllPanels.end(); ++iter)
	{
		LLPanelClassified* self = *iter;
		// For top picks, must match pick id
		if (self->mClassifiedID != classified_id)
		{
			continue;
		}

		// We need to check to see if the data came from the new stat_table 
		// or the old classified table. We also need to cache the data from 
		// the two separate sources so as to display the aggregate totals.

		if (from_new_table)
		{
			self->mTeleportClicksNew = teleport;
			self->mMapClicksNew = map;
			self->mProfileClicksNew = profile;
		}
		else
		{
			self->mTeleportClicksOld = teleport;
			self->mMapClicksOld = map;
			self->mProfileClicksOld = profile;
		}

		if (self->mClickThroughText)
		{
			LLStringUtil::format_map_t args;
			args["[TELEPORT]"] = llformat ("%d", self->mTeleportClicksNew + self->mTeleportClicksOld);
			args["[MAP]"] = llformat ("%d", self->mMapClicksNew + self->mMapClicksOld);
			args["[PROFILE]"] = llformat ("%d", self->mProfileClicksNew + self->mProfileClicksOld);
			std::string msg = LLTrans::getString ("ClassifiedClicksTxt", args);
			self->mClickThroughText->setText(msg);
		}
	}
}

// Schedules the panel to request data
// from the server next time it is drawn.
void LLPanelClassified::markForServerRequest()
{
	mDataRequested = FALSE;
}


std::string LLPanelClassified::getClassifiedName()
{
	return mNameEditor->getText();
}


void LLPanelClassified::sendClassifiedInfoRequest()
{
    LLMessageSystem *msg = gMessageSystem;

	if (mClassifiedID != mRequestedID)
	{
		msg->newMessageFast(_PREHASH_ClassifiedInfoRequest);
		msg->nextBlockFast(_PREHASH_AgentData);
		msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() );
		msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() );
		msg->nextBlockFast(_PREHASH_Data);
		msg->addUUIDFast(_PREHASH_ClassifiedID, mClassifiedID);
		gAgent.sendReliableMessage();

		mDataRequested = TRUE;
		mRequestedID = mClassifiedID;

		// While we're at it let's get the stats from the new table if that
		// capability exists.
		std::string url = gAgent.getRegion()->getCapability("SearchStatRequest");
		LLSD body;
		body["classified_id"] = mClassifiedID;

		if (!url.empty())
		{
			llinfos << "Classified stat request via capability" << llendl;
			LLHTTPClient::post(url, body, new LLClassifiedStatsResponder(mClassifiedID));
		}
	}
}


void LLPanelClassified::sendClassifiedInfoUpdate()
{
	// If we don't have a classified id yet, we'll need to generate one,
	// otherwise we'll keep overwriting classified_id 00000 in the database.
	if (mClassifiedID.isNull())
	{
		// TODO:  Don't do this on the client.
		mClassifiedID.generate();
	}

	LLMessageSystem* msg = gMessageSystem;

	msg->newMessageFast(_PREHASH_ClassifiedInfoUpdate);
	msg->nextBlockFast(_PREHASH_AgentData);
	msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
	msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
	msg->nextBlockFast(_PREHASH_Data);
	msg->addUUIDFast(_PREHASH_ClassifiedID, mClassifiedID);
	// TODO: fix this
	U32 category = mCategoryCombo->getCurrentIndex() + 1;
	msg->addU32Fast(_PREHASH_Category, category);
	msg->addStringFast(_PREHASH_Name, mNameEditor->getText());
	msg->addStringFast(_PREHASH_Desc, mDescEditor->getText());

	// fills in on simulator if null
	msg->addUUIDFast(_PREHASH_ParcelID, mParcelID);
	// fills in on simulator if null
	msg->addU32Fast(_PREHASH_ParentEstate, 0);
	msg->addUUIDFast(_PREHASH_SnapshotID, mSnapshotCtrl->getImageAssetID());
	msg->addVector3dFast(_PREHASH_PosGlobal, mPosGlobal);
	BOOL mature = mMatureCombo->getCurrentIndex() == MATURE_CONTENT;
	BOOL auto_renew = FALSE;
	if (mAutoRenewCheck) 
	{
		auto_renew = mAutoRenewCheck->get();
	}
    // These flags doesn't matter here.
    const bool adult_enabled = false;
	const bool is_pg = false;
	U8 flags = pack_classified_flags_request(auto_renew, is_pg, mature, adult_enabled);
	msg->addU8Fast(_PREHASH_ClassifiedFlags, flags);
	msg->addS32("PriceForListing", mPriceForListing);
	gAgent.sendReliableMessage();

	mDirty = false;
}


//static
void LLPanelClassified::processClassifiedInfoReply(LLMessageSystem *msg, void **)
{
	lldebugs << "processClassifiedInfoReply()" << llendl;
    // Extract the agent id and verify the message is for this
    // client.
    LLUUID agent_id;
    msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id );
    if (agent_id != gAgent.getID())
    {
        llwarns << "Agent ID mismatch in processClassifiedInfoReply"
            << llendl;
		return;
    }

    LLUUID classified_id;
    msg->getUUIDFast(_PREHASH_Data, _PREHASH_ClassifiedID, classified_id);

    LLUUID creator_id;
    msg->getUUIDFast(_PREHASH_Data, _PREHASH_CreatorID, creator_id);

    LLUUID parcel_id;
    msg->getUUIDFast(_PREHASH_Data, _PREHASH_ParcelID, parcel_id);

	std::string name;
	msg->getStringFast(_PREHASH_Data, _PREHASH_Name, name);

	std::string desc;
	msg->getStringFast(_PREHASH_Data, _PREHASH_Desc, desc);

	LLUUID snapshot_id;
	msg->getUUIDFast(_PREHASH_Data, _PREHASH_SnapshotID, snapshot_id);

    // "Location text" is actually the original
    // name that owner gave the parcel, and the location.
	std::string location_text;

    msg->getStringFast(_PREHASH_Data, _PREHASH_ParcelName, location_text);
	if (!location_text.empty())
	{
		location_text.append(", ");
	}

	std::string sim_name;
	msg->getStringFast(_PREHASH_Data, _PREHASH_SimName, sim_name);

	LLVector3d pos_global;
	msg->getVector3dFast(_PREHASH_Data, _PREHASH_PosGlobal, pos_global);

    S32 region_x = llround((F32)pos_global.mdV[VX]) % REGION_WIDTH_UNITS;
    S32 region_y = llround((F32)pos_global.mdV[VY]) % REGION_WIDTH_UNITS;
	S32 region_z = llround((F32)pos_global.mdV[VZ]);

	std::string buffer = llformat("%s (%d, %d, %d)", sim_name.c_str(), region_x, region_y, region_z);
    location_text.append(buffer);

	U8 flags;
	msg->getU8Fast(_PREHASH_Data, _PREHASH_ClassifiedFlags, flags);
	//BOOL enabled = is_cf_enabled(flags);
	bool mature = is_cf_mature(flags);
	bool auto_renew = is_cf_auto_renew(flags);

	U32 date = 0;
	msg->getU32Fast(_PREHASH_Data, _PREHASH_CreationDate, date);
	time_t tim = date;

	// future use
	U32 expiration_date = 0;
	msg->getU32("Data", "ExpirationDate", expiration_date);

	U32 category = 0;
	msg->getU32Fast(_PREHASH_Data, _PREHASH_Category, category);

	S32 price_for_listing = 0;
	msg->getS32("Data", "PriceForListing", price_for_listing);

    // Look up the panel to fill in
	for (panel_list_t::iterator iter = sAllPanels.begin(); iter != sAllPanels.end(); ++iter)
	{
		LLPanelClassified* self = *iter;
		// For top picks, must match pick id
		if (self->mClassifiedID != classified_id)
		{
			continue;
		}

        // Found the panel, now fill in the information
		self->mClassifiedID = classified_id;
		self->mCreatorID = creator_id;
		self->mParcelID = parcel_id;
		self->mPriceForListing = price_for_listing;
		self->mSimName.assign(sim_name);
		self->mPosGlobal = pos_global;

		// Update UI controls
        self->mNameEditor->setText(name);
        self->mDescEditor->setText(desc);
        self->mSnapshotCtrl->setImageAssetID(snapshot_id);
        self->mLocationEditor->setText(location_text);
		self->mLocationChanged = false;

		self->mCategoryCombo->setCurrentByIndex(category - 1);
		if(mature)
		{
			self->mMatureCombo->setCurrentByIndex(MATURE_CONTENT);
		}
		else
		{
			self->mMatureCombo->setCurrentByIndex(PG_CONTENT);
		}
		if (self->mAutoRenewCheck)
		{
			self->mAutoRenewCheck->set(auto_renew);
		}

		std::string dateStr = self->getString("dateStr");
		LLSD substitution;
		substitution["datetime"] = (S32) tim;
		LLStringUtil::format (dateStr, substitution);

		LLStringUtil::format_map_t string_args;
		string_args["[DATE]"] = dateStr;
		string_args["[AMT]"] = llformat("%d", price_for_listing);
		self->childSetText("classified_info_text", self->getString("ad_placed_paid", string_args));

		// If we got data from the database, we know the listing is paid for.
		self->mPaidFor = TRUE;

		self->mUpdateBtn->setLabel(self->getString("update_txt"));

		self->resetDirty();

		// I don't know if a second call is deliberate or a bad merge, so I'm leaving it here. 
		self->resetDirty();
    }
}

void LLPanelClassified::draw()
{
	refresh();

	LLPanel::draw();
}


void LLPanelClassified::refresh()
{
	if (!mDataRequested)
	{
		sendClassifiedInfoRequest();
	}

    // Check for god mode
    BOOL godlike = gAgent.isGodlike();
	BOOL is_self = (gAgent.getID() == mCreatorID);

    // Set button visibility/enablement appropriately
	if (mInFinder)
	{

		// End user doesn't ned to see price twice, or date posted.

		mSnapshotCtrl->setEnabled(godlike);
		if(godlike)
		{
			//make it smaller, so text is more legible
			mSnapshotCtrl->setOrigin(20, 175);
			mSnapshotCtrl->reshape(300, 200);
		}
		else
		{
			mSnapshotCtrl->setOrigin(mSnapshotSize.mLeft, mSnapshotSize.mBottom);
			mSnapshotCtrl->reshape(mSnapshotSize.getWidth(), mSnapshotSize.getHeight());
			//normal
		}
		mNameEditor->setEnabled(godlike);
		mDescEditor->setEnabled(godlike);
		mCategoryCombo->setEnabled(godlike);
		mCategoryCombo->setVisible(godlike);

		mMatureCombo->setEnabled(godlike);
		mMatureCombo->setVisible(godlike);

		// Jesse (who is the only one who uses this, as far as we can tell
		// Says that he does not want a set location button - he has used it
		// accidently in the past.
		mSetBtn->setVisible(FALSE);
		mSetBtn->setEnabled(FALSE);

		mUpdateBtn->setEnabled(godlike);
		mUpdateBtn->setVisible(godlike);
	}
	else
	{
		mSnapshotCtrl->setEnabled(is_self);
		mNameEditor->setEnabled(is_self);
		mDescEditor->setEnabled(is_self);
		//mPriceEditor->setEnabled(is_self);
		mCategoryCombo->setEnabled(is_self);
		mMatureCombo->setEnabled(is_self);

		if( is_self )
		{							
			if( mMatureCombo->getCurrentIndex() == 0 )
			{
				// It's a new panel.
				// PG regions should have PG classifieds. AO should have mature.
								
				setDefaultAccessCombo();
			}
		}
		
		if (mAutoRenewCheck)
		{
			mAutoRenewCheck->setEnabled(is_self);
			mAutoRenewCheck->setVisible(is_self);
		}
		
		mClickThroughText->setEnabled(is_self);
		mClickThroughText->setVisible(is_self);

		mSetBtn->setVisible(is_self);
		mSetBtn->setEnabled(is_self);

		mUpdateBtn->setEnabled(is_self && checkDirty());
		mUpdateBtn->setVisible(is_self);
	}
}

// static
void LLPanelClassified::onClickUpdate(void* data)
{
	LLPanelClassified* self = (LLPanelClassified*)data;

	if(self == NULL) return;

	// Disallow leading spaces, punctuation, etc. that screw up
	// sort order.
	if ( ! self->titleIsValid() )
	{
		return;
	};

	// If user has not set mature, do not allow publish
	if(self->mMatureCombo->getCurrentIndex() == DECLINE_TO_STATE)
	{
		// Tell user about it
		LLNotificationsUtil::add("SetClassifiedMature", 
				LLSD(), 
				LLSD(), 
				boost::bind(&LLPanelClassified::confirmMature, self, _1, _2));
		return;
	}

	// Mature content flag is set, proceed
	self->gotMature();
}

// Callback from a dialog indicating response to mature notification
bool LLPanelClassified::confirmMature(const LLSD& notification, const LLSD& response)
{
	S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
	
	// 0 == Yes
	// 1 == No
	// 2 == Cancel
	switch(option)
	{
	case 0:
		mMatureCombo->setCurrentByIndex(MATURE_CONTENT);
		break;
	case 1:
		mMatureCombo->setCurrentByIndex(PG_CONTENT);
		break;
	default:
		return false;
	}
	
	// If we got here it means they set a valid value
	gotMature();
	return false;
}

// Called after we have determined whether this classified has
// mature content or not.
void LLPanelClassified::gotMature()
{
	// if already paid for, just do the update
	if (mPaidFor)
	{
		LLNotification::Params params("PublishClassified");
		params.functor.function(boost::bind(&LLPanelClassified::confirmPublish, this, _1, _2));
		LLNotifications::instance().forceResponse(params, 0);
	}
	else
	{
		// Ask the user how much they want to pay
		LLFloaterPriceForListing::show( callbackGotPriceForListing, this );
	}
}

// static
void LLPanelClassified::callbackGotPriceForListing(S32 option, std::string text, void* data)
{
	LLPanelClassified* self = (LLPanelClassified*)data;

	// Only do something if user hits publish
	if (option != 0) return;

	S32 price_for_listing = strtol(text.c_str(), NULL, 10);
	if (price_for_listing < MINIMUM_PRICE_FOR_LISTING)
	{
		LLSD args;
		std::string price_text = llformat("%d", MINIMUM_PRICE_FOR_LISTING);
		args["MIN_PRICE"] = price_text;
			
		LLNotificationsUtil::add("MinClassifiedPrice", args);
		return;
	}

	// price is acceptable, put it in the dialog for later read by 
	// update send
	self->mPriceForListing = price_for_listing;

	LLSD args;
	args["AMOUNT"] = llformat("%d", price_for_listing);
	LLNotificationsUtil::add("PublishClassified", args, LLSD(), 
									boost::bind(&LLPanelClassified::confirmPublish, self, _1, _2));
}

void LLPanelClassified::resetDirty()
{
	// Tell all the widgets to reset their dirty state since the ad was just saved
	if (mSnapshotCtrl)
		mSnapshotCtrl->resetDirty();
	if (mNameEditor)
		mNameEditor->resetDirty();
	if (mDescEditor)
		mDescEditor->resetDirty();
	if (mLocationEditor)
		mLocationEditor->resetDirty();
	mLocationChanged = false;
	if (mCategoryCombo)
		mCategoryCombo->resetDirty();
	if (mMatureCombo)
		mMatureCombo->resetDirty();
	if (mAutoRenewCheck)
		mAutoRenewCheck->resetDirty();
}

// invoked from callbackConfirmPublish
bool LLPanelClassified::confirmPublish(const LLSD& notification, const LLSD& response)
{
	S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
	// Option 0 = publish
	if (option != 0) return false;

	sendClassifiedInfoUpdate();

	// Big hack - assume that top picks are always in a browser,
	// and non-finder-classifieds are always in a tab container.
	if (! mInFinder)
	{
		LLTabContainer* tab = (LLTabContainer*)getParent();
		tab->setCurrentTabName(mNameEditor->getText());
	}

	resetDirty();
	return false;
}


// static
void LLPanelClassified::onClickTeleport(void* data)
{
    LLPanelClassified* self = (LLPanelClassified*)data;
	LLFloaterWorldMap* worldmap_instance = LLFloaterWorldMap::getInstance();
	
    if (!self->mPosGlobal.isExactlyZero()&&worldmap_instance)
    {
        gAgent.teleportViaLocation(self->mPosGlobal);		
        worldmap_instance->trackLocation(self->mPosGlobal);
		self->sendClassifiedClickMessage("teleport");
    }
}


// static
void LLPanelClassified::onClickMap(void* data)
{
	LLPanelClassified* self = (LLPanelClassified*)data;
	LLFloaterWorldMap* worldmap_instance = LLFloaterWorldMap::getInstance();
	if(worldmap_instance)
	{
		worldmap_instance->trackLocation(self->mPosGlobal);
		LLFloaterReg::showInstance("world_map", "center");
	}
	self->sendClassifiedClickMessage("map");
}

// static
void LLPanelClassified::onClickProfile(void* data)
{
	LLPanelClassified* self = (LLPanelClassified*)data;
	LLAvatarActions::showProfile(self->mCreatorID);
	self->sendClassifiedClickMessage("profile");
}

// static
/*
void LLPanelClassified::onClickLandmark(void* data)
{
    LLPanelClassified* self = (LLPanelClassified*)data;
	create_landmark(self->mNameEditor->getText(), "", self->mPosGlobal);
}
*/

// static
void LLPanelClassified::onClickSet(void* data)
{
    LLPanelClassified* self = (LLPanelClassified*)data;

	// Save location for later.
	self->mPosGlobal = gAgent.getPositionGlobal();

	std::string location_text;
	std::string regionName = LLTrans::getString("ClassifiedUpdateAfterPublish");
	LLViewerRegion* pRegion = gAgent.getRegion();
	if (pRegion)
	{
		regionName = pRegion->getName();
	}
	location_text.assign(regionName);
	location_text.append(", ");

    S32 region_x = llround((F32)self->mPosGlobal.mdV[VX]) % REGION_WIDTH_UNITS;
    S32 region_y = llround((F32)self->mPosGlobal.mdV[VY]) % REGION_WIDTH_UNITS;
	S32 region_z = llround((F32)self->mPosGlobal.mdV[VZ]);
   
	location_text.append(self->mSimName);
    location_text.append(llformat(" (%d, %d, %d)", region_x, region_y, region_z));

	self->mLocationEditor->setText(location_text);
	self->mLocationChanged = true;
	
	self->setDefaultAccessCombo();	

	// Set this to null so it updates on the next save.
	self->mParcelID.setNull();

	onCommitAny(NULL, data);
}


BOOL LLPanelClassified::checkDirty()
{
	mDirty = FALSE;
	if	( mSnapshotCtrl )			mDirty |= mSnapshotCtrl->isDirty();
	if	( mNameEditor )				mDirty |= mNameEditor->isDirty();
	if	( mDescEditor )				mDirty |= mDescEditor->isDirty();
	if	( mLocationEditor )			mDirty |= mLocationEditor->isDirty();
	if  ( mLocationChanged )		mDirty |= TRUE;
	if	( mCategoryCombo )			mDirty |= mCategoryCombo->isDirty();
	if	( mMatureCombo )			mDirty |= mMatureCombo->isDirty();
	if	( mAutoRenewCheck )			mDirty |= mAutoRenewCheck->isDirty();

	return mDirty;
}

// static
void LLPanelClassified::onCommitAny(LLUICtrl* ctrl, void* data)
{
	LLPanelClassified* self = (LLPanelClassified*)data;
	if (self)
	{
		self->checkDirty();
	}
}

// static
void LLPanelClassified::focusReceived(LLFocusableElement* ctrl, void* data)
{
	// allow the data to be saved
	onCommitAny((LLUICtrl*)ctrl, data);
}


void LLPanelClassified::sendClassifiedClickMessage(const std::string& type)
{
	// You're allowed to click on your own ads to reassure yourself
	// that the system is working.
	LLSD body;
	body["type"] = type;
	body["from_search"] = mFromSearch;
	body["classified_id"] = mClassifiedID;
	body["parcel_id"] = mParcelID;
	body["dest_pos_global"] = mPosGlobal.getValue();
	body["region_name"] = mSimName;

	std::string url = gAgent.getRegion()->getCapability("SearchStatTracking");
	llinfos << "LLPanelClassified::sendClassifiedClickMessage via capability" << llendl;
	LLHTTPClient::post(url, body, new LLHTTPClient::Responder());
}

////////////////////////////////////////////////////////////////////////////////////////////

LLFloaterPriceForListing::LLFloaterPriceForListing()
:	LLFloater(LLSD()),
	mCallback(NULL),
	mUserData(NULL)
{ }

//virtual
LLFloaterPriceForListing::~LLFloaterPriceForListing()
{ }

//virtual
BOOL LLFloaterPriceForListing::postBuild()
{
	LLLineEditor* edit = getChild<LLLineEditor>("price_edit");
	if (edit)
	{
		edit->setPrevalidate(LLTextValidate::validateNonNegativeS32);
		std::string min_price = llformat("%d", MINIMUM_PRICE_FOR_LISTING);
		edit->setText(min_price);
		edit->selectAll();
		edit->setFocus(TRUE);
	}

	childSetAction("set_price_btn", onClickSetPrice, this);

	childSetAction("cancel_btn", onClickCancel, this);

	setDefaultBtn("set_price_btn");
	return TRUE;
}

//static
void LLFloaterPriceForListing::show( void (*callback)(S32, std::string, void*), void* userdata)
{
	LLFloaterPriceForListing *self = new LLFloaterPriceForListing();

	// Builds and adds to gFloaterView
	LLUICtrlFactory::getInstance()->buildFloater(self, "floater_price_for_listing.xml", NULL);
	self->center();

	self->mCallback = callback;
	self->mUserData = userdata;
}

//static
void LLFloaterPriceForListing::onClickSetPrice(void* data)
{
	buttonCore(0, data);
}

//static
void LLFloaterPriceForListing::onClickCancel(void* data)
{
	buttonCore(1, data);
}

//static
void LLFloaterPriceForListing::buttonCore(S32 button, void* data)
{
	LLFloaterPriceForListing* self = (LLFloaterPriceForListing*)data;

	if (self->mCallback)
	{
		std::string text = self->childGetText("price_edit");
		self->mCallback(button, text, self->mUserData);
		self->closeFloater();
	}
}

void LLPanelClassified::setDefaultAccessCombo()
{
	// PG regions should have PG classifieds. AO should have mature.

	LLViewerRegion *regionp = gAgent.getRegion();

	switch( regionp->getSimAccess() )
	{
		case SIM_ACCESS_PG:	
			mMatureCombo->setCurrentByIndex(PG_CONTENT);
			break;
		case SIM_ACCESS_ADULT:
			mMatureCombo->setCurrentByIndex(MATURE_CONTENT);
			break;
		default:
			// You are free to move about the cabin.
			break;
	}
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////

LLPanelClassifiedInfo::LLPanelClassifiedInfo()
 : LLPanel()
 , mInfoLoaded(false)
 , mScrollingPanel(NULL)
 , mScrollContainer(NULL)
 , mScrollingPanelMinHeight(0)
 , mScrollingPanelWidth(0)
 , mSnapshotStreched(false)
 , mTeleportClicksOld(0)
 , mMapClicksOld(0)
 , mProfileClicksOld(0)
 , mTeleportClicksNew(0)
 , mMapClicksNew(0)
 , mProfileClicksNew(0)
 , mSnapshotCtrl(NULL)
{
	sAllPanels.push_back(this);
}

LLPanelClassifiedInfo::~LLPanelClassifiedInfo()
{
	sAllPanels.remove(this);
}

// static
LLPanelClassifiedInfo* LLPanelClassifiedInfo::create()
{
	LLPanelClassifiedInfo* panel = new LLPanelClassifiedInfo();
	LLUICtrlFactory::getInstance()->buildPanel(panel, "panel_classified_info.xml");
	return panel;
}

BOOL LLPanelClassifiedInfo::postBuild()
{
	childSetAction("back_btn", boost::bind(&LLPanelClassifiedInfo::onExit, this));
	childSetAction("show_on_map_btn", boost::bind(&LLPanelClassifiedInfo::onMapClick, this));
	childSetAction("teleport_btn", boost::bind(&LLPanelClassifiedInfo::onTeleportClick, this));

	mScrollingPanel = getChild<LLPanel>("scroll_content_panel");
	mScrollContainer = getChild<LLScrollContainer>("profile_scroll");

	mScrollingPanelMinHeight = mScrollContainer->getScrolledViewRect().getHeight();
	mScrollingPanelWidth = mScrollingPanel->getRect().getWidth();

	mSnapshotCtrl = getChild<LLTextureCtrl>("classified_snapshot");
	mSnapshotRect = getDefaultSnapshotRect();

	return TRUE;
}

void LLPanelClassifiedInfo::setExitCallback(const commit_callback_t& cb)
{
	getChild<LLButton>("back_btn")->setClickedCallback(cb);
}

void LLPanelClassifiedInfo::setEditClassifiedCallback(const commit_callback_t& cb)
{
	getChild<LLButton>("edit_btn")->setClickedCallback(cb);
}

void LLPanelClassifiedInfo::reshape(S32 width, S32 height, BOOL called_from_parent /* = TRUE */)
{
	LLPanel::reshape(width, height, called_from_parent);

	if (!mScrollContainer || !mScrollingPanel)
		return;

	static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);

	S32 scroll_height = mScrollContainer->getRect().getHeight();
	if (mScrollingPanelMinHeight >= scroll_height)
	{
		mScrollingPanel->reshape(mScrollingPanelWidth, mScrollingPanelMinHeight);
	}
	else
	{
		mScrollingPanel->reshape(mScrollingPanelWidth + scrollbar_size, scroll_height);
	}

	mSnapshotRect = getDefaultSnapshotRect();
	stretchSnapshot();
}

void LLPanelClassifiedInfo::onOpen(const LLSD& key)
{
	LLUUID avatar_id = key["classified_creator_id"];
	if(avatar_id.isNull())
	{
		return;
	}

	if(getAvatarId().notNull())
	{
		LLAvatarPropertiesProcessor::getInstance()->removeObserver(getAvatarId(), this);
	}

	setAvatarId(avatar_id);

	resetData();
	resetControls();
	scrollToTop();

	setClassifiedId(key["classified_id"]);
	setClassifiedName(key["classified_name"]);
	setDescription(key["classified_desc"]);
	setSnapshotId(key["classified_snapshot_id"]);
	setFromSearch(key["from_search"]);

	llinfos << "Opening classified [" << getClassifiedName() << "] (" << getClassifiedId() << ")" << llendl;

	LLAvatarPropertiesProcessor::getInstance()->addObserver(getAvatarId(), this);
	LLAvatarPropertiesProcessor::getInstance()->sendClassifiedInfoRequest(getClassifiedId());
	gGenericDispatcher.addHandler("classifiedclickthrough", &sClassifiedClickThrough);

	// While we're at it let's get the stats from the new table if that
	// capability exists.
	std::string url = gAgent.getRegion()->getCapability("SearchStatRequest");
	if (!url.empty())
	{
		llinfos << "Classified stat request via capability" << llendl;
		LLSD body;
		body["classified_id"] = getClassifiedId();
		LLHTTPClient::post(url, body, new LLClassifiedStatsResponder(getClassifiedId()));
	}

	// Update classified click stats.
	// *TODO: Should we do this when opening not from search?
	sendClickMessage("profile");

	setInfoLoaded(false);
}

void LLPanelClassifiedInfo::processProperties(void* data, EAvatarProcessorType type)
{
	if(APT_CLASSIFIED_INFO == type)
	{
		LLAvatarClassifiedInfo* c_info = static_cast<LLAvatarClassifiedInfo*>(data);
		if(c_info && getClassifiedId() == c_info->classified_id)
		{
			setClassifiedName(c_info->name);
			setDescription(c_info->description);
			setSnapshotId(c_info->snapshot_id);
			setParcelId(c_info->parcel_id);
			setPosGlobal(c_info->pos_global);
			setSimName(c_info->sim_name);

			setClassifiedLocation(createLocationText(c_info->parcel_name, c_info->sim_name, c_info->pos_global));
			childSetValue("category", LLClassifiedInfo::sCategories[c_info->category]);

			static std::string mature_str = getString("type_mature");
			static std::string pg_str = getString("type_pg");
			static LLUIString  price_str = getString("l$_price");
			static std::string date_fmt = getString("date_fmt");

			bool mature = is_cf_mature(c_info->flags);
			childSetValue("content_type", mature ? mature_str : pg_str);
			getChild<LLIconCtrl>("content_type_moderate")->setVisible(mature);
			getChild<LLIconCtrl>("content_type_general")->setVisible(!mature);

			std::string auto_renew_str = is_cf_auto_renew(c_info->flags) ? 
				getString("auto_renew_on") : getString("auto_renew_off");
			childSetValue("auto_renew", auto_renew_str);

			price_str.setArg("[PRICE]", llformat("%d", c_info->price_for_listing));
			childSetValue("price_for_listing", LLSD(price_str));

			std::string date_str = date_fmt;
			LLStringUtil::format(date_str, LLSD().with("datetime", (S32) c_info->creation_date));
			childSetText("creation_date", date_str);

			setInfoLoaded(true);
		}
	}
}

void LLPanelClassifiedInfo::resetData()
{
	setClassifiedName(LLStringUtil::null);
	setDescription(LLStringUtil::null);
	setClassifiedLocation(LLStringUtil::null);
	setClassifiedId(LLUUID::null);
	setSnapshotId(LLUUID::null);
	setPosGlobal(LLVector3d::zero);
	setParcelId(LLUUID::null);
	setSimName(LLStringUtil::null);
	setFromSearch(false);

	// reset click stats
	mTeleportClicksOld	= 0;
	mMapClicksOld		= 0;
	mProfileClicksOld	= 0;
	mTeleportClicksNew	= 0;
	mMapClicksNew		= 0;
	mProfileClicksNew	= 0;

	childSetText("category", LLStringUtil::null);
	childSetText("content_type", LLStringUtil::null);
	childSetText("click_through_text", LLStringUtil::null);
	childSetText("price_for_listing", LLStringUtil::null);
	childSetText("auto_renew", LLStringUtil::null);
	childSetText("creation_date", LLStringUtil::null);
	childSetText("click_through_text", LLStringUtil::null);
	getChild<LLIconCtrl>("content_type_moderate")->setVisible(FALSE);
	getChild<LLIconCtrl>("content_type_general")->setVisible(FALSE);
}

void LLPanelClassifiedInfo::resetControls()
{
	bool is_self = getAvatarId() == gAgent.getID();

	childSetEnabled("edit_btn", is_self);
	childSetVisible("edit_btn", is_self);
	childSetVisible("price_layout_panel", is_self);
	childSetVisible("clickthrough_layout_panel", is_self);
}

void LLPanelClassifiedInfo::setClassifiedName(const std::string& name)
{
	childSetValue("classified_name", name);
}

std::string LLPanelClassifiedInfo::getClassifiedName()
{
	return childGetValue("classified_name").asString();
}

void LLPanelClassifiedInfo::setDescription(const std::string& desc)
{
	childSetValue("classified_desc", desc);
}

std::string LLPanelClassifiedInfo::getDescription()
{
	return childGetValue("classified_desc").asString();
}

void LLPanelClassifiedInfo::setClassifiedLocation(const std::string& location)
{
	childSetValue("classified_location", location);
}

std::string LLPanelClassifiedInfo::getClassifiedLocation()
{
	return childGetValue("classified_location").asString();
}

void LLPanelClassifiedInfo::setSnapshotId(const LLUUID& id)
{
	mSnapshotCtrl->setValue(id);
	mSnapshotStreched = false;
}

void LLPanelClassifiedInfo::draw()
{
	LLPanel::draw();

	// Stretch in draw because it takes some time to load a texture,
	// going to try to stretch snapshot until texture is loaded
	if(!mSnapshotStreched)
	{
		stretchSnapshot();
	}
}

LLUUID LLPanelClassifiedInfo::getSnapshotId()
{
	return childGetValue("classified_snapshot").asUUID();
}

// static
void LLPanelClassifiedInfo::setClickThrough(
	const LLUUID& classified_id,
	S32 teleport,
	S32 map,
	S32 profile,
	bool from_new_table)
{
	llinfos << "Click-through data for classified " << classified_id << " arrived: ["
			<< teleport << ", " << map << ", " << profile << "] ("
			<< (from_new_table ? "new" : "old") << ")" << llendl;

	for (panel_list_t::iterator iter = sAllPanels.begin(); iter != sAllPanels.end(); ++iter)
	{
		LLPanelClassifiedInfo* self = *iter;
		if (self->getClassifiedId() != classified_id)
		{
			continue;
		}

		// *HACK: Skip LLPanelClassifiedEdit instances: they don't display clicks data.
		// Those instances should not be in the list at all.
		if (typeid(*self) != typeid(LLPanelClassifiedInfo))
		{
			continue;
		}

		llinfos << "Updating classified info panel" << llendl;

		// We need to check to see if the data came from the new stat_table 
		// or the old classified table. We also need to cache the data from 
		// the two separate sources so as to display the aggregate totals.

		if (from_new_table)
		{
			self->mTeleportClicksNew = teleport;
			self->mMapClicksNew = map;
			self->mProfileClicksNew = profile;
		}
		else
		{
			self->mTeleportClicksOld = teleport;
			self->mMapClicksOld = map;
			self->mProfileClicksOld = profile;
		}

		static LLUIString ct_str = self->getString("click_through_text_fmt");

		ct_str.setArg("[TELEPORT]",	llformat("%d", self->mTeleportClicksNew + self->mTeleportClicksOld));
		ct_str.setArg("[MAP]",		llformat("%d", self->mMapClicksNew + self->mMapClicksOld));
		ct_str.setArg("[PROFILE]",	llformat("%d", self->mProfileClicksNew + self->mProfileClicksOld));

		self->childSetText("click_through_text", ct_str.getString());
		// *HACK: remove this when there is enough room for click stats in the info panel
		self->childSetToolTip("click_through_text", ct_str.getString());  

		llinfos << "teleport: " << llformat("%d", self->mTeleportClicksNew + self->mTeleportClicksOld)
				<< ", map: "    << llformat("%d", self->mMapClicksNew + self->mMapClicksOld)
				<< ", profile: " << llformat("%d", self->mProfileClicksNew + self->mProfileClicksOld)
				<< llendl;
	}
}

// static
std::string LLPanelClassifiedInfo::createLocationText(
	const std::string& original_name, 
	const std::string& sim_name, 
	const LLVector3d& pos_global)
{
	std::string location_text;
	
	location_text.append(original_name);

	if (!sim_name.empty())
	{
		if (!location_text.empty()) 
			location_text.append(", ");
		location_text.append(sim_name);
	}

	if (!location_text.empty()) 
		location_text.append(" ");

	if (!pos_global.isNull())
	{
		S32 region_x = llround((F32)pos_global.mdV[VX]) % REGION_WIDTH_UNITS;
		S32 region_y = llround((F32)pos_global.mdV[VY]) % REGION_WIDTH_UNITS;
		S32 region_z = llround((F32)pos_global.mdV[VZ]);
		location_text.append(llformat(" (%d, %d, %d)", region_x, region_y, region_z));
	}

	return location_text;
}

void LLPanelClassifiedInfo::stretchSnapshot()
{
	// *NOTE dzaporozhan
	// Could be moved to LLTextureCtrl

	LLViewerFetchedTexture* texture = mSnapshotCtrl->getTexture();

	if(!texture)
	{
		return;
	}

	if(0 == texture->getOriginalWidth() || 0 == texture->getOriginalHeight())
	{
		// looks like texture is not loaded yet
		return;
	}

	LLRect rc = mSnapshotRect;
	// *HACK dzaporozhan
	// LLTextureCtrl uses BTN_HEIGHT_SMALL as bottom for texture which causes
	// drawn texture to be smaller than expected. (see LLTextureCtrl::draw())
	// Lets increase texture height to force texture look as expected.
	rc.mBottom -= BTN_HEIGHT_SMALL;

	F32 t_width = texture->getFullWidth();
	F32 t_height = texture->getFullHeight();

	F32 ratio = llmin<F32>( (rc.getWidth() / t_width), (rc.getHeight() / t_height) );

	t_width *= ratio;
	t_height *= ratio;

	rc.setCenterAndSize(rc.getCenterX(), rc.getCenterY(), llfloor(t_width), llfloor(t_height));
	mSnapshotCtrl->setShape(rc);

	mSnapshotStreched = true;
}

LLRect LLPanelClassifiedInfo::getDefaultSnapshotRect()
{
	// Using scroll container makes getting default rect a hard task
	// because rect in postBuild() and in first reshape() is not the same.
	// Using snapshot_panel makes it easier to reshape snapshot.
	return getChild<LLUICtrl>("snapshot_panel")->getLocalRect();
}

void LLPanelClassifiedInfo::scrollToTop()
{
	LLScrollContainer* scrollContainer = findChild<LLScrollContainer>("profile_scroll");
	if (scrollContainer)
		scrollContainer->goToTop();
}

// static
// *TODO: move out of the panel
void LLPanelClassifiedInfo::sendClickMessage(
		const std::string& type,
		bool from_search,
		const LLUUID& classified_id,
		const LLUUID& parcel_id,
		const LLVector3d& global_pos,
		const std::string& sim_name)
{
	// You're allowed to click on your own ads to reassure yourself
	// that the system is working.
	LLSD body;
	body["type"]			= type;
	body["from_search"]		= from_search;
	body["classified_id"]	= classified_id;
	body["parcel_id"]		= parcel_id;
	body["dest_pos_global"]	= global_pos.getValue();
	body["region_name"]		= sim_name;

	std::string url = gAgent.getRegion()->getCapability("SearchStatTracking");
	llinfos << "Sending click msg via capability (url=" << url << ")" << llendl;
	llinfos << "body: [" << body << "]" << llendl;
	LLHTTPClient::post(url, body, new LLClassifiedClickMessageResponder());
}

void LLPanelClassifiedInfo::sendClickMessage(const std::string& type)
{
	sendClickMessage(
		type,
		fromSearch(),
		getClassifiedId(),
		getParcelId(),
		getPosGlobal(),
		getSimName());
}

void LLPanelClassifiedInfo::onMapClick()
{
	sendClickMessage("map");
	LLFloaterWorldMap::getInstance()->trackLocation(getPosGlobal());
	LLFloaterReg::showInstance("world_map", "center");
}

void LLPanelClassifiedInfo::onTeleportClick()
{
	if (!getPosGlobal().isExactlyZero())
	{
		sendClickMessage("teleport");
		gAgent.teleportViaLocation(getPosGlobal());
		LLFloaterWorldMap::getInstance()->trackLocation(getPosGlobal());
	}
}

void LLPanelClassifiedInfo::onExit()
{
	LLAvatarPropertiesProcessor::getInstance()->removeObserver(getAvatarId(), this);
	gGenericDispatcher.addHandler("classifiedclickthrough", NULL); // deregister our handler
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////

static const S32 CB_ITEM_MATURE = 0;
static const S32 CB_ITEM_PG	   = 1;

LLPanelClassifiedEdit::LLPanelClassifiedEdit()
 : LLPanelClassifiedInfo()
 , mIsNew(false)
 , mIsNewWithErrors(false)
 , mCanClose(false)
 , mPublishFloater(NULL)
{
}

LLPanelClassifiedEdit::~LLPanelClassifiedEdit()
{
}

//static
LLPanelClassifiedEdit* LLPanelClassifiedEdit::create()
{
	LLPanelClassifiedEdit* panel = new LLPanelClassifiedEdit();
	LLUICtrlFactory::getInstance()->buildPanel(panel, "panel_edit_classified.xml");
	return panel;
}

BOOL LLPanelClassifiedEdit::postBuild()
{
	LLPanelClassifiedInfo::postBuild();

	LLTextureCtrl* snapshot = getChild<LLTextureCtrl>("classified_snapshot");
	snapshot->setOnSelectCallback(boost::bind(&LLPanelClassifiedEdit::onChange, this));

	LLUICtrl* edit_icon = getChild<LLUICtrl>("edit_icon");
	snapshot->setMouseEnterCallback(boost::bind(&LLPanelClassifiedEdit::onTexturePickerMouseEnter, this, edit_icon));
	snapshot->setMouseLeaveCallback(boost::bind(&LLPanelClassifiedEdit::onTexturePickerMouseLeave, this, edit_icon));
	edit_icon->setVisible(false);

	LLLineEditor* line_edit = getChild<LLLineEditor>("classified_name");
	line_edit->setKeystrokeCallback(boost::bind(&LLPanelClassifiedEdit::onChange, this), NULL);

	LLTextEditor* text_edit = getChild<LLTextEditor>("classified_desc");
	text_edit->setKeystrokeCallback(boost::bind(&LLPanelClassifiedEdit::onChange, this));

	LLComboBox* combobox = getChild<LLComboBox>( "category");
	LLClassifiedInfo::cat_map::iterator iter;
	for (iter = LLClassifiedInfo::sCategories.begin();
		iter != LLClassifiedInfo::sCategories.end();
		iter++)
	{
		combobox->add(LLTrans::getString(iter->second));
	}

	combobox->setCommitCallback(boost::bind(&LLPanelClassifiedEdit::onChange, this));

	childSetCommitCallback("content_type", boost::bind(&LLPanelClassifiedEdit::onChange, this), NULL);
	childSetCommitCallback("price_for_listing", boost::bind(&LLPanelClassifiedEdit::onChange, this), NULL);
	childSetCommitCallback("auto_renew", boost::bind(&LLPanelClassifiedEdit::onChange, this), NULL);

	childSetAction("save_changes_btn", boost::bind(&LLPanelClassifiedEdit::onSaveClick, this));
	childSetAction("set_to_curr_location_btn", boost::bind(&LLPanelClassifiedEdit::onSetLocationClick, this));

	mSnapshotCtrl->setOnSelectCallback(boost::bind(&LLPanelClassifiedEdit::onTextureSelected, this));

	return TRUE;
}

void LLPanelClassifiedEdit::fillIn(const LLSD& key)
{
	setAvatarId(gAgent.getID());

	if(key.isUndefined())
	{
		setPosGlobal(gAgent.getPositionGlobal());

		LLUUID snapshot_id = LLUUID::null;
		std::string desc;
		LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel();

		if(parcel)
		{
			desc = parcel->getDesc();
			snapshot_id = parcel->getSnapshotID();
		}

		std::string region_name = LLTrans::getString("ClassifiedUpdateAfterPublish");
		LLViewerRegion* region = gAgent.getRegion();
		if (region)
		{
			region_name = region->getName();
		}

		childSetValue("classified_name", makeClassifiedName());
		childSetValue("classified_desc", desc);
		setSnapshotId(snapshot_id);
		setClassifiedLocation(createLocationText(getLocationNotice(), region_name, getPosGlobal()));
		// server will set valid parcel id
		setParcelId(LLUUID::null);
	}
	else
	{
		setClassifiedId(key["classified_id"]);
		setClassifiedName(key["name"]);
		setDescription(key["desc"]);
		setSnapshotId(key["snapshot_id"]);
		setCategory((U32)key["category"].asInteger());
		setContentType((U32)key["content_type"].asInteger());
		setClassifiedLocation(key["location_text"]);
		childSetValue("auto_renew", key["auto_renew"]);
		childSetValue("price_for_listing", key["price_for_listing"].asInteger());
	}
}

void LLPanelClassifiedEdit::onOpen(const LLSD& key)
{
	mIsNew = key.isUndefined();
	
	scrollToTop();

	// classified is not created yet
	bool is_new = isNew() || isNewWithErrors();

	if(is_new)
	{
		resetData();
		resetControls();

		fillIn(key);

		if(isNew())
		{
			LLAvatarPropertiesProcessor::getInstance()->addObserver(getAvatarId(), this);
		}
	}
	else
	{
		LLPanelClassifiedInfo::onOpen(key);
	}

	std::string save_btn_label = is_new ? getString("publish_label") : getString("save_label");
	childSetLabelArg("save_changes_btn", "[LABEL]", save_btn_label);

	enableVerbs(is_new);
	enableEditing(is_new);
	showEditing(!is_new);
	resetDirty();
	setInfoLoaded(false);
}

void LLPanelClassifiedEdit::processProperties(void* data, EAvatarProcessorType type)
{
	if(APT_CLASSIFIED_INFO == type)
	{
		LLAvatarClassifiedInfo* c_info = static_cast<LLAvatarClassifiedInfo*>(data);
		if(c_info && getClassifiedId() == c_info->classified_id)
		{
			// see LLPanelClassifiedEdit::sendUpdate() for notes
			mIsNewWithErrors = false;
			// for just created classified - panel will probably be closed when we get here.
			if(!getVisible())
			{
				return;
			}

			enableEditing(true);

			setClassifiedName(c_info->name);
			setDescription(c_info->description);
			setSnapshotId(c_info->snapshot_id);
			setPosGlobal(c_info->pos_global);

			setClassifiedLocation(createLocationText(c_info->parcel_name, c_info->sim_name, c_info->pos_global));
			// *HACK see LLPanelClassifiedEdit::sendUpdate()
			setCategory(c_info->category - 1);

			bool mature = is_cf_mature(c_info->flags);
			bool auto_renew = is_cf_auto_renew(c_info->flags);

			setContentType(mature ? CB_ITEM_MATURE : CB_ITEM_PG);
			childSetValue("auto_renew", auto_renew);
			childSetValue("price_for_listing", c_info->price_for_listing);
			childSetEnabled("price_for_listing", isNew());

			resetDirty();
			setInfoLoaded(true);
			enableVerbs(false);

			// for just created classified - in case user opened edit panel before processProperties() callback 
			childSetLabelArg("save_changes_btn", "[LABEL]", getString("save_label"));
		}
	}
}

BOOL LLPanelClassifiedEdit::isDirty() const
{
	if(mIsNew) 
	{
		return TRUE;
	}

	BOOL dirty = false;

	dirty |= LLPanelClassifiedInfo::isDirty();
	dirty |= getChild<LLUICtrl>("classified_snapshot")->isDirty();
	dirty |= getChild<LLUICtrl>("classified_name")->isDirty();
	dirty |= getChild<LLUICtrl>("classified_desc")->isDirty();
	dirty |= getChild<LLUICtrl>("category")->isDirty();
	dirty |= getChild<LLUICtrl>("content_type")->isDirty();
	dirty |= getChild<LLUICtrl>("auto_renew")->isDirty();
	dirty |= getChild<LLUICtrl>("price_for_listing")->isDirty();

	return dirty;
}

void LLPanelClassifiedEdit::resetDirty()
{
	LLPanelClassifiedInfo::resetDirty();
	getChild<LLUICtrl>("classified_snapshot")->resetDirty();
	getChild<LLUICtrl>("classified_name")->resetDirty();

	LLTextEditor* desc = getChild<LLTextEditor>("classified_desc");
	// call blockUndo() to really reset dirty(and make isDirty work as intended)
	desc->blockUndo();
	desc->resetDirty();

	getChild<LLUICtrl>("category")->resetDirty();
	getChild<LLUICtrl>("content_type")->resetDirty();
	getChild<LLUICtrl>("auto_renew")->resetDirty();
	getChild<LLUICtrl>("price_for_listing")->resetDirty();
}

void LLPanelClassifiedEdit::setSaveCallback(const commit_signal_t::slot_type& cb)
{
	mSaveButtonClickedSignal.connect(cb);
}

void LLPanelClassifiedEdit::setCancelCallback(const commit_signal_t::slot_type& cb)
{
	getChild<LLButton>("cancel_btn")->setClickedCallback(cb);
}

void LLPanelClassifiedEdit::resetControls()
{
	LLPanelClassifiedInfo::resetControls();

	getChild<LLComboBox>("category")->setCurrentByIndex(0);
	getChild<LLComboBox>("content_type")->setCurrentByIndex(0);
	childSetValue("auto_renew", false);
	childSetValue("price_for_listing", MINIMUM_PRICE_FOR_LISTING);
	childSetEnabled("price_for_listing", TRUE);
}

bool LLPanelClassifiedEdit::canClose()
{
	return mCanClose;
}

void LLPanelClassifiedEdit::draw()
{
	LLPanel::draw();

	// Need to re-stretch on every draw because LLTextureCtrl::onSelectCallback
	// does not trigger callbacks when user navigates through images.
	stretchSnapshot();
}

void LLPanelClassifiedEdit::stretchSnapshot()
{
	LLPanelClassifiedInfo::stretchSnapshot();

	getChild<LLUICtrl>("edit_icon")->setShape(mSnapshotCtrl->getRect());
}

U32 LLPanelClassifiedEdit::getContentType()
{
	LLComboBox* ct_cb = getChild<LLComboBox>("content_type");
	return ct_cb->getCurrentIndex();
}

void LLPanelClassifiedEdit::setContentType(U32 content_type)
{
	LLComboBox* ct_cb = getChild<LLComboBox>("content_type");
	ct_cb->setCurrentByIndex(content_type);
	ct_cb->resetDirty();
}

bool LLPanelClassifiedEdit::getAutoRenew()
{
	return childGetValue("auto_renew").asBoolean();
}

void LLPanelClassifiedEdit::sendUpdate()
{
	LLAvatarClassifiedInfo c_data;

	if(getClassifiedId().isNull())
	{
		setClassifiedId(LLUUID::generateNewID());
	}

	c_data.agent_id = gAgent.getID();
	c_data.classified_id = getClassifiedId();
	// *HACK 
	// Categories on server start with 1 while combo-box index starts with 0
	c_data.category = getCategory() + 1;
	c_data.name = getClassifiedName();
	c_data.description = getDescription();
	c_data.parcel_id = getParcelId();
	c_data.snapshot_id = getSnapshotId();
	c_data.pos_global = getPosGlobal();
	c_data.flags = getFlags();
	c_data.price_for_listing = getPriceForListing();

	LLAvatarPropertiesProcessor::getInstance()->sendClassifiedInfoUpdate(&c_data);
	
	if(isNew())
	{
		// Lets assume there will be some error.
		// Successful sendClassifiedInfoUpdate will trigger processProperties and
		// let us know there was no error.
		mIsNewWithErrors = true;
	}
}

U32 LLPanelClassifiedEdit::getCategory()
{
	LLComboBox* cat_cb = getChild<LLComboBox>("category");
	return cat_cb->getCurrentIndex();
}

void LLPanelClassifiedEdit::setCategory(U32 category)
{
	LLComboBox* cat_cb = getChild<LLComboBox>("category");
	cat_cb->setCurrentByIndex(category);
	cat_cb->resetDirty();
}

U8 LLPanelClassifiedEdit::getFlags()
{
	bool auto_renew = childGetValue("auto_renew").asBoolean();

	LLComboBox* content_cb = getChild<LLComboBox>("content_type");
	bool mature = content_cb->getCurrentIndex() == CB_ITEM_MATURE;
	
	return pack_classified_flags_request(auto_renew, false, mature, false);
}

void LLPanelClassifiedEdit::enableVerbs(bool enable)
{
	childSetEnabled("save_changes_btn", enable);
}

void LLPanelClassifiedEdit::enableEditing(bool enable)
{
	childSetEnabled("classified_snapshot", enable);
	childSetEnabled("classified_name", enable);
	childSetEnabled("classified_desc", enable);
	childSetEnabled("set_to_curr_location_btn", enable);
	childSetEnabled("category", enable);
	childSetEnabled("content_type", enable);
	childSetEnabled("price_for_listing", enable);
	childSetEnabled("auto_renew", enable);
}

void LLPanelClassifiedEdit::showEditing(bool show)
{
	childSetVisible("price_for_listing_label", show);
	childSetVisible("price_for_listing", show);
}

std::string LLPanelClassifiedEdit::makeClassifiedName()
{
	std::string name;

	LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel();
	if(parcel)
	{
		name = parcel->getName();
	}

	if(!name.empty())
	{
		return name;
	}

	LLViewerRegion* region = gAgent.getRegion();
	if(region)
	{
		name = region->getName();
	}

	return name;
}

S32 LLPanelClassifiedEdit::getPriceForListing()
{
	return childGetValue("price_for_listing").asInteger();
}

void LLPanelClassifiedEdit::setPriceForListing(S32 price)
{
	childSetValue("price_for_listing", price);
}

void LLPanelClassifiedEdit::onSetLocationClick()
{
	setPosGlobal(gAgent.getPositionGlobal());
	setParcelId(LLUUID::null);

	std::string region_name = LLTrans::getString("ClassifiedUpdateAfterPublish");
	LLViewerRegion* region = gAgent.getRegion();
	if (region)
	{
		region_name = region->getName();
	}

	setClassifiedLocation(createLocationText(getLocationNotice(), region_name, getPosGlobal()));

	// mark classified as dirty
	setValue(LLSD());

	onChange();
}

void LLPanelClassifiedEdit::onChange()
{
	enableVerbs(isDirty());
}

void LLPanelClassifiedEdit::onSaveClick()
{
	mCanClose = false;

	if(!isValidName())
	{
		notifyInvalidName();
		return;
	}
	if(isNew() || isNewWithErrors())
	{
		if(gStatusBar->getBalance() < getPriceForListing())
		{
			LLNotificationsUtil::add("ClassifiedInsufficientFunds");
			return;
		}

		mPublishFloater = LLFloaterReg::findTypedInstance<LLPublishClassifiedFloater>(
			"publish_classified", LLSD());

		if(!mPublishFloater)
		{
			mPublishFloater = LLFloaterReg::getTypedInstance<LLPublishClassifiedFloater>(
				"publish_classified", LLSD());

			mPublishFloater->setPublishClickedCallback(boost::bind
				(&LLPanelClassifiedEdit::onPublishFloaterPublishClicked, this));
		}

		// set spinner value before it has focus or value wont be set
		mPublishFloater->setPrice(getPriceForListing());
		mPublishFloater->openFloater(mPublishFloater->getKey());
		mPublishFloater->center();
	}
	else
	{
		doSave();
	}
}

void LLPanelClassifiedEdit::doSave()
{
	mCanClose = true;
	sendUpdate();
	resetDirty();

	mSaveButtonClickedSignal(this, LLSD());
}

void LLPanelClassifiedEdit::onPublishFloaterPublishClicked()
{
	setPriceForListing(mPublishFloater->getPrice());

	doSave();
}

std::string LLPanelClassifiedEdit::getLocationNotice()
{
	static std::string location_notice = getString("location_notice");
	return location_notice;
}

bool LLPanelClassifiedEdit::isValidName()
{
	std::string name = getClassifiedName();
	if (name.empty())
	{
		return false;
	}
	if (!isalnum(name[0]))
	{
		return false;
	}

	return true;
}

void LLPanelClassifiedEdit::notifyInvalidName()
{
	std::string name = getClassifiedName();
	if (name.empty())
	{
		LLNotificationsUtil::add("BlankClassifiedName");
	}
	else if (!isalnum(name[0]))
	{
		LLNotificationsUtil::add("ClassifiedMustBeAlphanumeric");
	}
}

void LLPanelClassifiedEdit::onTexturePickerMouseEnter(LLUICtrl* ctrl)
{
	ctrl->setVisible(TRUE);
}

void LLPanelClassifiedEdit::onTexturePickerMouseLeave(LLUICtrl* ctrl)
{
	ctrl->setVisible(FALSE);
}

void LLPanelClassifiedEdit::onTextureSelected()
{
	setSnapshotId(mSnapshotCtrl->getValue().asUUID());
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////

LLPublishClassifiedFloater::LLPublishClassifiedFloater(const LLSD& key)
 : LLFloater(key)
{
}

LLPublishClassifiedFloater::~LLPublishClassifiedFloater()
{
}

BOOL LLPublishClassifiedFloater::postBuild()
{
	LLFloater::postBuild();

	childSetAction("publish_btn", boost::bind(&LLFloater::closeFloater, this, false));
	childSetAction("cancel_btn", boost::bind(&LLFloater::closeFloater, this, false));

	return TRUE;
}

void LLPublishClassifiedFloater::setPrice(S32 price)
{
	childSetValue("price_for_listing", price);
}

S32 LLPublishClassifiedFloater::getPrice()
{
	return childGetValue("price_for_listing").asInteger();
}

void LLPublishClassifiedFloater::setPublishClickedCallback(const commit_signal_t::slot_type& cb)
{
	getChild<LLButton>("publish_btn")->setClickedCallback(cb);
}

void LLPublishClassifiedFloater::setCancelClickedCallback(const commit_signal_t::slot_type& cb)
{
	getChild<LLButton>("cancel_btn")->setClickedCallback(cb);
}

//EOF
