/**
 * @file llfloaterbuyland.cpp
 * @brief LLFloaterBuyLand 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$
 */

#include "llviewerprecompiledheaders.h"

#include "llfloaterbuyland.h"

// viewer includes
#include "llagent.h"
#include "llbutton.h"
#include "llcachename.h"
#include "llcheckboxctrl.h"
#include "llcombobox.h"
#include "llconfirmationmanager.h"
#include "llcurrencyuimanager.h"
#include "llfloater.h"
#include "llfloaterreg.h"
#include "llfloatertools.h"
#include "llframetimer.h"
#include "lliconctrl.h"
#include "lllineeditor.h"
#include "llnotificationsutil.h"
#include "llparcel.h"
#include "llslurl.h"
#include "llstatusbar.h"
#include "lltextbox.h"
#include "lltexturectrl.h"
#include "lltrans.h"
#include "llviewchildren.h"
#include "llviewercontrol.h"
#include "lluictrlfactory.h"
#include "llviewerparcelmgr.h"
#include "llviewerregion.h"
#include "llviewertexteditor.h"
#include "llviewerwindow.h"
#include "llweb.h"
#include "llwindow.h"
#include "llworld.h"
#include "llxmlrpctransaction.h"
#include "llviewernetwork.h"
#include "roles_constants.h"

// NOTE: This is duplicated in lldatamoney.cpp ...
const F32 GROUP_LAND_BONUS_FACTOR = 1.1f;

class LLFloaterBuyLandUI
:   public LLFloater
{
public:
    LLFloaterBuyLandUI(const LLSD& key);
    virtual ~LLFloaterBuyLandUI();

    /*virtual*/ void onClose(bool app_quitting);

    // Left padding for maturity rating icon.
    static const S32 ICON_PAD = 2;

private:
    class SelectionObserver : public LLParcelObserver
    {
    public:
        SelectionObserver(LLFloaterBuyLandUI* floater) : mFloater(floater) {}
        virtual void changed();
    private:
        LLFloaterBuyLandUI* mFloater;
    };

private:
    SelectionObserver mParcelSelectionObserver;
    LLViewerRegion* mRegion;
    LLParcelSelectionHandle mParcel;
    bool            mIsClaim;
    bool            mIsForGroup;

    bool            mCanBuy;
    bool            mCannotBuyIsError;
    std::string     mCannotBuyReason;
    std::string     mCannotBuyURI;

    bool            mBought;

    // information about the agent
    S32             mAgentCommittedTier;
    S32             mAgentCashBalance;
    bool            mAgentHasNeverOwnedLand;

    // information about the parcel
    bool            mParcelValid;
    bool            mParcelIsForSale;
    bool            mParcelIsGroupLand;
    S32             mParcelGroupContribution;
    S32             mParcelPrice;
    S32             mParcelActualArea;
    S32             mParcelBillableArea;
    S32             mParcelSupportedObjects;
    bool            mParcelSoldWithObjects;
    std::string     mParcelLocation;
    LLUUID          mParcelSnapshot;
    std::string     mParcelSellerName;

    // user's choices
    S32             mUserPlanChoice;

    // from website
    bool            mSiteValid;
    bool            mSiteMembershipUpgrade;
    std::string     mSiteMembershipAction;
    std::vector<std::string>
                    mSiteMembershipPlanIDs;
    std::vector<std::string>
                    mSiteMembershipPlanNames;
    bool            mSiteLandUseUpgrade;
    std::string     mSiteLandUseAction;
    std::string     mSiteConfirm;

    // values in current Preflight transaction... used to avoid extra
    // preflights when the parcel manager goes update crazy
    S32             mPreflightAskBillableArea;
    S32             mPreflightAskCurrencyBuy;

    LLViewChildren      mChildren;
    LLCurrencyUIManager mCurrency;

    enum TransactionType
    {
        TransactionPreflight,
        TransactionCurrency,
        TransactionBuy
    };
    LLXMLRPCTransaction* mTransaction;
    TransactionType      mTransactionType;

    LLViewerParcelMgr::ParcelBuyInfo*   mParcelBuyInfo;

public:
    void setForGroup(bool is_for_group);
    void setParcel(LLViewerRegion* region, LLParcelSelectionHandle parcel);

    void updateAgentInfo();
    void updateParcelInfo();
    void updateCovenantInfo();
    static void onChangeAgreeCovenant(LLUICtrl* ctrl, void* user_data);
    void updateFloaterCovenant(const LLTextBase* source, const LLUUID &asset_id);
    void updateFloaterCovenantText(const std::string& string, const LLUUID &asset_id);
    void updateFloaterEstateName(const std::string& name);
    void updateFloaterLastModified(const std::string& text);
    void updateFloaterEstateOwnerName(const std::string& name);
    void updateWebSiteInfo();
    void finishWebSiteInfo();

    void runWebSitePrep(const std::string& password);
    void finishWebSitePrep();
    void sendBuyLand();

    void updateNames();
    // Name cache callback
    void updateGroupName(const LLUUID& id,
                         const std::string& name,
                         bool is_group);

    void refreshUI();

    void startTransaction(TransactionType type, const LLSD& params);
    bool checkTransaction();

    void tellUserError(const std::string& message, const std::string& uri);

    virtual bool postBuild();

    void startBuyPreConfirm();
    void startBuyPostConfirm(const std::string& password);

    void onClickBuy();
    void onClickCancel();
     void onClickErrorWeb();

    virtual void draw();
    virtual bool canClose();

    void onVisibilityChanged ( const LLSD& new_visibility );

private:
    void onCovenantTextUpdated(const LLUUID& asset_id);
};

