/*
 * Copyright 2017 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "modules/sksg/include/SkSGGroup.h"

#include "include/core/SkCanvas.h"
#include "include/private/base/SkAssert.h"
#include "include/private/base/SkDebug.h"
#include "modules/sksg/include/SkSGNode.h"

#include <algorithm>

class SkMatrix;
struct SkPoint;

namespace sksg {
class InvalidationController;

Group::Group() = default;

Group::Group(std::vector<sk_sp<RenderNode>> children)
    : fChildren(std::move(children)) {
    for (const auto& child : fChildren) {
        this->observeInval(child);
    }
}

Group::~Group() {
    for (const auto& child : fChildren) {
        this->unobserveInval(child);
    }
}

void Group::clear() {
    for (const auto& child : fChildren) {
        this->unobserveInval(child);
    }
    fChildren.clear();
}

void Group::addChild(sk_sp<RenderNode> node) {
    // should we allow duplicates?
    for (const auto& child : fChildren) {
        if (child == node) {
            return;
        }
    }

    this->observeInval(node);
    fChildren.push_back(std::move(node));

    this->invalidate();
}

void Group::removeChild(const sk_sp<RenderNode>& node) {
    SkDEBUGCODE(const auto origSize = fChildren.size());
    fChildren.erase(std::remove(fChildren.begin(), fChildren.end(), node), fChildren.end());
    SkASSERT(fChildren.size() == origSize - 1);

    this->unobserveInval(node);
    this->invalidate();
}

void Group::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
    const auto local_ctx = ScopedRenderContext(canvas, ctx).setIsolation(this->bounds(),
                                                                         canvas->getTotalMatrix(),
                                                                         fRequiresIsolation);

    for (const auto& child : fChildren) {
        child->render(canvas, local_ctx);
    }
}

const RenderNode* Group::onNodeAt(const SkPoint& p) const {
    for (auto it = fChildren.crbegin(); it != fChildren.crend(); ++it) {
        if (const auto* node = (*it)->nodeAt(p)) {
            return node;
        }
    }

    return nullptr;
}

SkRect Group::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
    SkASSERT(this->hasInval());

    SkRect bounds = SkRect::MakeEmpty();
    fRequiresIsolation = false;

    for (size_t i = 0; i < fChildren.size(); ++i) {
        const auto child_bounds = fChildren[i]->revalidate(ic, ctm);

        // If any of the child nodes overlap, group effects require layer isolation.
        if (!fRequiresIsolation && i > 0 && child_bounds.intersects(bounds)) {
#if 1
            // Testing conservatively against the union of prev bounds is cheap and good enough.
            fRequiresIsolation = true;
#else
            // Testing exhaustively doesn't seem to increase the layer elision rate in practice.
            for (size_t j = 0; j < i; ++ j) {
                if (child_bounds.intersects(fChildren[i]->bounds())) {
                    fRequiresIsolation = true;
                    break;
                }
            }
#endif
        }

        bounds.join(child_bounds);
    }

    return bounds;
}

} // namespace sksg
