// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Inkscape::LayerManager
 *
 * Owned by the Desktop, this manager tracks layer objects as
 * distinct entities from groups, providing a comprehensive set
 * of utilities, signals and other Layer based organisation.
 *
 * Refactored from LayerManager and layer-fns in 2021.
 *
 * Copyright 2001 Ximian, Inc.
 *           2002 Lauris Kaplinski <lauris@kaplinski.com>
 *           2006 John Bintz <jcoswell@coswellproductions.org>
 *           2006 MenTaLguY <mental@rydia.net>
 *           2007 Jon A. Cruz <jon@joncruz.org>
 *           2008 Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
 *           2010 Abhishek Sharma
 *           2021 Martin Owens <doctormo@geek-2.com>
 *
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include <set>

#include <sigc++/functors/mem_fun.h>
#include <sigc++/adaptors/hide.h>


#include "desktop.h"
#include "document.h"
#include "gc-finalized.h"
#include "layer-manager.h"
#include "selection.h"

#include "inkgc/gc-managed.h"

#include "object-hierarchy.h"
#include "object/sp-defs.h"
#include "object/sp-root.h"
#include "object/sp-item-group.h"

#include "xml/node-observer.h"

namespace Inkscape {

LayerManager::LayerManager(SPDesktop *desktop)
    : _desktop(desktop)
    , _document(nullptr)
{
    _layer_hierarchy = std::make_unique<Inkscape::ObjectHierarchy>(nullptr);
    _layer_hierarchy->connectAdded(sigc::mem_fun(*this, &LayerManager::_layer_activated));
    _layer_hierarchy->connectRemoved(sigc::mem_fun(*this, &LayerManager::_layer_deactivated));
    _layer_hierarchy->connectChanged(sigc::mem_fun(*this, &LayerManager::_selectedLayerChanged));
    _document_connection = desktop->connectDocumentReplaced(sigc::mem_fun(this, &LayerManager::_setDocument));
    _setDocument(desktop, desktop->doc());
}

LayerManager::~LayerManager()
{
    _layer_connection.disconnect();
    _activate_connection.disconnect();
    _deactivate_connection.disconnect();
    _document_connection.disconnect();
    _resource_connection.disconnect();
    _document = nullptr;
}

void LayerManager::_setDocument(SPDesktop *, SPDocument *document) {
    _layer_hierarchy->clear();
    _resource_connection.disconnect();
    _document = document;
    if (document) {
        _resource_connection = document->connectResourcesChanged("layer", sigc::mem_fun(*this, &LayerManager::_rebuild));
        _layer_hierarchy->setTop(document->getRoot());
    }
    _rebuild();
}

void LayerManager::_layer_activated(SPObject *layer)
{
    if (auto group = dynamic_cast<SPGroup *>(layer)) {
        group->setLayerDisplayMode(_desktop->dkey, SPGroup::LAYER);
    }
}

void LayerManager::_layer_deactivated(SPObject *layer)
{
    if (auto group = dynamic_cast<SPGroup *>(layer)) {
        group->setLayerDisplayMode(_desktop->dkey, SPGroup::GROUP);
    }
}

/**
 * Returns current root (=bottom) layer.
 */
SPGroup *LayerManager::currentRoot() const
{
    return dynamic_cast<SPGroup *>(_layer_hierarchy->top());
}

/**
 * Returns current top layer.
 */
SPGroup *LayerManager::currentLayer() const
{
    return dynamic_cast<SPGroup *>(_layer_hierarchy->bottom());
}

/**
 * Resets the bottom layer to the current root
 */
void LayerManager::reset() {
    _layer_hierarchy->setBottom(currentRoot());
}


/*
 * Return a unique layer name similar to param label
 * A unique name is made by substituting or appending the label's number suffix with
 * the next unique larger number suffix not already used for any layer name
 */
Glib::ustring LayerManager::getNextLayerName( SPObject* obj, gchar const *label)
{
    Glib::ustring incoming( label ? label : "Layer 1" );
    Glib::ustring result(incoming);
    Glib::ustring base(incoming);
    Glib::ustring split(" ");
    guint startNum = 1;

    gint pos = base.length()-1;
    while (pos >= 0 && g_ascii_isdigit(base[pos])) {
        pos-- ;
    }

    gchar* numpart = g_strdup(base.substr(pos+1).c_str());
    if ( numpart ) {
        gchar* endPtr = nullptr;
        guint64 val = g_ascii_strtoull( numpart, &endPtr, 10);
        if ( ((val > 0) || (endPtr != numpart)) && (val < 65536) ) {
            base.erase( pos+1);
            result = incoming;
            startNum = static_cast<int>(val);
            split = "";
        }
        g_free(numpart);
    }

    std::set<Glib::ustring> currentNames;
    std::vector<SPObject *> layers = _document->getResourceList("layer");
    if (currentRoot()) {
        for (auto layer : layers) { 
            if (layer != obj)
                currentNames.insert(layer->label() ? Glib::ustring(layer->label()) : Glib::ustring());
        }
    }

    // Not sure if we need to cap it, but we'll just be paranoid for the moment
    // Intentionally unsigned
    guint endNum = startNum + 3000;
    for ( guint i = startNum; (i < endNum) && (currentNames.find(result) != currentNames.end()); i++ ) {
        result = Glib::ustring::format(base, split, i);
    }

    return result;
}

void LayerManager::renameLayer( SPObject* obj, gchar const *label, bool uniquify )
{
    Glib::ustring incoming( label ? label : "" );
    Glib::ustring result(incoming);

    if (uniquify) {
        result = getNextLayerName(obj, label);
    }

    obj->setLabel( result.c_str() );
}

/**
 * Sets the current layer of the desktop.
 *
 * Make \a object the top layer.
 */
void LayerManager::setCurrentLayer(SPObject *object, bool clear) {
    if (currentRoot()) {
        g_return_if_fail(SP_IS_GROUP(object));
        g_return_if_fail( currentRoot() == object || (currentRoot() && currentRoot()->isAncestorOf(object)) );
        _layer_hierarchy->setBottom(object);

        Inkscape::Preferences *prefs = Inkscape::Preferences::get();
        if (clear && prefs->getBool("/options/selection/layerdeselect", true)) {
            _desktop->getSelection()->clear();
        }
    }
}

std::list<SPItem *> LayerManager::getAllLayers()
{
    std::list<SPItem *> layers;
    for (SPObject *obj = Inkscape::previous_layer(currentRoot(), currentRoot()); obj;
         obj = Inkscape::previous_layer(currentRoot(), obj)) {
        auto item = SP_ITEM(obj);
        layers.push_back(item);
    }
    return layers;
}

void LayerManager::toggleHideAllLayers(bool hide) {
    for ( SPObject* obj = Inkscape::previous_layer(currentRoot(), currentRoot()); obj; obj = Inkscape::previous_layer(currentRoot(), obj) ) {
        SP_ITEM(obj)->setHidden(hide);
    }
}
void LayerManager::toggleLockAllLayers(bool lock) {
    for ( SPObject* obj = Inkscape::previous_layer(currentRoot(), currentRoot()); obj; obj = Inkscape::previous_layer(currentRoot(), obj) ) {
        SP_ITEM(obj)->setLocked(lock);
    }
}


void LayerManager::_rebuild() {
//     Debug::EventTracker<DebugLayerRebuild> tracker1();

    _clear();

    if (!_document || !_desktop)
        return;

    std::vector<SPObject *> layers = _document->getResourceList("layer");

    if (auto root = _desktop->layerManager().currentRoot()) {
        _addOne(root);
        std::set<SPGroup *> layersToAdd;

        for (auto &layer : layers) {
            bool needsAdd = false;
            std::set<SPGroup *> additional;

            if (root->isAncestorOf(layer)) {
                needsAdd = true;
                for (SPObject* curr = layer; curr && (curr != root) && needsAdd; curr = curr->parent) {
                    if (auto group = dynamic_cast<SPGroup *>(curr)) {
                        if (group->isLayer()) {
                            // If we have a layer-group as the one or a parent, ensure it is listed as a valid layer.
                            needsAdd &= ( std::find(layers.begin(),layers.end(),curr) != layers.end() );
			    // XML Tree being used here directly while it shouldn't be...
                            if ( (!(group->getRepr())) || (!(group->getRepr()->parent())) ) {
                                needsAdd = false;
                            }
                        } else {
                            // If a non-layer group is a parent of layer groups, then show it also as a layer.
                            // TODO add the magic Inkscape group mode?
                            // XML Tree being used directly while it shouldn't be...
                            if ( group->getRepr() && group->getRepr()->parent() ) {
                                additional.insert(group);
                            } else {
                                needsAdd = false;
                            }
                        }
                    }
                }
            }
            if (needsAdd) {
                if (!includes(layer)) {
                    layersToAdd.insert(SP_GROUP(layer));
                }
                for (auto it : additional) {
                    if (!includes(it)) {
                        layersToAdd.insert(it);
                    }
                }
            }
        }

        for (auto layer : layersToAdd) {
            // Filter out objects in the middle of being deleted

            // Such may have been the cause of bug 1339397.
            // See http://sourceforge.net/tracker/index.php?func=detail&aid=1339397&group_id=93438&atid=604306

            SPObject const *higher = layer;
            while ( higher && (higher->parent != root) ) {
                higher = higher->parent;
            }
            Inkscape::XML::Node const* node = higher ? higher->getRepr() : nullptr;
            if ( node && node->parent() ) {
                _addOne(layer);
            }
        }
    }
}

static bool is_layer(SPObject &object) {
    return SP_IS_GROUP(&object) &&
           SP_GROUP(&object)->layerMode() == SPGroup::LAYER;
}

void LayerManager::_selectedLayerChanged(SPObject *top, SPObject *bottom)
{
    if (auto group = dynamic_cast<SPGroup *>(bottom)) {
        _layer_changed_signal.emit(group);
    }
}

/** Finds the next sibling layer for a \a layer
 *
 *  @returns NULL if there are no further layers under a parent
 */
static SPObject *next_sibling_layer(SPObject *layer) {
    if (layer->parent == nullptr) {
      return nullptr;
    }
    SPObject::ChildrenList &list = layer->parent->children;
    auto l = std::find_if(++list.iterator_to(*layer), list.end(), &is_layer);
    return l != list.end() ? &*l : nullptr;
}

/** Finds the previous sibling layer for a \a layer
 *
 *  @returns NULL if there are no further layers under a parent
 */
static SPObject *previous_sibling_layer(SPObject *layer) {
    SPObject::ChildrenList &list = layer->parent->children;
    auto start = SPObject::ChildrenList::reverse_iterator(list.iterator_to(*layer));
    auto l = std::find_if(start, list.rend(), &is_layer);
    return l != list.rend() ? &*l : nullptr;
}

/** Finds the first child of a \a layer
 *
 *  @returns the layer itself if layer has no sublayers
 */
static SPObject *first_descendant_layer(SPObject *layer) {
    while (true) {
        auto first_descendant = std::find_if(layer->children.begin(), layer->children.end(), &is_layer);
        if (first_descendant == layer->children.end()) {
            break;
        }
        layer = &*first_descendant;
    }

    return layer;
}

/** Finds the last (topmost) child of a \a layer
 *
 *  @returns NULL if layer has no sublayers
 */
static SPObject *last_child_layer(SPObject *layer) {
    auto& list = layer->children;
    auto l = std::find_if(list.rbegin(), list.rend(), &is_layer);
    return l != list.rend() ? &*l : nullptr;
}

static SPObject *last_elder_layer(SPObject *root, SPObject *layer) {
    SPObject *result = nullptr;

    while ( layer != root ) {
        SPObject *sibling(previous_sibling_layer(layer));
        if (sibling) {
            result = sibling;
            break;
        }
        layer = layer->parent;
    }

    return result;
}

/** Finds the next layer under \a root, relative to \a layer in
 *  depth-first order.
 *
 *  @returns NULL if there are no further layers under \a root
 */
SPObject *next_layer(SPObject *root, SPObject *layer) {
    g_return_val_if_fail(layer != nullptr, NULL);
    SPObject *result = nullptr;

    SPObject *sibling = next_sibling_layer(layer);
    if (sibling) {
        result = first_descendant_layer(sibling);
    } else if ( layer->parent != root ) {
        result = layer->parent;
    }

    return result;
}


/** Finds the previous layer under \a root, relative to \a layer in
 *  depth-first order.
 *
 *  @returns NULL if there are no prior layers under \a root.
 */
SPObject *previous_layer(SPObject *root, SPObject *layer) {
    g_return_val_if_fail(layer != nullptr, NULL);
    SPObject *result = nullptr;

    SPObject *child = last_child_layer(layer);
    if (child) {
        result = child;
    } else if ( layer != root ) {
        SPObject *sibling = previous_sibling_layer(layer);
        if (sibling) {
            result = sibling;
        } else {
            result = last_elder_layer(root, layer->parent);
        }
    }

    return result;
}

/**
*  Creates a new layer.  Advances to the next layer id indicated
 *  by the string "layerNN", then creates a new group object of
 *  that id with attribute inkscape:groupmode='layer', and finally
 *  appends the new group object to \a root after object \a layer.
 *
 *  \pre \a root should be either \a layer or an ancestor of it
 */
SPObject *create_layer(SPObject *root, SPObject *layer, LayerRelativePosition position) {
    SPDocument *document = root->document;

    static int layer_suffix=1;
    gchar *id=nullptr;
    do {
        g_free(id);
        id = g_strdup_printf("layer%d", layer_suffix++);
    } while (document->getObjectById(id));

    Inkscape::XML::Document *xml_doc = document->getReprDoc();
    Inkscape::XML::Node *repr = xml_doc->createElement("svg:g");
    repr->setAttribute("inkscape:groupmode", "layer");
    repr->setAttribute("id", id);
    g_free(id);

    if ( LPOS_CHILD == position ) {
        root = layer;
        SPObject *child_layer = Inkscape::last_child_layer(layer);
        if ( nullptr != child_layer ) {
            layer = child_layer;
        }
    }

    if ( root == layer ) {
        root->getRepr()->appendChild(repr);
    } else {
        Inkscape::XML::Node *layer_repr = layer->getRepr();
        layer_repr->parent()->addChild(repr, layer_repr);

        if ( LPOS_BELOW == position ) {
            SP_ITEM(document->getObjectByRepr(repr))->lowerOne();
        }
    }

    return document->getObjectByRepr(repr);
}

std::vector<SPItem*> get_layers_to_toggle(SPObject* layer, SPObject* current_root) {
    std::vector<SPItem*> layers;
    if (!SP_IS_GROUP(layer) ||
        !(current_root == layer || (current_root && current_root->isAncestorOf(layer)))) {
        g_warning("Bogus input to get_layers_to_toggle_toggle");
        return layers;
    }

    for (SPObject* obj = Inkscape::next_layer(current_root, layer); obj; obj = Inkscape::next_layer(current_root, obj)) {
        // skip ancestors
        auto item = SP_ITEM(obj);
        if (!obj->isAncestorOf(layer) && item) {
            layers.push_back(item);
        }
    }
    for (SPObject* obj = Inkscape::previous_layer(current_root, layer); obj; obj = Inkscape::previous_layer(current_root, obj)) {
        auto item = SP_ITEM(obj);
        if (!obj->isAncestorOf(layer) && item) {
            layers.push_back(item);
        }
    }

    return layers;
}

/**
 * Toggle the sensitivity of every layer except the given layer.
 */
void LayerManager::toggleLockOtherLayers(SPObject *object, bool force_lock) {
    auto layers = get_layers_to_toggle(object, currentRoot());
    if (layers.empty()) return;

    bool othersLocked = force_lock ? true : std::any_of(layers.begin(), layers.end(), [](SPItem* l){ return !l->isLocked(); });

    if (SPItem* item = SP_ITEM(object)) {
        if (item->isLocked()) {
            item->setLocked(false);
        }
    }

    for (auto layer : layers) {
        if (layer->isLocked() != othersLocked) {
            layer->setLocked(othersLocked);
        }
    }
}

/**
 * Toggle the visibility of every layer except the given layer.
 */
void LayerManager::toggleLayerSolo(SPObject *object, bool force_hide) {
    auto layers = get_layers_to_toggle(object, currentRoot());
    if (layers.empty()) return;

    bool othersShowing = force_hide ? true : std::any_of(layers.begin(), layers.end(), [](SPItem* l){ return !l->isHidden(); });

    if (SPItem* item = SP_ITEM(object)) {
        if (item->isHidden()) {
            item->setHidden(false);
        }
    }

    for (auto & layer : layers) {
        if (layer->isHidden() != othersShowing) {
            layer->setHidden(othersShowing);
        }
    }
}

/**
 * Return layer that contains \a object.
 */
SPObject *LayerManager::layerForObject(SPObject *object) {
    g_return_val_if_fail(object != nullptr, NULL);
    if (isLayer(object)) {
        return object;
    }

    SPObject *root=currentRoot();
    object = object->parent;
    while ( object && object != root && !isLayer(object) ) {
        // Objects in defs have no layer and are NOT in the root layer
        if(SP_IS_DEFS(object))
            return nullptr;
        object = object->parent;
    }
    return object;
}

/**
 * True if object is a layer.
 */
bool LayerManager::isLayer(SPObject *object) const
{
    if (auto group = dynamic_cast<SPGroup *>(object)) {
        return group->effectiveLayerMode(_desktop->dkey) == SPGroup::LAYER;
    }
    return false;
}

/**
 * Return the SPGroup if we have a layer object.
 */
SPGroup *LayerManager::asLayer(SPObject *object)
{
    if (auto group = dynamic_cast<SPGroup *>(object)) {
        return group->isLayer() ? group : nullptr;
    }
    return nullptr;
}

}

/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
  indent-tabs-mode:nil
  fill-column:99
  End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
