/**
 * @file llscrollcolumnheader.cpp
 * @brief Scroll lists are composed of rows (items), each of which
 * contains columns (cells).
 *
 * $LicenseInfo:firstyear=2007&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 "llscrolllistcolumn.h"

#include "llbutton.h"
#include "llresizebar.h"
#include "llscrolllistcell.h"
#include "llscrolllistctrl.h"
#include "llscrolllistitem.h"
#include "lluictrlfactory.h"

const S32 MIN_COLUMN_WIDTH = 20;

// defaults for LLScrollColumnHeader param block pulled from widgets/scroll_column_header.xml
static LLWidgetNameRegistry::StaticRegistrar sRegisterColumnHeaderParams(&typeid(LLScrollColumnHeader::Params), "scroll_column_header");

//---------------------------------------------------------------------------
// LLScrollColumnHeader
//---------------------------------------------------------------------------
LLScrollColumnHeader::Params::Params()
:   column("column")
{}


LLScrollColumnHeader::LLScrollColumnHeader(const LLScrollColumnHeader::Params& p)
:   LLButton(p), // use combobox params to steal images
    mColumn(p.column),
    mHasResizableElement(false)
{
    setClickedCallback(boost::bind(&LLScrollColumnHeader::onClick, this, _2));

    // resize handles on left and right
    const S32 RESIZE_BAR_THICKNESS = 3;
    LLResizeBar::Params resize_bar_p;
    resize_bar_p.resizing_view(this);
    resize_bar_p.rect(LLRect(getRect().getWidth() - RESIZE_BAR_THICKNESS, getRect().getHeight(), getRect().getWidth(), 0));
    resize_bar_p.min_size(MIN_COLUMN_WIDTH);
    resize_bar_p.side(LLResizeBar::RIGHT);
    resize_bar_p.enabled(false);
    mResizeBar = LLUICtrlFactory::create<LLResizeBar>(resize_bar_p);
    addChild(mResizeBar);
}

LLScrollColumnHeader::~LLScrollColumnHeader()
{}

void LLScrollColumnHeader::draw()
{
    std::string sort_column = mColumn->mParentCtrl->getSortColumnName();
    bool draw_arrow = !mColumn->mLabel.empty()
            && mColumn->mParentCtrl->isSorted()
            // check for indirect sorting column as well as column's sorting name
            && (sort_column == mColumn->mSortingColumn || sort_column == mColumn->mName);

    bool is_ascending = mColumn->mParentCtrl->getSortAscending();
    if (draw_arrow)
    {
        setImageOverlay(is_ascending ? "up_arrow.tga" : "down_arrow.tga", LLFontGL::RIGHT, LLColor4::white);
    }
    else
    {
        setImageOverlay(LLUUID::null);
    }

    // Draw children
    LLButton::draw();
}

bool LLScrollColumnHeader::handleDoubleClick(S32 x, S32 y, MASK mask)
{
    if (canResize() && mResizeBar->getRect().pointInRect(x, y))
    {
        // reshape column to max content width
        mColumn->mParentCtrl->calcMaxContentWidth();
        LLRect column_rect = getRect();
        column_rect.mRight = column_rect.mLeft + mColumn->mMaxContentWidth;
        setShape(column_rect, true);
    }
    else
    {
        onClick(LLSD());
    }
    return true;
}

void LLScrollColumnHeader::onClick(const LLSD& data)
{
    if (mColumn)
    {
        LLScrollListCtrl::onClickColumn(mColumn);
    }
}

LLView* LLScrollColumnHeader::findSnapEdge(S32& new_edge_val, const LLCoordGL& mouse_dir, ESnapEdge snap_edge, ESnapType snap_type, S32 threshold, S32 padding)
{
    // this logic assumes dragging on right
    llassert(snap_edge == SNAP_RIGHT);

    // use higher snap threshold for column headers
    threshold = llmin(threshold, 10);

    LLRect snap_rect = getSnapRect();

    mColumn->mParentCtrl->calcMaxContentWidth();

    S32 snap_delta = mColumn->mMaxContentWidth - snap_rect.getWidth();

    // x coord growing means column growing, so same signs mean we're going in right direction
    if (llabs(snap_delta) <= threshold && mouse_dir.mX * snap_delta > 0 )
    {
        new_edge_val = snap_rect.mRight + snap_delta;
    }
    else
    {
        LLScrollListColumn* next_column = mColumn->mParentCtrl->getColumn(mColumn->mIndex + 1);
        while (next_column)
        {
            if (next_column->mHeader)
            {
                snap_delta = (next_column->mHeader->getSnapRect().mRight - next_column->mMaxContentWidth) - snap_rect.mRight;
                if (llabs(snap_delta) <= threshold && mouse_dir.mX * snap_delta > 0 )
                {
                    new_edge_val = snap_rect.mRight + snap_delta;
                }
                break;
            }
            next_column = mColumn->mParentCtrl->getColumn(next_column->mIndex + 1);
        }
    }

    return this;
}

void LLScrollColumnHeader::handleReshape(const LLRect& new_rect, bool by_user)
{
    S32 new_width = new_rect.getWidth();
    S32 delta_width = new_width - (getRect().getWidth() /*+ mColumn->mParentCtrl->getColumnPadding()*/);

    if (delta_width != 0)
    {
        S32 remaining_width = -delta_width;
        S32 col;
        for (col = mColumn->mIndex + 1; col < mColumn->mParentCtrl->getNumColumns(); col++)
        {
            LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col);
            if (!columnp) continue;

            if (columnp->mHeader && columnp->mHeader->canResize())
            {
                // how many pixels in width can this column afford to give up?
                S32 resize_buffer_amt = llmax(0, columnp->getWidth() - MIN_COLUMN_WIDTH);

                // user shrinking column, need to add width to other columns
                if (delta_width < 0)
                {
                    if (columnp->getWidth() > 0)
                    {
                        // statically sized column, give all remaining width to this column
                        columnp->setWidth(columnp->getWidth() + remaining_width);
                        if (columnp->mRelWidth > 0.f)
                        {
                            columnp->mRelWidth = (F32)columnp->getWidth() / (F32)mColumn->mParentCtrl->getItemListRect().getWidth();
                        }
                        // all padding went to this widget, we're done
                        break;
                    }
                }
                else
                {
                    // user growing column, need to take width from other columns
                    remaining_width += resize_buffer_amt;

                    if (columnp->getWidth() > 0)
                    {
                        columnp->setWidth(columnp->getWidth() - llmin(columnp->getWidth() - MIN_COLUMN_WIDTH, delta_width));
                        if (columnp->mRelWidth > 0.f)
                        {
                            columnp->mRelWidth = (F32)columnp->getWidth() / (F32)mColumn->mParentCtrl->getItemListRect().getWidth();
                        }
                    }

                    if (remaining_width >= 0)
                    {
                        // width sucked up from neighboring columns, done
                        break;
                    }
                }
            }
        }

        // clamp resize amount to maximum that can be absorbed by other columns
        if (delta_width > 0)
        {
            delta_width += llmin(remaining_width, 0);
        }

        // propagate constrained delta_width to new width for this column
        new_width = getRect().getWidth() + delta_width - mColumn->mParentCtrl->getColumnPadding();

        // use requested width
        mColumn->setWidth(new_width);

        // update proportional spacing
        if (mColumn->mRelWidth > 0.f)
        {
            mColumn->mRelWidth = (F32)new_width / (F32)mColumn->mParentCtrl->getItemListRect().getWidth();
        }

        // tell scroll list to layout columns again
        // do immediate update to get proper feedback to resize handle
        // which needs to know how far the resize actually went
        const bool force_update = true;
        mColumn->mParentCtrl->updateColumns(force_update);
    }
}

