// SPDX-License-Identifier: GPL-2.0-or-later
/** @file
 * TODO: insert short description here
 *//*
 * Authors: see git history
 *
 * Copyright (C) 2018 Authors
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */
/*
 * Inkscape::XML::CompositeNodeObserver - combine multiple observers
 *
 * Copyright 2005 MenTaLguY <mental@rydia.net>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * See the file COPYING for details.
 *
 */

#include <algorithm>
#include <cstring>
#include <glib.h>

#include "xml/composite-node-observer.h"
#include "xml/node-event-vector.h"
#include "debug/event-tracker.h"
#include "debug/simple-event.h"

namespace Inkscape {

namespace XML {

void CompositeNodeObserver::notifyChildAdded(Node &node, Node &child, Node *prev)
{
    _startIteration();
    for (auto & iter : _active)
    {
        if (!iter.marked) {
            iter.observer.notifyChildAdded(node, child, prev);
        }
    }
    _finishIteration();
}

void CompositeNodeObserver::notifyChildRemoved(Node &node, Node &child,
                                                           Node *prev)
{
    _startIteration();
    for (auto & iter : _active)
    {
        if (!iter.marked) {
            iter.observer.notifyChildRemoved(node, child, prev);
        }
    }
    _finishIteration();
}

void CompositeNodeObserver::notifyChildOrderChanged(Node &node, Node &child,
                                                                Node *old_prev,
                                                                Node *new_prev)
{
    _startIteration();
    for (auto & iter : _active)
    {
        if (!iter.marked) {
            iter.observer.notifyChildOrderChanged(node, child, old_prev, new_prev);
        }
    }
    _finishIteration();
}

void CompositeNodeObserver::notifyContentChanged(
    Node &node,
    Util::ptr_shared old_content, Util::ptr_shared new_content
) {
    _startIteration();
    for (auto & iter : _active)
    {
        if (!iter.marked) {
            iter.observer.notifyContentChanged(node, old_content, new_content);
        }
    }
    _finishIteration();
}

void CompositeNodeObserver::notifyAttributeChanged(
    Node &node, GQuark name,
    Util::ptr_shared old_value, Util::ptr_shared new_value
) {
    _startIteration();
    for (auto & iter : _active)
    {
        if (!iter.marked) {
            iter.observer.notifyAttributeChanged(node, name, old_value, new_value);
        }
    }
    _finishIteration();
}

void CompositeNodeObserver::notifyElementNameChanged(Node& node, GQuark old_name, GQuark new_name)
{
    _startIteration();
    for (auto& iter : _active) {
        if (!iter.marked) {
            iter.observer.notifyElementNameChanged(node, old_name, new_name);
        }
    }
    _finishIteration();
}

void CompositeNodeObserver::add(NodeObserver &observer) {
    if (_iterating) {
        _pending.emplace_back(observer);
    } else {
        _active.emplace_back(observer);
    }
}

namespace {

class VectorNodeObserver : public NodeObserver, public GC::Managed<> {
public:
    VectorNodeObserver(NodeEventVector const &v, void *d)
    : vector(v), data(d) {}

    NodeEventVector const &vector;
    void * const data;

    void notifyChildAdded(Node &node, Node &child, Node *prev) override {
        if (vector.child_added) {
            vector.child_added(&node, &child, prev, data);
        }
    }

    void notifyChildRemoved(Node &node, Node &child, Node *prev) override {
        if (vector.child_removed) {
            vector.child_removed(&node, &child, prev, data);
        }
    }

    void notifyChildOrderChanged(Node &node, Node &child, Node *old_prev, Node *new_prev) override {
        if (vector.order_changed) {
            vector.order_changed(&node, &child, old_prev, new_prev, data);
        }
    }

    void notifyContentChanged(Node &node, Util::ptr_shared old_content, Util::ptr_shared new_content) override {
        if (vector.content_changed) {
            vector.content_changed(&node, old_content, new_content, data);
        }
    }

