/*
 * Copyright 2020-2023 MicroEJ Corp. All rights reserved.
 * This library is provided in source code for use, modification and test, subject to license terms.
 * Any modification of the source code will break MicroEJ Corp. warranties on the whole library.
 */
package ej.mwt.render;

import ej.bon.Constants;
import ej.microui.MicroUI;
import ej.microui.display.Display;
import ej.microui.display.GraphicsContext;
import ej.mwt.Container;
import ej.mwt.Desktop;
import ej.mwt.Widget;

/**
 * This render policy renders the requested widget or its parent if transparent.
 * <p>
 * When a widget is asked to be rendered:
 * <ul>
 * <li>if the widget is transparent, its parent is asked to be rendered (and recursively),</li>
 * <li>otherwise, the widget is rendered asynchronously.</li>
 * </ul>
 * <p>
 * When one widget is asked to be rendered, a flush is requested to the display. When several widgets are asked to be
 * rendered in a row, only one flush is done at the end.
 *
 * @see Widget#isTransparent()
 * @see Display#requestFlush()
 */
public class DefaultRenderPolicy extends RenderPolicy {

    /**
     * Creates a default render policy.
     *
     * @param desktop
     *            the desktop.
     */
    public DefaultRenderPolicy(Desktop desktop) {
        super(desktop);
    }

    /**
     * {@inheritDoc}
     * <p>
     * The graphics context is translated to the position of the parent of the widget. The clipping area is set to the
     * intersection of the parent's bounds and the given bounds.
     */
    @Override
    public void renderDesktop() {
        Desktop desktop = getDesktop();
        Widget widget = desktop.getWidget();
        if (widget != null) {
            // reset translation and clip
            GraphicsContext g = Display.getDisplay().getGraphicsContext();
            g.resetTranslation();
            g.resetClip();
            // render widget
            if (widget.isShown()) {
                renderWidget(g, widget);
            }
        }
    }

    @Override
    public void requestRender(Widget widget, int x, int y, int width, int height) {
        if (Constants.getBoolean(DEBUG_RENDER_ENABLED_CONSTANT)) {
            assert MONITOR != null;
            MONITOR.onRenderRequested(widget, x, y, width, height);
        }
        while (widget.isTransparent()) {
            Container parent = widget.getParent();
            if (parent == null) {
                break;
            }
            // Render the parent.
            x += parent.getContentX() + widget.getX();
            y += parent.getContentY() + widget.getY();
            widget = parent;
        }
        asynchronousRender(widget, x, y, width, height);
    }

    /**
     * This method performs the increment render of the widget. Its implementation only renders the widget and not its
     * siblings, but this behavior may be changed by overriding this method.
     * <p>
     * The given bounds are relative to the widget.
     *
     * @param widget
     *            the widget to render.
     * @param x
     *            the x coordinate of the area to render.
     * @param y
     *            the y coordinate of the area to render.
     * @param width
     *            the width of the area to render.
     * @param height
     *            the height of the area to render.
     */
    protected void renderWidget(Widget widget, int x, int y, int width, int height) {
        // reset translation and clip
        GraphicsContext g = Display.getDisplay().getGraphicsContext();
        g.resetTranslation();
        g.resetClip();
        // compute translation and clip
        setParentClip(widget, g);
        g.intersectClip(widget.getX() + x, widget.getY() + y, width, height);
        // render widget
        renderWidget(g, widget);
    }

    /**
     * Clips and translates a graphics context to the absolute bounds of the parent of a widget.
     * <p>
     * It applies recursively a translation to each parent's coordinates, and a clip to its content size.
     *
     * @param widget
     *            the widget to clip from.
     * @param g
     *            the graphics context to clip on.
     */
    protected void setParentClip(Widget widget, GraphicsContext g) {
        Container parent = widget.getParent();
        if (parent != null) {
            setParentClip(parent, g);
            g.translate(parent.getX() + parent.getContentX(), parent.getY() + parent.getContentY());
            g.intersectClip(0, 0, parent.getContentWidth(), parent.getContentHeight());
        }
    }
}