// static
void LLFloaterBuyLand::buyLand(
    LLViewerRegion* region, LLParcelSelectionHandle parcel, bool is_for_group)
{
    if(is_for_group && !gAgent.hasPowerInActiveGroup(GP_LAND_DEED))
    {
        LLNotificationsUtil::add("OnlyOfficerCanBuyLand");
        return;
    }

    LLFloaterBuyLandUI* ui = LLFloaterReg::showTypedInstance<LLFloaterBuyLandUI>("buy_land");
    if (ui)
    {
        ui->setForGroup(is_for_group);
        ui->setParcel(region, parcel);
    }
}

// static
void LLFloaterBuyLand::updateCovenant(const LLTextBase* source, const LLUUID& asset_id)
{
    if (LLFloaterBuyLandUI* floater = LLFloaterReg::findTypedInstance<LLFloaterBuyLandUI>("buy_land"))
    {
        floater->updateFloaterCovenant(source, asset_id);
    }
}

// static
void LLFloaterBuyLand::updateCovenantText(const std::string& string, const LLUUID &asset_id)
{
    LLFloaterBuyLandUI* floater = LLFloaterReg::findTypedInstance<LLFloaterBuyLandUI>("buy_land");
    if (floater)
    {
        floater->updateFloaterCovenantText(string, asset_id);
    }
}

// static
void LLFloaterBuyLand::updateEstateName(const std::string& name)
{
    LLFloaterBuyLandUI* floater = LLFloaterReg::findTypedInstance<LLFloaterBuyLandUI>("buy_land");
    if (floater)
    {
        floater->updateFloaterEstateName(name);
    }
}

// static
void LLFloaterBuyLand::updateLastModified(const std::string& text)
{
    LLFloaterBuyLandUI* floater = LLFloaterReg::findTypedInstance<LLFloaterBuyLandUI>("buy_land");
    if (floater)
    {
        floater->updateFloaterLastModified(text);
    }
}

// static
void LLFloaterBuyLand::updateEstateOwnerName(const std::string& name)
{
    LLFloaterBuyLandUI* floater = LLFloaterReg::findTypedInstance<LLFloaterBuyLandUI>("buy_land");
    if (floater)
    {
        floater->updateFloaterEstateOwnerName(name);
    }
}

// static
LLFloater* LLFloaterBuyLand::buildFloater(const LLSD& key)
{
    LLFloaterBuyLandUI* floater = new LLFloaterBuyLandUI(key);
    return floater;
}

//----------------------------------------------------------------------------
// LLFloaterBuyLandUI
//----------------------------------------------------------------------------

#if LL_WINDOWS
// passing 'this' during construction generates a warning. The callee
// only uses the pointer to hold a reference to 'this' which is
// already valid, so this call does the correct thing. Disable the
// warning so that we can compile without generating a warning.
#pragma warning(disable : 4355)
#endif
LLFloaterBuyLandUI::LLFloaterBuyLandUI(const LLSD& key)
:   LLFloater(LLSD()),
    mParcelSelectionObserver(this),
    mParcel(0),
    mBought(false),
    mParcelValid(false), mSiteValid(false),
    mChildren(*this), mCurrency(*this), mTransaction(0),
    mParcelBuyInfo(0)
{
    LLViewerParcelMgr::getInstance()->addObserver(&mParcelSelectionObserver);

}

LLFloaterBuyLandUI::~LLFloaterBuyLandUI()
{
    LLViewerParcelMgr::getInstance()->removeObserver(&mParcelSelectionObserver);
    LLViewerParcelMgr::getInstance()->deleteParcelBuy(&mParcelBuyInfo);

    delete mTransaction;
}

// virtual
void LLFloaterBuyLandUI::onClose(bool app_quitting)
{
    // This object holds onto observer, transactions, and parcel state.
    // Despite being single_instance, destroy it to call destructors and clean
    // everything up.
    setVisible(false);
    destroy();
}

void LLFloaterBuyLandUI::SelectionObserver::changed()
{
    if (LLViewerParcelMgr::getInstance()->selectionEmpty())
    {
        mFloater->closeFloater();
    }
    else
    {
        mFloater->setParcel(LLViewerParcelMgr::getInstance()->getSelectionRegion(),
                            LLViewerParcelMgr::getInstance()->getParcelSelection());
    }
}


void LLFloaterBuyLandUI::updateAgentInfo()
{
    mAgentCommittedTier = gStatusBar->getSquareMetersCommitted();
    mAgentCashBalance = gStatusBar->getBalance();

    // *TODO: This is an approximation, we should send this value down
    // to the viewer. See SL-10728 for details.
    mAgentHasNeverOwnedLand = mAgentCommittedTier == 0;
}