void LLScrollColumnHeader::setHasResizableElement(bool resizable)
{
    if (mHasResizableElement != resizable)
    {
        mColumn->mParentCtrl->dirtyColumns();
        mHasResizableElement = resizable;
    }
}

void LLScrollColumnHeader::updateResizeBars()
{
    S32 num_resizable_columns = 0;
    S32 col;
    for (col = 0; col < mColumn->mParentCtrl->getNumColumns(); col++)
    {
        LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col);
        if (columnp && columnp->mHeader && columnp->mHeader->canResize())
        {
            num_resizable_columns++;
        }
    }

    S32 num_resizers_enabled = 0;

    // now enable/disable resize handles on resizable columns if we have at least two
    for (col = 0; col < mColumn->mParentCtrl->getNumColumns(); col++)
    {
        LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col);
        if (!columnp || !columnp->mHeader) continue;
        bool enable = num_resizable_columns >= 2 && num_resizers_enabled < (num_resizable_columns - 1) && columnp->mHeader->canResize();
        columnp->mHeader->enableResizeBar(enable);
        if (enable)
        {
            num_resizers_enabled++;
        }
    }
}

void LLScrollColumnHeader::enableResizeBar(bool enable)
{
    mResizeBar->setEnabled(enable);
}

bool LLScrollColumnHeader::canResize()
{
    return getVisible() && (mHasResizableElement || mColumn->mDynamicWidth);
}

void LLScrollListColumn::SortNames::declareValues()
{
    declare("ascending", LLScrollListColumn::ASCENDING);
    declare("descending", LLScrollListColumn::DESCENDING);
}

//
// LLScrollListColumn
//
//static
const LLScrollListColumn::Params& LLScrollListColumn::getDefaultParams()
{
    return LLUICtrlFactory::getDefaultParams<LLScrollListColumn>();
}


LLScrollListColumn::LLScrollListColumn(const Params& p, LLScrollListCtrl* parent)
:   mWidth(0),
    mIndex (-1),
    mParentCtrl(parent),
    mName(p.name),
    mLabel(p.header.label),
    mHeader(NULL),
    mMaxContentWidth(0),
    mDynamicWidth(p.width.dynamic_width),
    mRelWidth(p.width.relative_width),
    mFontAlignment(p.halign),
    mSortingColumn(p.sort_column)
{
    if (p.sort_ascending.isProvided())
    {
        mSortDirection = p.sort_ascending() ? ASCENDING : DESCENDING;
    }
    else
    {
        mSortDirection = p.sort_direction;
    }

    setWidth(p.width.pixel_width);
}

void LLScrollListColumn::setWidth(S32 width)
{
    if (!mDynamicWidth && mRelWidth <= 0.f)
    {
        mParentCtrl->updateStaticColumnWidth(this, width);
    }
    mWidth = width;
}