    void notifyAttributeChanged(Node &node, GQuark name, Util::ptr_shared old_value, Util::ptr_shared new_value) override {
        if (vector.attr_changed) {
            vector.attr_changed(&node, g_quark_to_string(name), old_value, new_value, false, data);
        }
    }

    void notifyElementNameChanged(Node& node, GQuark old_name, GQuark new_name) override {
        if (vector.element_name_changed) {
            vector.element_name_changed(&node, g_quark_to_string(old_name), g_quark_to_string(new_name), data);
        }
    }
};

}

void CompositeNodeObserver::addListener(NodeEventVector const &vector,
                                        void *data)
{
    Debug::EventTracker<Debug::SimpleEvent<Debug::Event::XML> > tracker("add-listener");
    add(*(new VectorNodeObserver(vector, data)));
}

namespace {

typedef CompositeNodeObserver::ObserverRecord ObserverRecord;
typedef CompositeNodeObserver::ObserverRecordList ObserverRecordList;

template <typename ObserverPredicate>
struct unmarked_record_satisfying {
    ObserverPredicate predicate;
    unmarked_record_satisfying(ObserverPredicate p) : predicate(p) {}
    bool operator()(ObserverRecord const &record) {
        return !record.marked && predicate(record.observer);
    }
};

template <typename Predicate>
bool mark_one(ObserverRecordList &observers, unsigned &marked_count,
              Predicate p)
{
    ObserverRecordList::iterator found=std::find_if(
        observers.begin(), observers.end(),
        unmarked_record_satisfying<Predicate>(p)
    );

    if ( found != observers.end() ) {
        ++marked_count;
        found->marked = true;
        return true;
    } else {
        return false;
    }
}

template <typename Predicate>
bool remove_one(ObserverRecordList &observers, unsigned &/*marked_count*/,
                Predicate p)
{
    ObserverRecordList::iterator found = std::find_if(
        observers.begin(), observers.end(),
        unmarked_record_satisfying<Predicate>(p)
    );

    if ( found != observers.end() ) {
        observers.erase(found);
        return true;
    } else {
        return false;
    }
}

bool is_marked(ObserverRecord const &record) { return record.marked; }

void remove_all_marked(ObserverRecordList &observers, unsigned &marked_count)
{
    if (marked_count) {
        g_assert(!observers.empty());

        observers.remove_if(is_marked);
        marked_count = 0;
    }
}

}

void CompositeNodeObserver::_finishIteration() {
    if (!--_iterating) {
        remove_all_marked(_active, _active_marked);
        remove_all_marked(_pending, _pending_marked);
        _active.splice(_active.end(), std::move(_pending));
        g_assert(_pending.empty());
    }
}

namespace {

struct eql_observer {
    NodeObserver const &observer;
    eql_observer(NodeObserver const &o) : observer(o) {}
    bool operator()(NodeObserver const &other) {
        return &observer == &other;
    }
};

}

void CompositeNodeObserver::remove(NodeObserver &observer) {
    eql_observer p(observer);
    if (_iterating) {
        mark_one(_active, _active_marked, p) ||
        mark_one(_pending, _pending_marked, p);
    } else {
        remove_one(_active, _active_marked, p) ||
        remove_one(_pending, _pending_marked, p);
    }
}

namespace {

struct vector_data_matches {
    void * const data;
    vector_data_matches(void *d) : data(d) {}
    
    bool operator()(NodeObserver const &observer) {
        VectorNodeObserver const *vo=dynamic_cast<VectorNodeObserver const *>(&observer);
        bool OK = false;
        if (vo) {
            if (vo && vo->data == data) {
                OK = true;
            }
        }
        return OK;
    }
};

}

void CompositeNodeObserver::removeListenerByData(void *data) {
    Debug::EventTracker<Debug::SimpleEvent<Debug::Event::XML> > tracker("remove-listener-by-data");
    vector_data_matches p(data);
    if (_iterating) {
        mark_one(_active, _active_marked, p) ||
        mark_one(_pending, _pending_marked, p);
    } else {
        remove_one(_active, _active_marked, p) ||
        remove_one(_pending, _pending_marked, p);
    }
}
    
}

}

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