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

#include "linden_common.h"
#include "llwindowshade.h"

#include "lllayoutstack.h"
#include "lltextbox.h"
#include "lliconctrl.h"
#include "llbutton.h"
#include "llcheckboxctrl.h"
#include "lllineeditor.h"

const S32 MIN_NOTIFICATION_AREA_HEIGHT = 30;
const S32 MAX_NOTIFICATION_AREA_HEIGHT = 100;

static LLDefaultChildRegistry::Register<LLWindowShade> r("window_shade");

LLWindowShade::Params::Params()
:   bg_image("bg_image"),
    modal("modal", false),
    text_color("text_color"),
    shade_color("shade_color"),
    can_close("can_close", true)
{
    changeDefault(mouse_opaque, false);
}

LLWindowShade::LLWindowShade(const LLWindowShade::Params& params)
:   LLUICtrl(params),
    mModal(params.modal),
    mFormHeight(0),
    mTextColor(params.text_color)
{
    setFocusRoot(true);
}

void LLWindowShade::initFromParams(const LLWindowShade::Params& params)
{
    LLUICtrl::initFromParams(params);

    LLLayoutStack::Params layout_p;
    layout_p.name = "notification_stack";
    layout_p.rect = params.rect;
    layout_p.follows.flags = FOLLOWS_ALL;
    layout_p.mouse_opaque = false;
    layout_p.orientation = LLLayoutStack::VERTICAL;
    layout_p.border_size = 0;

    LLLayoutStack* stackp = LLUICtrlFactory::create<LLLayoutStack>(layout_p);
    addChild(stackp);

    LLLayoutPanel::Params panel_p;
    panel_p.rect = LLRect(0, MIN_NOTIFICATION_AREA_HEIGHT, 800, 0);
    panel_p.name = "notification_area";
    panel_p.visible = false;
    panel_p.user_resize = false;
    panel_p.background_visible = true;
    panel_p.bg_alpha_image = params.bg_image;
    panel_p.auto_resize = false;
    mNotificationsArea = LLUICtrlFactory::create<LLLayoutPanel>(panel_p);
    stackp->addChild(mNotificationsArea);

    panel_p = LLUICtrlFactory::getDefaultParams<LLLayoutPanel>();
    panel_p.auto_resize = true;
    panel_p.user_resize = false;
    panel_p.rect = params.rect;
    panel_p.name = "background_area";
    panel_p.mouse_opaque = false;
    panel_p.background_visible = false;
    panel_p.bg_alpha_color = params.shade_color;
    mBackgroundArea = LLUICtrlFactory::create<LLLayoutPanel>(panel_p);
    stackp->addChild(mBackgroundArea);

    layout_p = LLUICtrlFactory::getDefaultParams<LLLayoutStack>();
    layout_p.rect = LLRect(0, 30, 800, 0);
    layout_p.follows.flags = FOLLOWS_ALL;
    layout_p.orientation = LLLayoutStack::HORIZONTAL;
    stackp = LLUICtrlFactory::create<LLLayoutStack>(layout_p);
    mNotificationsArea->addChild(stackp);

    panel_p = LLUICtrlFactory::getDefaultParams<LLLayoutPanel>();
    panel_p.rect.height = 30;
    LLLayoutPanel* panel = LLUICtrlFactory::create<LLLayoutPanel>(panel_p);
    stackp->addChild(panel);

    LLIconCtrl::Params icon_p;
    icon_p.name = "notification_icon";
    icon_p.rect = LLRect(5, 25, 21, 10);
    panel->addChild(LLUICtrlFactory::create<LLIconCtrl>(icon_p));

    LLTextBox::Params text_p;
    text_p.rect = LLRect(31, 23, panel->getRect().getWidth() - 5, 3);
    text_p.follows.flags = FOLLOWS_ALL;
    text_p.text_color = mTextColor;
    text_p.font = LLFontGL::getFontSansSerifSmall();
    text_p.font.style = "BOLD";
    text_p.name = "notification_text";
    text_p.use_ellipses = true;
    text_p.wrap = true;
    mNotificationsText = LLUICtrlFactory::create<LLTextBox>(text_p);
    panel->addChild(mNotificationsText);

    panel_p = LLUICtrlFactory::getDefaultParams<LLLayoutPanel>();
    panel_p.auto_resize = false;
    panel_p.user_resize = false;
    panel_p.name="form_elements";
    panel_p.rect = LLRect(0, MIN_NOTIFICATION_AREA_HEIGHT, 130, 0);
    LLLayoutPanel* form_elements_panel = LLUICtrlFactory::create<LLLayoutPanel>(panel_p);
    stackp->addChild(form_elements_panel);

    panel_p = LLUICtrlFactory::getDefaultParams<LLLayoutPanel>();
    panel_p.auto_resize = false;
    panel_p.user_resize = false;
    panel_p.rect = LLRect(0, MIN_NOTIFICATION_AREA_HEIGHT, 25, 0);
    panel_p.name = "close_panel";
    LLLayoutPanel* close_panel = LLUICtrlFactory::create<LLLayoutPanel>(panel_p);
    stackp->addChild(close_panel);

    LLButton::Params button_p;
    button_p.name = "close_notification";
    button_p.rect = LLRect(5, 23, 21, 7);
    button_p.image_color.control="DkGray_66";
    button_p.image_unselected.name="Icon_Close_Foreground";
    button_p.image_selected.name="Icon_Close_Press";
    button_p.click_callback.function = boost::bind(&LLWindowShade::onCloseNotification, this);

    close_panel->addChild(LLUICtrlFactory::create<LLButton>(button_p));

    close_panel->setVisible(params.can_close);
}