void LLFloaterBuyLandUI::updateParcelInfo()
{
    LLParcel* parcel = mParcel->getParcel();
    mParcelValid = parcel && mRegion;
    mParcelIsForSale = false;
    mParcelIsGroupLand = false;
    mParcelGroupContribution = 0;
    mParcelPrice = 0;
    mParcelActualArea = 0;
    mParcelBillableArea = 0;
    mParcelSupportedObjects = 0;
    mParcelSoldWithObjects = false;
    mParcelLocation = "";
    mParcelSnapshot.setNull();
    mParcelSellerName = "";

    mCanBuy = false;
    mCannotBuyIsError = false;

    if (!mParcelValid)
    {
        mCannotBuyReason = getString("no_land_selected");
        return;
    }

    if (mParcel->getMultipleOwners())
    {
        mCannotBuyReason = getString("multiple_parcels_selected");
        return;
    }

    const LLUUID& parcelOwner = parcel->getOwnerID();

    mIsClaim = parcel->isPublic();
    if (!mIsClaim)
    {
        mParcelActualArea = parcel->getArea();
        mParcelIsForSale = parcel->getForSale();
        mParcelIsGroupLand = parcel->getIsGroupOwned();
        mParcelPrice = mParcelIsForSale ? parcel->getSalePrice() : 0;

        if (mParcelIsGroupLand)
        {
            LLUUID group_id = parcel->getGroupID();
            mParcelGroupContribution = gAgent.getGroupContribution(group_id);
        }
    }
    else
    {
        mParcelActualArea = mParcel->getClaimableArea();
        mParcelIsForSale = true;
        mParcelPrice = mParcelActualArea * parcel->getClaimPricePerMeter();
    }

    mParcelBillableArea =
        ll_round(mRegion->getBillableFactor() * mParcelActualArea);

    mParcelSupportedObjects = ll_round(
        parcel->getMaxPrimCapacity() * parcel->getParcelPrimBonus());
    // Can't have more than region max tasks, regardless of parcel
    // object bonus factor.
    LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion();
    if (region)
    {
        S32 max_tasks_per_region = (S32)region->getMaxTasks();
        mParcelSupportedObjects = llmin(mParcelSupportedObjects, max_tasks_per_region);
    }

    mParcelSoldWithObjects = parcel->getSellWithObjects();


    LLVector3 center = parcel->getCenterpoint();
    mParcelLocation = llformat("%s %d,%d",
                mRegion->getName().c_str(),
                (int)center[VX], (int)center[VY]
                );

    mParcelSnapshot = parcel->getSnapshotID();

    updateNames();

    bool haveEnoughCash = mParcelPrice <= mAgentCashBalance;
    S32 cashBuy = haveEnoughCash ? 0 : (mParcelPrice - mAgentCashBalance);
    mCurrency.setAmount(cashBuy, true);
    mCurrency.setZeroMessage(haveEnoughCash ? getString("none_needed") : LLStringUtil::null);

    // checks that we can buy the land

    if (mIsForGroup && !gAgent.hasPowerInActiveGroup(GP_LAND_DEED))
    {
        mCannotBuyReason = getString("cant_buy_for_group");
        return;
    }

    if (!mIsClaim)
    {
        const LLUUID& authorizedBuyer = parcel->getAuthorizedBuyerID();
        const LLUUID buyer = gAgent.getID();
        const LLUUID newOwner = mIsForGroup ? gAgent.getGroupID() : buyer;

        if (!mParcelIsForSale
            || (mParcelPrice == 0  &&  authorizedBuyer.isNull()))
        {

            mCannotBuyReason = getString("parcel_not_for_sale");
            return;
        }

        if (parcelOwner == newOwner)
        {
            if (mIsForGroup)
            {
                mCannotBuyReason = getString("group_already_owns");
            }
            else
            {
                mCannotBuyReason = getString("you_already_own");
            }
            return;
        }

        if (!authorizedBuyer.isNull() && buyer != authorizedBuyer)
        {
            // Maybe the parcel is set for sale to a group we are in.
            bool authorized_group =
                gAgent.hasPowerInGroup(authorizedBuyer,GP_LAND_DEED)
                && gAgent.hasPowerInGroup(authorizedBuyer,GP_LAND_SET_SALE_INFO);

            if (!authorized_group)
            {
                mCannotBuyReason = getString("set_to_sell_to_other");
                return;
            }
        }
    }
    else
    {
        if (mParcelActualArea == 0)
        {
            mCannotBuyReason = getString("no_public_land");
            return;
        }

        if (mParcel->hasOthersSelected())
        {
            // Policy: Must not have someone else's land selected
            mCannotBuyReason = getString("not_owned_by_you");
            return;
        }
    }

    mCanBuy = true;
}

