/***************************************************************************
        Copyright (C) 2002-2026 Kentaro Kitagawa
                           kitag@issp.u-tokyo.ac.jp

        This program is free software; you can redistribute it and/or
        modify it under the terms of the GNU Library General Public
        License as published by the Free Software Foundation; either
        version 2 of the License, or (at your option) any later version.

        You should have received a copy of the GNU Library General
        Public License and a list of authors along with this program;
        see the files COPYING and AUTHORS.
***************************************************************************/
#ifndef XITEMNODE_H_
#define XITEMNODE_H_

#include "xnode.h"
#include "xlistnode.h"

//! Posses a pointer to a member of a list
class DECLSPEC_KAME XItemNodeBase : public XValueNodeBase {
public:
    explicit XItemNodeBase(const char *name, bool runtime = false, bool auto_set_any = false);
    virtual ~XItemNodeBase() = default;

    struct Item { XString name, label; };
    virtual std::vector<Item> itemStrings(const Snapshot &shot_of_list) const = 0;

    bool autoSetAny() const {return !!m_lsnTryAutoSet;}

    struct DECLSPEC_KAME Payload : public XValueNodeBase::Payload {
        struct ListChangeEvent {
            Snapshot shot_of_list; //!< Re-broadcasters must fill this with a fresh snapshot,
                //!< not forward a stale one. Immediate listeners may mutate the list
                //!< between re-broadcasts, and FLAG_AVOID_DUP keeps only the last event.
            XItemNodeBase *emitter;
        };
        Talker<ListChangeEvent> &onListChanged() {return m_tlkOnListChanged;}
        const Talker<ListChangeEvent> &onListChanged() const {return m_tlkOnListChanged;}
        //! Useful to rearrange item strings when items are about to be shown by UI (QComboBox).
        //! Does nothing unless the talker is listened.
        Talker<XItemNodeBase *> &onItemRefreshRequested() {return m_tlkOnRefreshRequested;}
        const Talker<XItemNodeBase *> &onItemRefreshRequested() const {return m_tlkOnRefreshRequested;}
    private:
        TalkerOnce<ListChangeEvent> m_tlkOnListChanged;
        TalkerOnce<XItemNodeBase *> m_tlkOnRefreshRequested;
    };
private:
    shared_ptr<Listener> m_lsnTryAutoSet;
    void onTryAutoSet(const Snapshot &shot, const Payload::ListChangeEvent &e);
};

DECLSPEC_KAME void
xpointeritemnode_throwConversionError_();

template <class TL>
class XPointerItemNode : public XItemNodeBase {
public:
    XPointerItemNode(const char *name, bool runtime, Transaction &tr_list,
        const shared_ptr<TL> &list, bool auto_set_any = false)
        :  XItemNodeBase(name, runtime, auto_set_any), m_list(list) {
        m_lsnOnItemReleased = tr_list[ *list].onRelease().connect( *this, &XPointerItemNode<TL>::onItemReleased);
        m_lsnOnListChanged = tr_list[ *list].onListChanged().connect( *this, &XPointerItemNode<TL>::lsnOnListChanged);
    }
    virtual ~XPointerItemNode() = default;

    struct Payload : public XItemNodeBase::Payload {
        operator shared_ptr<XNode>() const { return m_var.lock();}
        virtual XString to_str() const override {
            shared_ptr<XNode> node( *this);
            if(node)
                return node->getLabel();
            else
                return m_pendingLabel;
        }
        Payload &operator=(const shared_ptr<XNode> &t) {
            m_var = t;
            m_pendingLabel = {};
            tr().mark(onValueChanged(), static_cast<XValueNodeBase*>( &node()));
            return *this;
        }
        XString pendingLabel() const { return m_pendingLabel; }
    protected:
        friend class XPointerItemNode;
        virtual void str_(const XString &var) override {
            m_pendingLabel = {};
            if(var.empty()) {
                *this = shared_ptr<XNode>();
                return;
            }
            if(auto list = static_cast<const XPointerItemNode&>(node()).m_list.lock()) {
                Snapshot shot( *list);
                if(shot.size()) {
                    for(auto it = shot.list()->begin(); it != shot.list()->end(); ++it) {
                        if(( *it)->getLabel() == var) {
                            *this = *it;
                            return;
                        }
                    }
                }
            }
            // Item not found yet — store label and defer resolution to lsnOnListChanged
            m_pendingLabel = var;
        }
        weak_ptr<XNode> m_var;
        XString m_pendingLabel;
    };
private:
    void onItemReleased(const Snapshot& /*shot*/, const XListNodeBase::Payload::ReleaseEvent &e) {
        for(Snapshot shot( *this);;) {
            if(e.released != (shared_ptr<XNode>)shot[ *this])
                break;
            Transaction tr(shot);
            tr[ *this] = shared_ptr<XNode>();
            if(tr.commit()) break;
        }
    }
    void lsnOnListChanged(const Snapshot& /*shot*/, XListNodeBase* node) {
        if(auto list = m_list.lock()) {
            assert(node == list.get());
            // Use a fresh list snapshot for both pending-label resolution and
            // re-broadcast: the triggering shot may not contain entries created
            // by nested immediate listeners (e.g. proxy XScalarEntry from
            // XCalibratedEntry::onSelectionChanged).
            Snapshot fresh_shot( *list);
            // Retry deferred label resolution if pending
            for(bool committed = true;;) {
                Snapshot shot_self( *this);
                XString pending = shot_self[ *this].pendingLabel();
                if(pending.empty() || !fresh_shot.size(list))
                    break;
                for(auto it = fresh_shot.list(list)->begin(); it != fresh_shot.list(list)->end(); ++it) {
                    if(( *it)->getLabel() == pending) {
                        Transaction tr(shot_self);
                        tr[ *this] = *it;
                        committed = tr.commit();
                        break;
                    }
                }
                if(committed)
                    break;
            }
            Snapshot( *this).talk(( **this)->onListChanged(),
                XItemNodeBase::Payload::ListChangeEvent({fresh_shot, this}));
        }
    }
    shared_ptr<Listener> m_lsnOnItemReleased, m_lsnOnListChanged;
protected:
    weak_ptr<TL> m_list;
};