void LLWindowShade::draw()
{
    LLRect message_rect = mNotificationsText->getTextBoundingRect();

    mNotificationsArea->reshape(mNotificationsArea->getRect().getWidth(),
        llclamp(message_rect.getHeight() + 15,
                llmax(mFormHeight, MIN_NOTIFICATION_AREA_HEIGHT),
                MAX_NOTIFICATION_AREA_HEIGHT));

    LLUICtrl::draw();

    while(!mNotifications.empty() && !mNotifications.back()->isActive())
    {
        mNotifications.pop_back();
        // go ahead and hide
        hide();
    }

    if (mNotifications.empty())
    {
        hide();
    }
    else if (mNotificationsArea->getVisibleAmount() < 0.01f)
    {
        displayLatestNotification();
    }

    if (!mNotificationsArea->getVisible() && (mNotificationsArea->getVisibleAmount() < 0.001f))
    {
        mBackgroundArea->setBackgroundVisible(false);
        setMouseOpaque(false);
    }
}

void LLWindowShade::hide()
{
    mNotificationsArea->setVisible(false);
}

void LLWindowShade::onCloseNotification()
{
    if (!mNotifications.empty())
        LLNotifications::instance().cancel(mNotifications.back());
}

void LLWindowShade::onClickIgnore(LLUICtrl* ctrl)
{
    LLNotificationPtr notify = getCurrentNotification();
    if (!notify) return;

    bool check = ctrl->getValue().asBoolean();
    if (notify->getForm()->getIgnoreType() == LLNotificationForm::IGNORE_SHOW_AGAIN)
    {
        // question was "show again" so invert value to get "ignore"
        check = !check;
    }
    notify->setIgnored(check);
}

void LLWindowShade::onClickNotificationButton(const std::string& name)
{
    LLNotificationPtr notify = getCurrentNotification();
    if (!notify) return;

    mNotificationResponse[name] = true;

    notify->respond(mNotificationResponse);
}

void LLWindowShade::onEnterNotificationText(LLUICtrl* ctrl, const std::string& name)
{
    mNotificationResponse[name] = ctrl->getValue().asString();
}

void LLWindowShade::show(LLNotificationPtr notification)
{
    mNotifications.push_back(notification);

    displayLatestNotification();
}