void LLFloaterBuyLandUI::updateCovenantInfo()
{
    LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion();
    if (!region)
        return;

    U8 sim_access = region->getSimAccess();
    std::string rating = LLViewerRegion::accessToString(sim_access);

    LLTextBox* region_name = getChild<LLTextBox>("region_name_text");
    std::string region_name_txt = region->getName() + " ("+rating +")";
    region_name->setText(region_name_txt);

    LLIconCtrl* rating_icon = getChild<LLIconCtrl>("rating_icon");
    LLRect rect = rating_icon->getRect();
    S32 region_name_width = llmin(region_name->getRect().getWidth(), region_name->getTextBoundingRect().getWidth());
    S32 icon_left_pad = region_name->getRect().mLeft + region_name_width + ICON_PAD;
    region_name->setToolTip(region_name->getText());
    rating_icon->setRect(rect.setOriginAndSize(icon_left_pad, rect.mBottom, rect.getWidth(), rect.getHeight()));

    switch (sim_access)
    {
    case SIM_ACCESS_PG:
        rating_icon->setValue(getString("icon_PG"));
        break;

    case SIM_ACCESS_ADULT:
        rating_icon->setValue(getString("icon_R"));
        break;

    default:
        rating_icon->setValue(getString("icon_M"));
    }

    LLTextBox* region_type = getChild<LLTextBox>("region_type_text");
    region_type->setText(region->getLocalizedSimProductName());
    region_type->setToolTip(region->getLocalizedSimProductName());

    LLTextBox* resellable_clause = getChild<LLTextBox>("resellable_clause");
    const char* can_resell = region->getRegionFlag(REGION_FLAGS_BLOCK_LAND_RESELL) ? "can_not_resell" : "can_resell";
    resellable_clause->setText(getString(can_resell));

    LLTextBox* changeable_clause = getChild<LLTextBox>("changeable_clause");
    const char* can_change = region->getRegionFlag(REGION_FLAGS_ALLOW_PARCEL_CHANGES) ? "can_change" : "can_not_change";
    changeable_clause->setText(getString(can_change));

    LLCheckBoxCtrl* check = getChild<LLCheckBoxCtrl>("agree_covenant");
    check->set(false);
    check->setEnabled(true);
    check->setCommitCallback(onChangeAgreeCovenant, this);

    LLTextBox* box = getChild<LLTextBox>("covenant_text");
    box->setVisible(false);

    // send EstateCovenantInfo message
    LLMessageSystem *msg = gMessageSystem;
    msg->newMessage("EstateCovenantRequest");
    msg->nextBlockFast(_PREHASH_AgentData);
    msg->addUUIDFast(_PREHASH_AgentID,  gAgent.getID());
    msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID());
    msg->sendReliable(region->getHost());
}

// static
void LLFloaterBuyLandUI::onChangeAgreeCovenant(LLUICtrl* ctrl, void* user_data)
{
    if (user_data)
    {
        ((LLFloaterBuyLandUI*)user_data)->refreshUI();
    }
}

void LLFloaterBuyLandUI::updateFloaterCovenant(const LLTextBase* source, const LLUUID& asset_id)
{
    LLViewerTextEditor* editor = getChild<LLViewerTextEditor>("covenant_editor");
    editor->copyContents(source);

    onCovenantTextUpdated(asset_id);
}

void LLFloaterBuyLandUI::updateFloaterCovenantText(const std::string &string, const LLUUID& asset_id)
{
    LLViewerTextEditor* editor = getChild<LLViewerTextEditor>("covenant_editor");
    editor->setText(string);

    onCovenantTextUpdated(asset_id);
}

void LLFloaterBuyLandUI::onCovenantTextUpdated(const LLUUID& asset_id)
{
    LLCheckBoxCtrl* check = getChild<LLCheckBoxCtrl>("agree_covenant");
    LLTextBox* box = getChild<LLTextBox>("covenant_text");
    if (asset_id.isNull())
    {
        check->set(true);
        check->setEnabled(false);
        refreshUI();

        // remove the line stating that you must agree
        box->setVisible(false);
    }
    else
    {
        check->setEnabled(true);

        // remove the line stating that you must agree
        box->setVisible(true);
    }
}

void LLFloaterBuyLandUI::updateFloaterEstateName(const std::string& name)
{
    LLTextBox* box = getChild<LLTextBox>("estate_name_text");
    box->setText(name);
    box->setToolTip(name);
}

void LLFloaterBuyLandUI::updateFloaterLastModified(const std::string& text)
{
    LLTextBox* editor = getChild<LLTextBox>("covenant_timestamp_text");
    editor->setText(text);
}

void LLFloaterBuyLandUI::updateFloaterEstateOwnerName(const std::string& name)
{
    LLTextBox* box = getChild<LLTextBox>("estate_owner_text");
    box->setText(name);
}

void LLFloaterBuyLandUI::updateWebSiteInfo()
{
    S32 askBillableArea = mIsForGroup ? 0 : mParcelBillableArea;
    S32 askCurrencyBuy = mCurrency.getAmount();

    if (mTransaction &&
        mTransactionType == TransactionPreflight &&
        mPreflightAskBillableArea == askBillableArea &&
        mPreflightAskCurrencyBuy == askCurrencyBuy)
    {
        return;
    }

    mPreflightAskBillableArea = askBillableArea;
    mPreflightAskCurrencyBuy = askCurrencyBuy;

#if 0
    // enable this code if you want the details to blank while we're talking
    // to the web site... it's kind of jarring
    mSiteValid = false;
    mSiteMembershipUpgrade = false;
    mSiteMembershipAction = "(waiting)";
    mSiteMembershipPlanIDs.clear();
    mSiteMembershipPlanNames.clear();
    mSiteLandUseUpgrade = false;
    mSiteLandUseAction = "(waiting)";
    mSiteCurrencyEstimated = false;
    mSiteCurrencyEstimatedCost = 0;
#endif

    LLSD params = LLSD::emptyMap();
    params["agentId"] = gAgent.getID().asString();
    params["secureSessionId"] = gAgent.getSecureSessionID().asString();
    params["language"] = LLUI::getLanguage();
    params["billableArea"] = mPreflightAskBillableArea;
    params["currencyBuy"] = mPreflightAskCurrencyBuy;

    startTransaction(TransactionPreflight, params);
}