template <class TL, class... VT>
class XItemNode;

template <class TL>
class XItemNode<TL> : public XPointerItemNode<TL> {
public:
    struct Payload : public XPointerItemNode<TL>::Payload {};
protected:
    XItemNode(const char *name, bool runtime, Transaction &tr_list,
        const shared_ptr<TL> &list, bool auto_set_any = false)
        :  XPointerItemNode<TL>(name, runtime, tr_list, list, auto_set_any) {
    }
    virtual ~XItemNode() = default;
    virtual std::vector<XItemNodeBase::Item> itemStrings(const Snapshot &) const override {
        Snapshot shot_self( *this);
        XString pending = shot_self[ *this].pendingLabel();
        if(pending.length() && !(shared_ptr<XNode>)shot_self[ *this])
            return {{pending, "(" + pending + ")"}};
        return {};
    }
};

//! A pointer to a XListNode TL, T1 (and VT) are value types
//! template <class TL, class... VT>
template <class TL, class T1, class... VT>
class XItemNode<TL, T1, VT...> : public XItemNode<TL, VT...> {
public:
    XItemNode(const char *name, bool runtime, Transaction &tr_list,
        const shared_ptr<TL> &list, bool auto_set_any = false)
        :  XItemNode<TL, VT...>(name, runtime, tr_list, list, auto_set_any) {
    }
    virtual ~XItemNode() = default;

    struct Payload : public XItemNode<TL, VT...>::Payload {
        Payload() : XItemNode<TL, VT...>::Payload() {}
        operator shared_ptr<T1>() const {
            return dynamic_pointer_cast<T1>(shared_ptr<XNode>( *this));
        }
        Payload &operator=(const shared_ptr<XNode> &t) {
            XPointerItemNode<TL>::Payload::operator=(t);
            return *this;
        }
    };

    virtual std::vector<XItemNodeBase::Item> itemStrings(const Snapshot &shot) const override {
        std::vector<XItemNodeBase::Item> items;
        if(auto list = this->m_list.lock()) {
            if(shot.size(list)) {
                for(auto it = shot.list(list)->begin(); it != shot.list(list)->end(); ++it) {
                    if(dynamic_pointer_cast<T1>( *it)) {
                        items.push_back({( *it)->getName(), ( *it)->getLabel()});
                    }
                }
            }
        }
        auto tail = this->XItemNode<TL, VT...>::itemStrings(shot);
        items.insert(items.end(), tail.begin(), tail.end());
        return items;
    }
};

//! Contains strings, value is one of strings
class DECLSPEC_KAME XComboNode : public XItemNodeBase {
public:
    explicit XComboNode(const char *name, bool runtime = false, bool auto_set_any = false);
    virtual ~XComboNode() = default;

    virtual std::vector<XItemNodeBase::Item> itemStrings(const Snapshot &shot) const override {
        return shot[ *this].itemStrings();
    }

    struct DECLSPEC_KAME Payload : public XItemNodeBase::Payload {
        Payload() : XItemNodeBase::Payload(), m_strings(std::make_shared<std::deque<XString>>()),
            m_var(std::pair<XString, int>("", -1)) {}
        void add(const XString &str);
        void add(std::initializer_list<XString> strlist) {for(auto &&x: strlist){ add(x);}}
        void add(const std::vector<XItemNodeBase::Item> &items);
        void clear();
        operator int() const { return m_var.second;}
        virtual XString to_str() const override { return m_var.first;}
        Payload &operator=(int t);
        Payload &operator=(const XString &);
        virtual std::vector<XItemNodeBase::Item> itemStrings() const;

    protected:
        virtual void str_(const XString &) override;
    private:
        shared_ptr<std::deque<XString> > m_strings;
        std::pair<XString, int> m_var;
    };
};

#endif /*XITEMNODE_H_*/
