/*
 * Copyright (C) 2009 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */

#include "config.h"

#if ENABLE(DATAGRID)

#include "RenderDataGrid.h"

#include "CSSStyleSelector.h"
#include "FocusController.h"
#include "Frame.h"
#include "GraphicsContext.h"
#include "Page.h"
#include "RenderView.h"
#include "Scrollbar.h"

using std::min;

namespace WebCore {

static const int cDefaultWidth = 300;

RenderDataGrid::RenderDataGrid(Element* elt)
    : RenderBlock(elt)
{
    if (Page* page = frame()->page()) {
        m_page = page;
        m_page->addScrollableArea(this);
    }
}

RenderDataGrid::~RenderDataGrid()
{
    if (m_page)
        m_page->removeScrollableArea(this);
}

void RenderDataGrid::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
{
    RenderBlock::styleDidChange(diff, oldStyle);
    recalcStyleForColumns();
}

void RenderDataGrid::recalcStyleForColumns()
{
    DataGridColumnList* columns = gridElement()->columns();
    unsigned length = columns->length();
    for (unsigned i = 0; i < length; ++i)
        recalcStyleForColumn(columns->item(i));
}

void RenderDataGrid::recalcStyleForColumn(DataGridColumn* column)
{
    if (!column->columnStyle())
        column->setColumnStyle(document()->styleSelector()->pseudoStyleForDataGridColumn(column, style()));
    if (!column->headerStyle())
        column->setHeaderStyle(document()->styleSelector()->pseudoStyleForDataGridColumnHeader(column, style()));
}

RenderStyle* RenderDataGrid::columnStyle(DataGridColumn* column)
{
    if (!column->columnStyle())
        recalcStyleForColumn(column);
    return column->columnStyle();
}

RenderStyle* RenderDataGrid::headerStyle(DataGridColumn* column)
{
    if (!column->headerStyle())
        recalcStyleForColumn(column);
    return column->headerStyle();
}

void RenderDataGrid::computePreferredLogicalWidths()
{
    m_minPreferredLogicalWidth = 0;
    m_maxPreferredLogicalWidth = 0;

    if (style()->width().isFixed() && style()->width().value() > 0)
        m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = computeContentBoxLogicalWidth(style()->width().value());
    else
        m_maxPreferredLogicalWidth = computeContentBoxLogicalWidth(cDefaultWidth);

    if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
        m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value()));
        m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value()));
    } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
        m_minPreferredLogicalWidth = 0;
    else
        m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth;

    if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
        m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value()));
        m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value()));
    }

    int toAdd = borderAndPaddingWidth();
    m_minPreferredLogicalWidth += toAdd;
    m_maxPreferredLogicalWidth += toAdd;
                                
    setPreferredLogicalWidthsDirty(false);
}

void RenderDataGrid::layout()
{
    RenderBlock::layout();
    layoutColumns();
}

void RenderDataGrid::layoutColumns()
{
    // FIXME: Implement.
}

void RenderDataGrid::paintObject(PaintInfo& paintInfo, int tx, int ty)
{
    if (style()->visibility() != VISIBLE)
        return;

    // Paint our background and border.
    RenderBlock::paintObject(paintInfo, tx, ty);
    
    if (paintInfo.phase != PaintPhaseForeground)
        return;

    // Paint our column headers first.
    paintColumnHeaders(paintInfo, tx, ty);
}

void RenderDataGrid::paintColumnHeaders(PaintInfo& paintInfo, int tx, int ty)
{
    DataGridColumnList* columns = gridElement()->columns();
    unsigned length = columns->length();
    for (unsigned i = 0; i < length; ++i) {
        DataGridColumn* column = columns->item(i);
        RenderStyle* columnStyle = headerStyle(column);

        // Don't render invisible columns.
        if (!columnStyle || columnStyle->display() == NONE || columnStyle->visibility() != VISIBLE)
            continue;
        
        // Paint the column header if it intersects the dirty rect.
        IntRect columnRect(column->rect());
        columnRect.move(tx, ty);
        if (columnRect.intersects(paintInfo.rect))
            paintColumnHeader(column, paintInfo, tx, ty);
    }
}

void RenderDataGrid::paintColumnHeader(DataGridColumn*, PaintInfo&, int, int)
{
    // FIXME: Implement.
}

// Scrolling implementation functions
int RenderDataGrid::scrollSize(ScrollbarOrientation orientation) const
{
    return ((orientation == VerticallScrollbar) && m_vBar) ? (m_vBar->totalSize() - m_vBar->visibleSize()) : 0;
}

void RenderDataGrid::setScrollOffsetFromAnimation(const IntPoint& offset)
{
    if (m_vBar)
        m_vBar->setValue(offset.y(), Scrollbar::FromScrollAnimator);
}

void RenderDataGrid::valueChanged(Scrollbar*)
{
    // FIXME: Implement.
}

void RenderDataGrid::invalidateScrollbarRect(Scrollbar*, const IntRect&)
{
    // FIXME: Implement.
}

bool RenderDataGrid::isActive() const
{
    Page* page = frame()->page();
    return page && page->focusController()->isActive();
}


IntRect RenderDataGrid::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntRect& scrollbarRect) const
{
    RenderView* view = this->view();
    if (!view)
        return scrollbarRect;

    IntRect rect = scrollbarRect;

    int scrollbarLeft = width() - borderRight() - scrollbar->width();
    int scrollbarTop = borderTop();
    rect.move(scrollbarLeft, scrollbarTop);

    return view->frameView()->convertFromRenderer(this, rect);
}

IntRect RenderDataGrid::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntRect& parentRect) const
{
    RenderView* view = this->view();
    if (!view)
        return parentRect;

    IntRect rect = view->frameView()->convertToRenderer(this, parentRect);

    int scrollbarLeft = width() - borderRight() - scrollbar->width();
    int scrollbarTop = borderTop();
    rect.move(-scrollbarLeft, -scrollbarTop);
    return rect;
}

IntPoint RenderDataGrid::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntPoint& scrollbarPoint) const
{
    RenderView* view = this->view();
    if (!view)
        return scrollbarPoint;

    IntPoint point = scrollbarPoint;

    int scrollbarLeft = width() - borderRight() - scrollbar->width();
    int scrollbarTop = borderTop();
    point.move(scrollbarLeft, scrollbarTop);

    return view->frameView()->convertFromRenderer(this, point);
}

IntPoint RenderDataGrid::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntPoint& parentPoint) const
{
    RenderView* view = this->view();
    if (!view)
        return parentPoint;

    IntPoint point = view->frameView()->convertToRenderer(this, parentPoint);

    int scrollbarLeft = width() - borderRight() - scrollbar->width();
    int scrollbarTop = borderTop();
    point.move(-scrollbarLeft, -scrollbarTop);
    return point;
}

bool RenderDataGrid::shouldSuspendScrollAnimations() const
{
    RenderView* view = this->view();
    if (!view)
        return true;
    return view->frameView()->shouldSuspendScrollAnimations();
}

}

#endif