void LLFloaterBuyLandUI::finishWebSiteInfo()
{
    const LLSD& result = mTransaction->response();

    mSiteValid = result["success"].asBoolean();
    if (!mSiteValid)
    {
        tellUserError(
            result["errorMessage"].asString(),
            result["errorURI"].asString()
        );
        return;
    }

    const LLSD& membership = result["membership"];
    mSiteMembershipUpgrade = membership["upgrade"].asBoolean();
    mSiteMembershipAction = membership["action"].asString();
    mSiteMembershipPlanIDs.clear();
    mSiteMembershipPlanNames.clear();
    const LLSD& levels = membership["levels"];
    for (auto it = levels.beginArray(); it != levels.endArray(); ++it)
    {
        const LLSD& level = *it;
        mSiteMembershipPlanIDs.push_back(level["id"].asString());
        mSiteMembershipPlanNames.push_back(level["description"].asString());
    }
    mUserPlanChoice = 0;

    const LLSD& landUse = result["landUse"];
    mSiteLandUseUpgrade = landUse["upgrade"].asBoolean();
    mSiteLandUseAction = landUse["action"].asString();

    const LLSD& currency = result["currency"];
    if (currency.has("estimatedCost"))
    {
        mCurrency.setUSDEstimate(currency["estimatedCost"].asInteger());
    }
    if (currency.has("estimatedLocalCost"))
    {
        mCurrency.setLocalEstimate(currency["estimatedLocalCost"].asString());
    }

    mSiteConfirm = result["confirm"].asString();
}

void LLFloaterBuyLandUI::runWebSitePrep(const std::string& password)
{
    if (!mCanBuy)
    {
        return;
    }

    bool remove_contribution = getChild<LLUICtrl>("remove_contribution")->getValue().asBoolean();
    mParcelBuyInfo = LLViewerParcelMgr::getInstance()->setupParcelBuy(gAgent.getID(), gAgent.getSessionID(),
                        gAgent.getGroupID(), mIsForGroup, mIsClaim, remove_contribution);

    if (mParcelBuyInfo
        && !mSiteMembershipUpgrade
        && !mSiteLandUseUpgrade
        && mCurrency.getAmount() == 0
        && mSiteConfirm != "password")
    {
        sendBuyLand();
        return;
    }


    std::string newLevel = "noChange";

    if (mSiteMembershipUpgrade)
    {
        LLComboBox* levels = getChild<LLComboBox>( "account_level");
        if (levels)
        {
            mUserPlanChoice = levels->getCurrentIndex();
            newLevel = mSiteMembershipPlanIDs[mUserPlanChoice];
        }
    }

    LLSD params = LLSD::emptyMap();
    params["agentId"] = gAgent.getID().asString();
    params["secureSessionId"] = gAgent.getSecureSessionID().asString();
    params["language"] = LLUI::getLanguage();
    params["levelId"] = newLevel;
    params["billableArea"] = mIsForGroup ? 0 : mParcelBillableArea;
    params["currencyBuy"] = mCurrency.getAmount();
    params["estimatedCost"] = mCurrency.getUSDEstimate();
    params["estimatedLocalCost"] = mCurrency.getLocalEstimate();
    params["confirm"] = mSiteConfirm;

    if (!password.empty())
    {
        params["password"] = password;
    }

    startTransaction(TransactionBuy, params);
}

void LLFloaterBuyLandUI::finishWebSitePrep()
{
    const LLSD& result = mTransaction->response();

    bool success = result["success"].asBoolean();
    if (!success)
    {
        tellUserError(
            result["errorMessage"].asString(),
            result["errorURI"].asString()
        );
        return;
    }

    sendBuyLand();
}

void LLFloaterBuyLandUI::sendBuyLand()
{
    if (mParcelBuyInfo)
    {
        LLViewerParcelMgr::getInstance()->sendParcelBuy(mParcelBuyInfo);
        LLViewerParcelMgr::getInstance()->deleteParcelBuy(&mParcelBuyInfo);
        mBought = true;
    }
}

void LLFloaterBuyLandUI::updateNames()
{
    LLParcel* parcelp = mParcel->getParcel();

    if (!parcelp)
    {
        mParcelSellerName = LLStringUtil::null;
        return;
    }

    if (mIsClaim)
    {
        mParcelSellerName = "Linden Lab";
    }
    else if (parcelp->getIsGroupOwned())
    {
        gCacheName->getGroup(parcelp->getGroupID(),
            boost::bind(&LLFloaterBuyLandUI::updateGroupName, this,
                _1, _2, _3));
    }
    else
    {
        mParcelSellerName = LLSLURL("agent", parcelp->getOwnerID(), "completename").getSLURLString();
    }
}

void LLFloaterBuyLandUI::updateGroupName(const LLUUID& id,
                         const std::string& name,
                         bool is_group)
{
    LLParcel* parcelp = mParcel->getParcel();
    if (parcelp
        && parcelp->getGroupID() == id)
    {
        // request is current
        mParcelSellerName = name;
    }
}

void LLFloaterBuyLandUI::startTransaction(TransactionType type, const LLSD& params)
{
    delete mTransaction;
    mTransaction = NULL;

    mTransactionType = type;

    // Select a URI and method appropriate for the transaction type.
    static std::string transaction_uri;
    if (transaction_uri.empty())
    {
        transaction_uri = LLGridManager::getInstance()->getHelperURI() + "landtool.php";
    }

    const char* method;
    switch (mTransactionType)
    {
        case TransactionPreflight:
            method = "preflightBuyLandPrep";
            break;
        case TransactionBuy:
            method = "buyLandPrep";
            break;
        default:
            LL_WARNS() << "LLFloaterBuyLandUI: Unknown transaction type!" << LL_ENDL;
            return;
    }

    mTransaction = new LLXMLRPCTransaction(transaction_uri, method, params);
}

