// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Editable view implementation
 *
 * Authors:
 *   Lauris Kaplinski <lauris@kaplinski.com>
 *   MenTaLguY <mental@rydia.net>
 *   bulia byak <buliabyak@users.sf.net>
 *   Ralf Stephan <ralf@ark.in-berlin.de>
 *   John Bintz <jcoswell@coswellproductions.org>
 *   Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
 *   Jon A. Cruz <jon@joncruz.org>
 *   Abhishek Sharma
 *
 * Copyright (C) 2007 Jon A. Cruz
 * Copyright (C) 2006-2008 Johan Engelen
 * Copyright (C) 2006 John Bintz
 * Copyright (C) 2004 MenTaLguY
 * Copyright (C) 1999-2002 Lauris Kaplinski
 * Copyright (C) 2000-2001 Ximian, Inc.
 *
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include "layer-model.h"

#include "document.h"
#include "layer-fns.h"
#include "object-hierarchy.h"

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

// Callbacks
static void _layer_activated(SPObject *layer, Inkscape::LayerModel *layer_model);
static void _layer_deactivated(SPObject *layer, Inkscape::LayerModel *layer_model);
static void _layer_changed(SPObject *top, SPObject *bottom, Inkscape::LayerModel *layer_model);

namespace Inkscape {

LayerModel::LayerModel()
    : _doc( nullptr )
    , _layer_hierarchy( nullptr )
    , _display_key( 0 )
{
}

LayerModel::~LayerModel()
{
    if (_layer_hierarchy) {
        delete _layer_hierarchy;
//        _layer_hierarchy = NULL; //this should be here, but commented to find other bug somewhere else.
    }
}

void LayerModel::setDocument(SPDocument *doc)
{
    _doc = doc;
    if (_layer_hierarchy) {
        _layer_hierarchy->clear();
        delete _layer_hierarchy;
    }
    _layer_hierarchy = new Inkscape::ObjectHierarchy(nullptr);
    _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
    _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
    _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_changed), this));
    _layer_hierarchy->setTop(doc->getRoot());
}

void LayerModel::setDisplayKey(unsigned int display_key)
{
    _display_key = display_key;
}

SPDocument *LayerModel::getDocument()
{
    return _doc;
}

/**
 * Returns current root (=bottom) layer.
 */
SPObject *LayerModel::currentRoot() const
{
    return _layer_hierarchy ? _layer_hierarchy->top() : nullptr;
}

/**
 * Returns current top layer.
 */
SPObject *LayerModel::currentLayer() const
{
    return _layer_hierarchy ? _layer_hierarchy->bottom() : nullptr;
}

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

/**
 * Sets the current layer of the desktop.
 *
 * Make \a object the top layer.
 */
void LayerModel::setCurrentLayer(SPObject *object) {
    g_return_if_fail(SP_IS_GROUP(object));
    g_return_if_fail( currentRoot() == object || (currentRoot() && currentRoot()->isAncestorOf(object)) );
    // printf("Set Layer to ID: %s\n", object->getId());
    _layer_hierarchy->setBottom(object);
}

void LayerModel::toggleHideAllLayers(bool hide) {

    for ( SPObject* obj = Inkscape::previous_layer(currentRoot(), currentRoot()); obj; obj = Inkscape::previous_layer(currentRoot(), obj) ) {
        SP_ITEM(obj)->setHidden(hide);
    }
}

void LayerModel::toggleLockAllLayers(bool lock) {

    for ( SPObject* obj = Inkscape::previous_layer(currentRoot(), currentRoot()); obj; obj = Inkscape::previous_layer(currentRoot(), obj) ) {
        SP_ITEM(obj)->setLocked(lock);
    }
}

void LayerModel::toggleLockOtherLayers(SPObject *object) {
    g_return_if_fail(SP_IS_GROUP(object));
    g_return_if_fail( currentRoot() == object || (currentRoot() && currentRoot()->isAncestorOf(object)) );

    bool othersLocked = false;
    std::vector<SPObject*> layers;
    for ( SPObject* obj = Inkscape::next_layer(currentRoot(), object); obj; obj = Inkscape::next_layer(currentRoot(), obj) ) {
        // Don't lock any ancestors, since that would in turn lock the layer as well
        if (!obj->isAncestorOf(object)) {
            layers.push_back(obj);
            othersLocked |= !SP_ITEM(obj)->isLocked();
        }
    }
    for ( SPObject* obj = Inkscape::previous_layer(currentRoot(), object); obj; obj = Inkscape::previous_layer(currentRoot(), obj) ) {
        if (!obj->isAncestorOf(object)) {
            layers.push_back(obj);
            othersLocked |= !SP_ITEM(obj)->isLocked();
        }
    }

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

    for ( std::vector<SPObject*>::iterator it = layers.begin(); it != layers.end(); ++it ) {
        SP_ITEM(*it)->setLocked(othersLocked);
    }
}


void LayerModel::toggleLayerSolo(SPObject *object) {
    g_return_if_fail(SP_IS_GROUP(object));
    g_return_if_fail( currentRoot() == object || (currentRoot() && currentRoot()->isAncestorOf(object)) );

    bool othersShowing = false;
    std::vector<SPObject*> layers;
    for ( SPObject* obj = Inkscape::next_layer(currentRoot(), object); obj; obj = Inkscape::next_layer(currentRoot(), obj) ) {
        // Don't hide ancestors, since that would in turn hide the layer as well
        if (!obj->isAncestorOf(object)) {
            layers.push_back(obj);
            othersShowing |= !SP_ITEM(obj)->isHidden();
        }
    }
    for ( SPObject* obj = Inkscape::previous_layer(currentRoot(), object); obj; obj = Inkscape::previous_layer(currentRoot(), obj) ) {
        if (!obj->isAncestorOf(object)) {
            layers.push_back(obj);
            othersShowing |= !SP_ITEM(obj)->isHidden();
        }
    }


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

    for ( std::vector<SPObject*>::iterator it = layers.begin(); it != layers.end(); ++it ) {
        SP_ITEM(*it)->setHidden(othersShowing);
    }
}

/**
 * Return layer that contains \a object.
 */
SPObject *LayerModel::layerForObject(SPObject *object) {
    g_return_val_if_fail(object != nullptr, NULL);

    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 LayerModel::isLayer(SPObject *object) const {
    return ( SP_IS_GROUP(object)
             && ( SP_GROUP(object)->effectiveLayerMode(_display_key)
                  == SPGroup::LAYER ) );
}

} // namespace Inkscape


/// Callback
static void
_layer_activated(SPObject *layer, Inkscape::LayerModel *layer_model) {
    g_return_if_fail(SP_IS_GROUP(layer));
    layer_model->_layer_activated_signal.emit(layer);
}

/// Callback
static void
_layer_deactivated(SPObject *layer, Inkscape::LayerModel *layer_model) {
    g_return_if_fail(SP_IS_GROUP(layer));
    layer_model->_layer_deactivated_signal.emit(layer);
}

/// Callback
static void
_layer_changed(SPObject *top, SPObject *bottom, Inkscape::LayerModel *layer_model)
{
    layer_model->_layer_changed_signal.emit (top, bottom);
}

/*
  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 :