void LLWindowShade::displayLatestNotification()
{
    if (mNotifications.empty()) return;

    LLNotificationPtr notification = mNotifications.back();

    LLSD payload = notification->getPayload();

    LLNotificationFormPtr formp = notification->getForm();
    mNotificationsArea->getChild<LLUICtrl>("notification_icon")->setValue(notification->getIcon());
    mNotificationsText->setValue(notification->getMessage());
    mNotificationsText->setToolTip(notification->getMessage());

    LLNotificationForm::EIgnoreType ignore_type = formp->getIgnoreType();
    LLLayoutPanel& form_elements = mNotificationsArea->getChildRef<LLLayoutPanel>("form_elements");
    form_elements.deleteAllChildren();
    form_elements.reshape(form_elements.getRect().getWidth(), MIN_NOTIFICATION_AREA_HEIGHT);

    const S32 FORM_PADDING_HORIZONTAL = 10;
    const S32 FORM_PADDING_VERTICAL = 3;
    const S32 WIDGET_HEIGHT = 24;
    const S32 LINE_EDITOR_WIDTH = 120;
    S32 cur_x = FORM_PADDING_HORIZONTAL;
    S32 cur_y = FORM_PADDING_VERTICAL + WIDGET_HEIGHT;
    S32 form_width = cur_x;

    if (ignore_type != LLNotificationForm::IGNORE_NO)
    {
        LLCheckBoxCtrl::Params checkbox_p;
        checkbox_p.name = "ignore_check";
        checkbox_p.rect = LLRect(cur_x, cur_y, cur_x, cur_y - WIDGET_HEIGHT);
        checkbox_p.label = formp->getIgnoreMessage();
        checkbox_p.label_text.text_color = LLColor4::black;
        checkbox_p.commit_callback.function = boost::bind(&LLWindowShade::onClickIgnore, this, _1);
        checkbox_p.initial_value = formp->getIgnored();

        LLCheckBoxCtrl* check = LLUICtrlFactory::create<LLCheckBoxCtrl>(checkbox_p);
        check->setRect(check->getBoundingRect());
        form_elements.addChild(check);
        cur_x = check->getRect().mRight + FORM_PADDING_HORIZONTAL;
        form_width = llmax(form_width, cur_x);
    }

    for (S32 i = 0; i < formp->getNumElements(); i++)
    {
        LLSD form_element = formp->getElement(i);
        std::string type = form_element["type"].asString();
        if (type == "button")
        {
            LLButton::Params button_p;
            button_p.name = form_element["name"];
            button_p.label = form_element["text"];
            button_p.rect = LLRect(cur_x, cur_y, cur_x, cur_y - WIDGET_HEIGHT);
            button_p.click_callback.function = boost::bind(&LLWindowShade::onClickNotificationButton, this, form_element["name"].asString());
            button_p.auto_resize = true;

            LLButton* button = LLUICtrlFactory::create<LLButton>(button_p);
            button->autoResize();
            form_elements.addChild(button);

            if (form_element["default"].asBoolean())
            {
                form_elements.setDefaultBtn(button);
            }

            cur_x = button->getRect().mRight + FORM_PADDING_HORIZONTAL;
            form_width = llmax(form_width, cur_x);
        }
        else if (type == "text" || type == "password")
        {
            // if not at beginning of line...
            if (cur_x != FORM_PADDING_HORIZONTAL)
            {
                // start new line
                cur_x = FORM_PADDING_HORIZONTAL;
                cur_y -= WIDGET_HEIGHT + FORM_PADDING_VERTICAL;
            }
            LLTextBox::Params label_p;
            label_p.name = form_element["name"].asString() + "_label";
            label_p.rect = LLRect(cur_x, cur_y, cur_x + LINE_EDITOR_WIDTH, cur_y - WIDGET_HEIGHT);
            label_p.initial_value = form_element["text"];
            label_p.text_color = mTextColor;
            label_p.font_valign = LLFontGL::VCENTER;
            label_p.v_pad = 5;
            LLTextBox* textbox = LLUICtrlFactory::create<LLTextBox>(label_p);
            textbox->reshapeToFitText();
            textbox->reshape(textbox->getRect().getWidth(), MIN_NOTIFICATION_AREA_HEIGHT - 2 * FORM_PADDING_VERTICAL);
            form_elements.addChild(textbox);
            cur_x = textbox->getRect().mRight + FORM_PADDING_HORIZONTAL;

            LLLineEditor::Params line_p;
            line_p.name = form_element["name"];
            line_p.keystroke_callback = boost::bind(&LLWindowShade::onEnterNotificationText, this, _1, form_element["name"].asString());
            line_p.is_password = type == "password";
            line_p.rect = LLRect(cur_x, cur_y, cur_x + LINE_EDITOR_WIDTH, cur_y - WIDGET_HEIGHT);

            LLLineEditor* line_editor = LLUICtrlFactory::create<LLLineEditor>(line_p);
            form_elements.addChild(line_editor);
            form_width = llmax(form_width, cur_x + LINE_EDITOR_WIDTH + FORM_PADDING_HORIZONTAL);

            // reset to start of next line
            cur_x = FORM_PADDING_HORIZONTAL;
            cur_y -= WIDGET_HEIGHT + FORM_PADDING_VERTICAL;
        }
    }

    mFormHeight = form_elements.getRect().getHeight() - (cur_y - WIDGET_HEIGHT - FORM_PADDING_VERTICAL);
    form_elements.reshape(form_width, mFormHeight);
    form_elements.setMinDim(form_width);

    // move all form elements back onto form surface
    S32 delta_y = WIDGET_HEIGHT + FORM_PADDING_VERTICAL - cur_y;
    for (child_list_const_iter_t it = form_elements.getChildList()->begin(), end_it = form_elements.getChildList()->end();
        it != end_it;
        ++it)
    {
        (*it)->translate(0, delta_y);
    }

    mNotificationsArea->setVisible(true);
    mBackgroundArea->setBackgroundVisible(mModal);

    setMouseOpaque(mModal);
}

void LLWindowShade::setBackgroundImage(LLUIImage* image)
{
    mNotificationsArea->setTransparentImage(image);
}

void LLWindowShade::setTextColor(LLColor4 color)
{
    mNotificationsText->setColor(color);
}

bool LLWindowShade::isShown() const
{
    return mNotificationsArea->getVisible();
}

void LLWindowShade::setCanClose(bool can_close)
{
    getChildView("close_panel")->setVisible(can_close);
}

LLNotificationPtr LLWindowShade::getCurrentNotification()
{
    if (mNotifications.empty())
    {
        return LLNotificationPtr();
    }
    return mNotifications.back();
}