/*
 * Copyright (C) 2009 Alex Milowski (alex@milowski.com). All rights reserved.
 * Copyright (C) 2010 François Sausset (sausset@gmail.com). 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "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 THE COPYRIGHT
 * OWNER 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(MATHML)

#include "RenderMathMLRoot.h"

#include "FontCache.h"
#include "GraphicsContext.h"
#include "PaintInfo.h"
#include "RenderIterator.h"
#include "RenderMathMLRadicalOperator.h"
#include "RenderMathMLSquareRoot.h"

namespace WebCore {
    
// RenderMathMLRoot implements drawing of radicals via the <mroot> and <msqrt> elements. For valid MathML elements, the DOM is
//
// <mroot> Base Index </mroot>
// <msqrt> Child1 Child2 ... ChildN </msqrt>
//
// and the structure of the render tree will be
//
// IndexWrapper RadicalWrapper BaseWrapper
//
// where RadicalWrapper contains an <mo>&#x221A;</mo>.
// For <mroot>, the IndexWrapper and BaseWrapper should contain exactly one child (Index and Base respectively).
// For <msqrt>, the IndexWrapper should be empty and the BaseWrapper can contain any number of children (Child1, ... ChildN).
//
// In order to accept invalid markup and to handle <mroot> and <msqrt> consistently, we will allow any number of children in the BaseWrapper of <mroot> too.
// We will allow the IndexWrapper to be empty and it will always contain the last child of the <mroot> if there are at least 2 elements.

RenderMathMLRoot::RenderMathMLRoot(Element& element, Ref<RenderStyle>&& style)
    : RenderMathMLBlock(element, WTFMove(style))
{
}

RenderMathMLRoot::RenderMathMLRoot(Document& document, Ref<RenderStyle>&& style)
    : RenderMathMLBlock(document, WTFMove(style))
{
}

RenderMathMLRootWrapper* RenderMathMLRoot::baseWrapper() const
{
    ASSERT(!isEmpty());
    return downcast<RenderMathMLRootWrapper>(lastChild());
}

RenderMathMLBlock* RenderMathMLRoot::radicalWrapper() const
{
    ASSERT(!isEmpty());
    return downcast<RenderMathMLBlock>(lastChild()->previousSibling());
}

RenderMathMLRootWrapper* RenderMathMLRoot::indexWrapper() const
{
    ASSERT(!isEmpty());
    return is<RenderMathMLSquareRoot>(*this) ? nullptr : downcast<RenderMathMLRootWrapper>(firstChild());
}

RenderMathMLRadicalOperator* RenderMathMLRoot::radicalOperator() const
{
    ASSERT(!isEmpty());
    return downcast<RenderMathMLRadicalOperator>(radicalWrapper()->firstChild());
}

void RenderMathMLRoot::restructureWrappers()
{
    ASSERT(!isEmpty());

    auto base = baseWrapper();
    auto index = indexWrapper();
    auto radical = radicalWrapper();

    // For visual consistency with the initial state, we remove the radical when the base/index wrappers become empty.
    if (base->isEmpty() && (!index || index->isEmpty())) {
        if (!radical->isEmpty()) {
            auto child = radicalOperator();
            radical->removeChild(*child);
            child->destroy();
        }
        // FIXME: early return!!!
    }

    if (radical->isEmpty()) {
        // We create the radical operator.
        RenderPtr<RenderMathMLRadicalOperator> radicalOperator = createRenderer<RenderMathMLRadicalOperator>(document(), RenderStyle::createAnonymousStyleWithDisplay(&style(), FLEX));
        radicalOperator->initializeStyle();
        radical->addChild(radicalOperator.leakPtr());
    }

    if (isRenderMathMLSquareRoot())
        return;

    if (auto childToMove = base->lastChild()) {
        // We move the last child of the base wrapper into the index wrapper if the index wrapper is empty and the base wrapper has at least two children.
        if (childToMove->previousSibling() && index->isEmpty()) {
            base->removeChildWithoutRestructuring(*childToMove);
            index->addChild(childToMove);
        }
    }

    if (auto childToMove = index->firstChild()) {
        // We move the first child of the index wrapper into the base wrapper if:
        // - either the index wrapper has at least two children.
        // - or the base wrapper is empty but the index wrapper is not.
        if (childToMove->nextSibling() || base->isEmpty()) {
            index->removeChildWithoutRestructuring(*childToMove);
            base->addChild(childToMove);
        }
    }
}

void RenderMathMLRoot::addChild(RenderObject* newChild, RenderObject* beforeChild)
{
    if (isEmpty()) {
        if (!isRenderMathMLSquareRoot()) {
            // We add the IndexWrapper.
            RenderMathMLBlock::addChild(RenderMathMLRootWrapper::createAnonymousWrapper(this).leakPtr());
        }

        // We create the radicalWrapper
        RenderMathMLBlock::addChild(RenderMathMLBlock::createAnonymousMathMLBlock().leakPtr());

        // We create the BaseWrapper.
        RenderMathMLBlock::addChild(RenderMathMLRootWrapper::createAnonymousWrapper(this).leakPtr());

        updateStyle();
    }

    // We insert the child.
    auto base = baseWrapper();
    auto index = indexWrapper();
    RenderElement* actualParent;
    RenderElement* actualBeforeChild;
    if (is<RenderMathMLSquareRoot>(*this)) {
        // For square root, we always insert the child into the base wrapper.
        actualParent = base;
        if (beforeChild && beforeChild->parent() == base)
            actualBeforeChild = downcast<RenderElement>(beforeChild);
        else
            actualBeforeChild = nullptr;
    } else {
        // For mroot, we insert the child into the parent of beforeChild, or at the end of the index. The wrapper structure is reorganize below.
        actualParent = beforeChild ? beforeChild->parent() : nullptr;
        if (actualParent == base || actualParent == index)
            actualBeforeChild = downcast<RenderElement>(beforeChild);
        else {
            actualParent = index;
            actualBeforeChild = nullptr;
        }
    }
    actualParent->addChild(newChild, actualBeforeChild);
    restructureWrappers();
}

void RenderMathMLRoot::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
{
    RenderMathMLBlock::styleDidChange(diff, oldStyle);
    if (!isEmpty())
        updateStyle();
}

void RenderMathMLRoot::updateFromElement()
{
    RenderMathMLBlock::updateFromElement();
    if (!isEmpty())
        updateStyle();
}

void RenderMathMLRoot::updateStyle()
{
    ASSERT(!isEmpty());

    // We set some constants to draw the radical, as defined in the OpenType MATH tables.

    m_ruleThickness = 0.05f * style().fontCascade().size();

    // FIXME: The recommended default for m_verticalGap in displaystyle is rule thickness + 1/4 x-height (https://bugs.webkit.org/show_bug.cgi?id=118737).
    m_verticalGap = 11 * m_ruleThickness / 4;
    m_extraAscender = m_ruleThickness;
    LayoutUnit kernBeforeDegree = 5 * style().fontCascade().size() / 18;
    LayoutUnit kernAfterDegree = -10 * style().fontCascade().size() / 18;
    m_degreeBottomRaisePercent = 0.6f;

    const auto& primaryFont = style().fontCascade().primaryFont();
    if (auto* mathData = style().fontCascade().primaryFont().mathData()) {
        // FIXME: m_verticalGap should use RadicalDisplayStyleVertical in display mode (https://bugs.webkit.org/show_bug.cgi?id=118737).
        m_verticalGap = mathData->getMathConstant(primaryFont, OpenTypeMathData::RadicalVerticalGap);
        m_ruleThickness = mathData->getMathConstant(primaryFont, OpenTypeMathData::RadicalRuleThickness);
        m_extraAscender = mathData->getMathConstant(primaryFont, OpenTypeMathData::RadicalExtraAscender);

        if (!isRenderMathMLSquareRoot()) {
            kernBeforeDegree = mathData->getMathConstant(primaryFont, OpenTypeMathData::RadicalKernBeforeDegree);
            kernAfterDegree = mathData->getMathConstant(primaryFont, OpenTypeMathData::RadicalKernAfterDegree);
            m_degreeBottomRaisePercent = mathData->getMathConstant(primaryFont, OpenTypeMathData::RadicalDegreeBottomRaisePercent);
        }
    }

    // We set the style of the anonymous wrappers.

    auto radical = radicalWrapper();
    auto radicalStyle = RenderStyle::createAnonymousStyleWithDisplay(&style(), FLEX);
    radicalStyle.get().setMarginTop(Length(0, Fixed)); // This will be updated in RenderMathMLRoot::layout().
    radical->setStyle(WTFMove(radicalStyle));
    radical->setNeedsLayoutAndPrefWidthsRecalc();

    auto base = baseWrapper();
    auto baseStyle = RenderStyle::createAnonymousStyleWithDisplay(&style(), FLEX);
    baseStyle.get().setMarginTop(Length(0, Fixed)); // This will be updated in RenderMathMLRoot::layout().
    baseStyle.get().setAlignItemsPosition(ItemPositionBaseline);
    base->setStyle(WTFMove(baseStyle));
    base->setNeedsLayoutAndPrefWidthsRecalc();

    if (!isRenderMathMLSquareRoot()) {
        // For mroot, we also set the style of the index wrapper.
        auto index = indexWrapper();
        auto indexStyle = RenderStyle::createAnonymousStyleWithDisplay(&style(), FLEX);
        indexStyle.get().setMarginTop(Length(0, Fixed)); // This will be updated in RenderMathMLRoot::layout().
        indexStyle.get().setMarginStart(Length(kernBeforeDegree, Fixed));
        indexStyle.get().setMarginEnd(Length(kernAfterDegree, Fixed));
        indexStyle.get().setAlignItemsPosition(ItemPositionBaseline);
        index->setStyle(WTFMove(indexStyle));
        index->setNeedsLayoutAndPrefWidthsRecalc();
    }
}

Optional<int> RenderMathMLRoot::firstLineBaseline() const
{
    if (!isEmpty()) {
        auto base = baseWrapper();
        return static_cast<int>(lroundf(base->firstLineBaseline().valueOr(-1) + base->marginTop()));
    }

    return RenderMathMLBlock::firstLineBaseline();
}

void RenderMathMLRoot::layout()
{
    if (isEmpty()) {
        RenderMathMLBlock::layout();
        return;
    }

    // FIXME: It seems that changing the top margin of the base below modifies its logical height and leads to reftest failures.
    // For now, we workaround that by avoiding to recompute the child margins if they were not reset in updateStyle().
    auto base = baseWrapper();
    if (base->marginTop() > 0) {
        RenderMathMLBlock::layout();
        return;
    }

    // We layout the children.
    for (RenderObject* child = firstChild(); child; child = child->nextSibling()) {
        if (child->needsLayout())
            downcast<RenderElement>(*child).layout();
    }

    auto radical = radicalOperator();
    if (radical) {
        // We stretch the radical sign to cover the height of the base wrapper.
        float baseHeight = base->logicalHeight();
        float baseHeightAboveBaseline = base->firstLineBaseline().valueOr(baseHeight);
        float baseDepthBelowBaseline = baseHeight - baseHeightAboveBaseline;
        baseHeightAboveBaseline += m_verticalGap;
        radical->stretchTo(baseHeightAboveBaseline, baseDepthBelowBaseline);

        // We modify the top margins to adjust the vertical positions of wrappers.
        float radicalTopMargin = m_extraAscender;
        float baseTopMargin = m_verticalGap + m_ruleThickness + m_extraAscender;
        if (!isRenderMathMLSquareRoot()) {
            // For mroot, we try to place the index so the space below its baseline is m_degreeBottomRaisePercent times the height of the radical.
            auto index = indexWrapper();
            float indexHeight = 0;
            if (!index->isEmpty())
                indexHeight = downcast<RenderBlock>(*index->firstChild()).logicalHeight();
            float indexTopMargin = (1.0 - m_degreeBottomRaisePercent) * radical->stretchSize() + radicalTopMargin - indexHeight;
            if (indexTopMargin < 0) {
                // If the index is too tall, we must add space at the top of renderer.
                radicalTopMargin -= indexTopMargin;
                baseTopMargin -= indexTopMargin;
                indexTopMargin = 0;
            }
            index->style().setMarginTop(Length(indexTopMargin, Fixed));
        }
        radical->style().setMarginTop(Length(radicalTopMargin, Fixed));
        base->style().setMarginTop(Length(baseTopMargin, Fixed));
    }

    RenderMathMLBlock::layout();
}

void RenderMathMLRoot::paint(PaintInfo& info, const LayoutPoint& paintOffset)
{
    RenderMathMLBlock::paint(info, paintOffset);
    
    if (isEmpty() || info.context().paintingDisabled() || style().visibility() != VISIBLE)
        return;

    auto base = baseWrapper();
    auto radical = radicalOperator();
    if (!base || !radical || !m_ruleThickness)
        return;

    // We draw the radical line.
    GraphicsContextStateSaver stateSaver(info.context());

    info.context().setStrokeThickness(m_ruleThickness);
    info.context().setStrokeStyle(SolidStroke);
    info.context().setStrokeColor(style().visitedDependentColor(CSSPropertyColor));

    // The preferred width of the radical is sometimes incorrect, so we draw a slightly longer line to ensure it touches the radical symbol (https://bugs.webkit.org/show_bug.cgi?id=130326).
    LayoutUnit sizeError = radical->trailingSpaceError();
    IntPoint adjustedPaintOffset = roundedIntPoint(paintOffset + location() + base->location() + LayoutPoint(-sizeError, -(m_verticalGap + m_ruleThickness / 2)));
    info.context().drawLine(adjustedPaintOffset, roundedIntPoint(LayoutPoint(adjustedPaintOffset.x() + base->offsetWidth() + sizeError, adjustedPaintOffset.y())));
}

RenderPtr<RenderMathMLRootWrapper> RenderMathMLRootWrapper::createAnonymousWrapper(RenderMathMLRoot* renderObject)
{
    RenderPtr<RenderMathMLRootWrapper> newBlock = createRenderer<RenderMathMLRootWrapper>(renderObject->document(), RenderStyle::createAnonymousStyleWithDisplay(&renderObject->style(), FLEX));
    newBlock->initializeStyle();
    return newBlock;
}

void RenderMathMLRootWrapper::removeChildWithoutRestructuring(RenderObject& child)
{
    RenderMathMLBlock::removeChild(child);
}

void RenderMathMLRootWrapper::removeChild(RenderObject& child)
{
    RenderMathMLBlock::removeChild(child);

    if (!(beingDestroyed() || documentBeingDestroyed()))
        downcast<RenderMathMLRoot>(*parent()).restructureWrappers();
}

}

#endif // ENABLE(MATHML)