bool LLFloaterBuyLandUI::checkTransaction()
{
    if (!mTransaction)
    {
        return false;
    }

    if (!mTransaction->process())
    {
        return false;
    }

    if (mTransaction->status(NULL) != LLXMLRPCTransaction::StatusComplete)
    {
        tellUserError(mTransaction->statusMessage(), mTransaction->statusURI());
    }
    else {
        switch (mTransactionType)
        {
            case TransactionPreflight:  finishWebSiteInfo();    break;
            case TransactionBuy:        finishWebSitePrep();    break;
            default: ;
        }
    }

    delete mTransaction;
    mTransaction = NULL;

    return true;
}

void LLFloaterBuyLandUI::tellUserError(
    const std::string& message, const std::string& uri)
{
    mCanBuy = false;
    mCannotBuyIsError = true;
    mCannotBuyReason = getString("fetching_error");
    mCannotBuyReason += message;
    mCannotBuyURI = uri;
}


// virtual
bool LLFloaterBuyLandUI::postBuild()
{
    setVisibleCallback(boost::bind(&LLFloaterBuyLandUI::onVisibilityChanged, this, _2));

    mCurrency.prepare();

    getChild<LLUICtrl>("buy_btn")->setCommitCallback( boost::bind(&LLFloaterBuyLandUI::onClickBuy, this));
    getChild<LLUICtrl>("cancel_btn")->setCommitCallback( boost::bind(&LLFloaterBuyLandUI::onClickCancel, this));
    getChild<LLUICtrl>("error_web")->setCommitCallback( boost::bind(&LLFloaterBuyLandUI::onClickErrorWeb, this));

    center();

    return true;
}

void LLFloaterBuyLandUI::setParcel(LLViewerRegion* region, LLParcelSelectionHandle parcel)
{
    if (mTransaction &&  mTransactionType == TransactionBuy)
    {
        // the user is buying, don't change the selection
        return;
    }

    mRegion = region;
    mParcel = parcel;

    updateAgentInfo();
    updateParcelInfo();
    updateCovenantInfo();
    if (mCanBuy)
    {
        updateWebSiteInfo();
    }
    refreshUI();
}

void LLFloaterBuyLandUI::setForGroup(bool forGroup)
{
    mIsForGroup = forGroup;
}

void LLFloaterBuyLandUI::draw()
{
    LLFloater::draw();

    bool needsUpdate = false;
    needsUpdate |= checkTransaction();
    needsUpdate |= mCurrency.process();

    if (mBought)
    {
        closeFloater();
    }
    else if (needsUpdate)
    {
        if (mCanBuy && mCurrency.hasError())
        {
            tellUserError(mCurrency.errorMessage(), mCurrency.errorURI());
        }

        refreshUI();
    }
}

// virtual
bool LLFloaterBuyLandUI::canClose()
{
    // mTransactionType check for pre-buy estimation stage and mCurrency to allow exit after transaction
    bool can_close = !mTransaction && (mTransactionType != TransactionBuy || mCurrency.canCancel());
    if (!can_close)
    {
        // explain to user why they can't do this, see DEV-9605
        LLNotificationsUtil::add("CannotCloseFloaterBuyLand");
    }
    return can_close;
}

void LLFloaterBuyLandUI::onVisibilityChanged ( const LLSD& new_visibility )
{
    if (new_visibility.asBoolean())
    {
        refreshUI();
    }
}

