/*
 * Copyright (C) 2019 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. AND ITS 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 APPLE INC. OR ITS 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.
 */

#pragma once

#if ENABLE(LAYOUT_FORMATTING_CONTEXT)

#include "LayoutRect.h"
#include "LayoutUnits.h"

namespace WebCore {
namespace Layout {

class Rect {
public:
    Rect() = default;
    Rect(LayoutUnit top, LayoutUnit left, LayoutUnit width, LayoutUnit height);
    Rect(LayoutPoint topLeft, LayoutUnit width, LayoutUnit height);
    Rect(const LayoutPoint& topLeft, const LayoutSize&);
    
    LayoutUnit top() const;
    LayoutUnit left() const;
    LayoutPoint topLeft() const;

    LayoutUnit bottom() const;
    LayoutUnit right() const;        
    LayoutPoint bottomRight() const;

    LayoutUnit width() const;
    LayoutUnit height() const;
    LayoutSize size() const;

    void setTop(LayoutUnit);
    void setLeft(LayoutUnit);
    void setTopLeft(const LayoutPoint&);
    void setWidth(LayoutUnit);
    void setHeight(LayoutUnit);
    void setSize(const LayoutSize&);

    void shiftLeftTo(LayoutUnit);
    void shiftRightTo(LayoutUnit);
    void shiftTopTo(LayoutUnit);
    void shiftBottomTo(LayoutUnit);

    void moveHorizontally(LayoutUnit);
    void moveVertically(LayoutUnit);
    void moveBy(LayoutPoint);

    void expand(Optional<LayoutUnit>, Optional<LayoutUnit>);
    void expandHorizontally(LayoutUnit delta) { expand(delta, { }); }
    void expandVertically(LayoutUnit delta) { expand({ }, delta); }
    void expandToContain(const Rect&);
    bool intersects(const Rect& rect) const { return m_rect.intersects(rect); }

    Rect clone() const;
    operator LayoutRect() const;

private:
#if ASSERT_ENABLED
    void invalidateTop() { m_hasValidTop = false; }
    void invalidateLeft() { m_hasValidLeft = false; }
    void invalidateWidth() { m_hasValidWidth = false; }
    void invalidateHeight() { m_hasValidHeight = false; }
    void invalidatePosition();

    bool hasValidPosition() const { return m_hasValidTop && m_hasValidLeft; }
    bool hasValidSize() const { return m_hasValidWidth && m_hasValidHeight; }
    bool hasValidGeometry() const { return hasValidPosition() && hasValidSize(); }

    void setHasValidPosition();
    void setHasValidSize();

    bool m_hasValidTop { false };
    bool m_hasValidLeft { false };
    bool m_hasValidWidth { false };
    bool m_hasValidHeight { false };
#endif // ASSERT_ENABLED
    LayoutRect m_rect;
};

inline Rect::Rect(LayoutUnit top, LayoutUnit left, LayoutUnit width, LayoutUnit height)
    : m_rect(left, top, width, height)
{
#if ASSERT_ENABLED
    m_hasValidTop = true;
    m_hasValidLeft = true;
    m_hasValidWidth = true;
    m_hasValidHeight = true;
#endif
}

inline Rect::Rect(LayoutPoint topLeft, LayoutUnit width, LayoutUnit height)
    : Rect(topLeft.y(), topLeft.x(), width, height)
{
}

inline Rect::Rect(const LayoutPoint& topLeft, const LayoutSize& size)
    : Rect(topLeft.y(), topLeft.x(), size.width(), size.height())
{
}

#if ASSERT_ENABLED
inline void Rect::invalidatePosition()
{
    invalidateTop();
    invalidateLeft();
}

inline void Rect::setHasValidPosition()
{
    m_hasValidTop = true;
    m_hasValidLeft = true;
}

inline void Rect::setHasValidSize()
{
    m_hasValidWidth = true;
    m_hasValidHeight = true;
}
#endif

inline LayoutUnit Rect::top() const
{
    ASSERT(m_hasValidTop);
    return m_rect.y();
}

inline LayoutUnit Rect::left() const
{
    ASSERT(m_hasValidLeft);
    return m_rect.x();
}

inline LayoutUnit Rect::bottom() const
{
    ASSERT(m_hasValidTop && m_hasValidHeight);
    return m_rect.maxY();
}

inline LayoutUnit Rect::right() const
{
    ASSERT(m_hasValidLeft && m_hasValidWidth);
    return m_rect.maxX();
}

inline LayoutPoint Rect::topLeft() const
{
    ASSERT(hasValidPosition());
    return m_rect.minXMinYCorner();
}

inline LayoutPoint Rect::bottomRight() const
{
    ASSERT(hasValidGeometry());
    return m_rect.maxXMaxYCorner();
}

inline LayoutSize Rect::size() const
{
    ASSERT(hasValidSize());
    return m_rect.size();
}

inline LayoutUnit Rect::width() const
{
    ASSERT(m_hasValidWidth);
    return m_rect.width();
}

inline LayoutUnit Rect::height() const
{
    ASSERT(m_hasValidHeight);
    return m_rect.height();
}

inline void Rect::setTopLeft(const LayoutPoint& topLeft)
{
#if ASSERT_ENABLED
    setHasValidPosition();
#endif
    m_rect.setLocation(topLeft);
}

inline void Rect::setTop(LayoutUnit top)
{
#if ASSERT_ENABLED
    m_hasValidTop = true;
#endif
    m_rect.setY(top);
}

inline void Rect::setLeft(LayoutUnit left)
{
#if ASSERT_ENABLED
    m_hasValidLeft = true;
#endif
    m_rect.setX(left);
}

inline void Rect::setWidth(LayoutUnit width)
{
#if ASSERT_ENABLED
    m_hasValidWidth = true;
#endif
    m_rect.setWidth(width);
}

inline void Rect::setHeight(LayoutUnit height)
{
#if ASSERT_ENABLED
    m_hasValidHeight = true;
#endif
    m_rect.setHeight(height);
}

inline void Rect::setSize(const LayoutSize& size)
{
#if ASSERT_ENABLED
    setHasValidSize();
#endif
    m_rect.setSize(size);
}

inline void Rect::shiftLeftTo(LayoutUnit left)
{
    ASSERT(m_hasValidLeft);
    m_rect.shiftXEdgeTo(left);
}

inline void Rect::shiftRightTo(LayoutUnit right)
{
    ASSERT(m_hasValidLeft && m_hasValidWidth);
    m_rect.shiftMaxXEdgeTo(right);
}

inline void Rect::shiftTopTo(LayoutUnit top)
{
    ASSERT(m_hasValidTop);
    m_rect.shiftYEdgeTo(top);
}

inline void Rect::shiftBottomTo(LayoutUnit bottom)
{
    ASSERT(m_hasValidTop && m_hasValidHeight);
    m_rect.shiftMaxYEdgeTo(bottom);
}

inline void Rect::moveHorizontally(LayoutUnit offset)
{
    ASSERT(m_hasValidLeft);
    m_rect.move(LayoutSize { offset, 0 });
}

inline void Rect::moveVertically(LayoutUnit offset)
{
    ASSERT(m_hasValidTop);
    m_rect.move(LayoutSize { 0, offset });
}

inline void Rect::moveBy(LayoutPoint offset)
{
    ASSERT(m_hasValidTop);
    ASSERT(m_hasValidLeft);
    m_rect.moveBy(offset);
}

inline void Rect::expand(Optional<LayoutUnit> width, Optional<LayoutUnit> height)
{
    ASSERT(!width || m_hasValidWidth);
    ASSERT(!height || m_hasValidHeight);
    m_rect.expand(width.valueOr(0), height.valueOr(0));
}

inline void Rect::expandToContain(const Rect& rect)
{
    ASSERT(m_hasValidWidth);
    ASSERT(m_hasValidHeight);
    m_rect.uniteEvenIfEmpty(rect);
}

inline Rect Rect::clone() const
{
    Rect rect;
#if ASSERT_ENABLED
    rect.m_hasValidTop = m_hasValidTop;
    rect.m_hasValidLeft = m_hasValidLeft;
    rect.m_hasValidWidth = m_hasValidWidth;
    rect.m_hasValidHeight  = m_hasValidHeight;
#endif 
    rect.m_rect = m_rect;
    return rect;
}

inline Rect::operator LayoutRect() const
{
    ASSERT(hasValidGeometry()); 
    return m_rect;
}

}
}
#endif