void LLFloaterBuyLandUI::refreshUI()
{
    // section zero: title area
    {
        LLTextureCtrl* snapshot = getChild<LLTextureCtrl>("info_image");
        if (snapshot)
        {
            snapshot->setImageAssetID(
                mParcelValid ? mParcelSnapshot : LLUUID::null);
        }

        if (mParcelValid)
        {
            getChild<LLUICtrl>("info_parcel")->setValue(mParcelLocation);

            LLStringUtil::format_map_t string_args;
            string_args["[AMOUNT]"] = llformat("%d", mParcelActualArea);
            string_args["[AMOUNT2]"] = llformat("%d", mParcelSupportedObjects);

            getChild<LLUICtrl>("info_size")->setValue(getString("meters_supports_object", string_args));

            F32 cost_per_sqm = 0.0f;
            if (mParcelActualArea > 0)
            {
                cost_per_sqm = (F32)mParcelPrice / (F32)mParcelActualArea;
            }

            LLStringUtil::format_map_t info_price_args;
            info_price_args["[PRICE]"] = llformat("%d", mParcelPrice);
            info_price_args["[PRICE_PER_SQM]"] = llformat("%.1f", cost_per_sqm);
            if (mParcelSoldWithObjects)
            {
                info_price_args["[SOLD_WITH_OBJECTS]"] = getString("sold_with_objects");
            }
            else
            {
                info_price_args["[SOLD_WITH_OBJECTS]"] = getString("sold_without_objects");
            }
            getChild<LLUICtrl>("info_price")->setValue(getString("info_price_string", info_price_args));
            getChildView("info_price")->setVisible( mParcelIsForSale);
        }
        else
        {
            getChild<LLUICtrl>("info_parcel")->setValue(getString("no_parcel_selected"));
            getChild<LLUICtrl>("info_size")->setValue(LLStringUtil::null);
            getChild<LLUICtrl>("info_price")->setValue(LLStringUtil::null);
        }

        getChild<LLUICtrl>("info_action")->setValue(
            mCanBuy
                ?
                    mIsForGroup
                        ? getString("buying_for_group")//"Buying land for group:"
                        : getString("buying_will")//"Buying this land will:"
                :
                    mCannotBuyIsError
                        ? getString("cannot_buy_now")//"Cannot buy now:"
                        : getString("not_for_sale")//"Not for sale:"

            );
    }

    bool showingError = !mCanBuy || !mSiteValid;

    // error section
    if (showingError)
    {
        mChildren.setBadge(std::string("step_error"),
            mCannotBuyIsError
                ? LLViewChildren::BADGE_ERROR
                : LLViewChildren::BADGE_WARN);

        LLTextBox* message = getChild<LLTextBox>("error_message");
        if (message)
        {
            message->setVisible(true);
            message->setValue(LLSD(!mCanBuy ? mCannotBuyReason : "(waiting for data)"));
        }

        getChildView("error_web")->setVisible(mCannotBuyIsError && !mCannotBuyURI.empty());
    }
    else
    {
        getChildView("step_error")->setVisible(false);
        getChildView("error_message")->setVisible(false);
        getChildView("error_web")->setVisible(false);
    }


    // section one: account
    if (!showingError)
    {
        mChildren.setBadge(std::string("step_1"),
            mSiteMembershipUpgrade
                ? LLViewChildren::BADGE_NOTE
                : LLViewChildren::BADGE_OK);
        getChild<LLUICtrl>("account_action")->setValue(mSiteMembershipAction);
        getChild<LLUICtrl>("account_reason")->setValue(
            mSiteMembershipUpgrade
                ?   getString("must_upgrade")
                :   getString("cant_own_land")
            );

        LLComboBox* levels = getChild<LLComboBox>( "account_level");
        if (levels)
        {
            levels->setVisible(mSiteMembershipUpgrade);

            levels->removeall();
            for(std::vector<std::string>::const_iterator i
                    = mSiteMembershipPlanNames.begin();
                i != mSiteMembershipPlanNames.end();
                ++i)
            {
                levels->add(*i);
            }

            levels->setCurrentByIndex(mUserPlanChoice);
        }

        getChildView("step_1")->setVisible(true);
        getChildView("account_action")->setVisible(true);
        getChildView("account_reason")->setVisible(true);
    }
    else
    {
        getChildView("step_1")->setVisible(false);
        getChildView("account_action")->setVisible(false);
        getChildView("account_reason")->setVisible(false);
        getChildView("account_level")->setVisible(false);
    }

    // section two: land use fees
    if (!showingError)
    {
        mChildren.setBadge(std::string("step_2"),
            mSiteLandUseUpgrade
                ? LLViewChildren::BADGE_NOTE
                : LLViewChildren::BADGE_OK);
        getChild<LLUICtrl>("land_use_action")->setValue(mSiteLandUseAction);

        std::string message;

        if (mIsForGroup)
        {
            LLStringUtil::format_map_t string_args;
            string_args["[GROUP]"] = std::string(gAgent.getGroupName());

            message += getString("insufficient_land_credits", string_args);

        }
        else
        {
            LLStringUtil::format_map_t string_args;
            string_args["[BUYER]"] = llformat("%d", mAgentCommittedTier);
            message += getString("land_holdings", string_args);
        }

        if (!mParcelValid)
        {
            message += LLTrans::getString("sentences_separator") + getString("no_parcel_selected");
        }
        else if (mParcelBillableArea == mParcelActualArea)
        {
            LLStringUtil::format_map_t string_args;
            string_args["[AMOUNT]"] = llformat("%d ", mParcelActualArea);
            message += LLTrans::getString("sentences_separator") + getString("parcel_meters", string_args);
        }
        else
        {

            if (mParcelBillableArea > mParcelActualArea)
            {
                LLStringUtil::format_map_t string_args;
                string_args["[AMOUNT]"] = llformat("%d ", mParcelBillableArea);
                message += LLTrans::getString("sentences_separator") + getString("premium_land", string_args);
            }
            else
            {
                LLStringUtil::format_map_t string_args;
                string_args["[AMOUNT]"] = llformat("%d ", mParcelBillableArea);
                message += LLTrans::getString("sentences_separator") + getString("discounted_land", string_args);
            }
        }

        getChild<LLUICtrl>("land_use_reason")->setValue(message);

        getChildView("step_2")->setVisible(true);
        getChildView("land_use_action")->setVisible(true);
        getChildView("land_use_reason")->setVisible(true);
    }
    else
    {
        getChildView("step_2")->setVisible(false);
        getChildView("land_use_action")->setVisible(false);
        getChildView("land_use_reason")->setVisible(false);
    }

    // section three: purchase & currency
    S32 finalBalance = mAgentCashBalance + mCurrency.getAmount() - mParcelPrice;
    bool willHaveEnough = finalBalance >= 0;
    bool haveEnough = mAgentCashBalance >= mParcelPrice;
    S32 minContribution = llceil((F32)mParcelBillableArea / GROUP_LAND_BONUS_FACTOR);
    bool groupContributionEnough = mParcelGroupContribution >= minContribution;

    mCurrency.updateUI(!showingError  &&  !haveEnough);

    if (!showingError)
    {
        mChildren.setBadge(std::string("step_3"),
            !willHaveEnough
                ? LLViewChildren::BADGE_WARN
                : mCurrency.getAmount() > 0
                    ? LLViewChildren::BADGE_NOTE
                    : LLViewChildren::BADGE_OK);

        LLStringUtil::format_map_t string_args;
        string_args["[AMOUNT]"] = llformat("%d", mParcelPrice);
        string_args["[SELLER]"] = mParcelSellerName;
        getChild<LLUICtrl>("purchase_action")->setValue(getString("pay_to_for_land", string_args));
        getChildView("purchase_action")->setVisible( mParcelValid);

        std::string reasonString;

        if (haveEnough)
        {
            LLStringUtil::format_map_t string_args;
            string_args["[AMOUNT]"] = llformat("%d", mAgentCashBalance);

            getChild<LLUICtrl>("currency_reason")->setValue(getString("have_enough_lindens", string_args));
        }
        else
        {
            LLStringUtil::format_map_t string_args;
            string_args["[AMOUNT]"] = llformat("%d", mAgentCashBalance);
            string_args["[AMOUNT2]"] = llformat("%d", mParcelPrice - mAgentCashBalance);

            getChild<LLUICtrl>("currency_reason")->setValue(getString("not_enough_lindens", string_args));

            getChild<LLUICtrl>("currency_est")->setTextArg("[LOCAL_AMOUNT]", mCurrency.getLocalEstimate());
        }

        if (willHaveEnough)
        {
            LLStringUtil::format_map_t string_args;
            string_args["[AMOUNT]"] = llformat("%d", finalBalance);

            getChild<LLUICtrl>("currency_balance")->setValue(getString("balance_left", string_args));

        }
        else
        {
            LLStringUtil::format_map_t string_args;
            string_args["[AMOUNT]"] = llformat("%d", mParcelPrice - mAgentCashBalance);

            getChild<LLUICtrl>("currency_balance")->setValue(getString("balance_needed", string_args));

        }

        getChild<LLUICtrl>("remove_contribution")->setValue(LLSD(groupContributionEnough));
        getChildView("remove_contribution")->setEnabled(groupContributionEnough);
        bool showRemoveContribution = mParcelIsGroupLand
                            && (mParcelGroupContribution > 0);
        getChildView("remove_contribution")->setLabelArg("[AMOUNT]",
                            llformat("%d", minContribution));
        getChildView("remove_contribution")->setVisible( showRemoveContribution);

        getChildView("step_3")->setVisible(true);
        getChildView("purchase_action")->setVisible(true);
        getChildView("currency_reason")->setVisible(true);
        getChildView("currency_balance")->setVisible(true);
    }
    else
    {
        getChildView("step_3")->setVisible(false);
        getChildView("purchase_action")->setVisible(false);
        getChildView("currency_reason")->setVisible(false);
        getChildView("currency_balance")->setVisible(false);
        getChildView("remove_group_donation")->setVisible(false);
    }


    bool agrees_to_covenant = false;
    LLCheckBoxCtrl* check = getChild<LLCheckBoxCtrl>("agree_covenant");
    if (check)
    {
        agrees_to_covenant = check->get();
    }

    getChildView("buy_btn")->setEnabled(mCanBuy  &&  mSiteValid  &&  willHaveEnough  &&  !mTransaction && agrees_to_covenant);
}

void LLFloaterBuyLandUI::startBuyPreConfirm()
{
    std::string action;

    if (mSiteMembershipUpgrade)
    {
        action += mSiteMembershipAction;
        action += "\n";

        LLComboBox* levels = getChild<LLComboBox>( "account_level");
        if (levels)
        {
            action += " * ";
            action += mSiteMembershipPlanNames[levels->getCurrentIndex()];
            action += "\n";
        }
    }
    if (mSiteLandUseUpgrade)
    {
        action += mSiteLandUseAction;
        action += "\n";
    }
    if (mCurrency.getAmount() > 0)
    {
        LLStringUtil::format_map_t string_args;
        string_args["[AMOUNT]"] = llformat("%d", mCurrency.getAmount());
        string_args["[LOCAL_AMOUNT]"] = mCurrency.getLocalEstimate();

        action += getString("buy_for_US", string_args);
    }

    LLStringUtil::format_map_t string_args;
    string_args["[AMOUNT]"] = llformat("%d", mParcelPrice);
    string_args["[SELLER]"] = mParcelSellerName;
    action += getString("pay_to_for_land", string_args);


    LLConfirmationManager::confirm(mSiteConfirm,
        action,
        *this,
        &LLFloaterBuyLandUI::startBuyPostConfirm);
}

void LLFloaterBuyLandUI::startBuyPostConfirm(const std::string& password)
{
    runWebSitePrep(password);

    mCanBuy = false;
    mCannotBuyReason = getString("processing");
    refreshUI();
}


void LLFloaterBuyLandUI::onClickBuy()
{
    startBuyPreConfirm();
}

void LLFloaterBuyLandUI::onClickCancel()
{
    closeFloater();
}

void LLFloaterBuyLandUI::onClickErrorWeb()
{
    LLWeb::loadURLExternal(mCannotBuyURI);
    closeFloater();
}