//  node graph
//  Copyright (C) 2008, 2009, 2010 Tim Blechmann
//
//  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.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; see the file COPYING.  If not, write to
//  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
//  Boston, MA 02111-1307, USA.

#pragma once

#include <memory>
#include <tuple>

#include "dsp_thread_queue_node.hpp"
#include "group.hpp"
#include "node_types.hpp"

#include "dsp_thread_queue/dsp_thread_queue.hpp"

namespace nova {

class node_graph
{
private:
    group root_group_ = {0};
    friend class dependency_graph_generator;

    static const std::size_t node_set_bucket_count = 4096;

public:
    typedef thread_queue_item dsp_thread_queue_item;
    typedef thread_queue dsp_thread_queue;

    /** \brief ctor
     *
     * - initialize root node */
    node_graph(void):
        node_set(node_set_type::bucket_traits(node_buckets, node_set_bucket_count))
    {
        node_set.insert(root_group_);
        root_group_.add_ref();
    }

    ~node_graph()
    {
        assert(root_group_.child_count() == 0);
    }

    uint32_t synth_count(void) const
    {
        return synth_count_;
    }

    uint32_t group_count(void) const
    {
        return group_count_;
    }

    template <typename Functor>
    void add_node(server_node * node, node_position_constraint const & constraint, Functor const & doOnFree)
    {
        server_node * reference_node = constraint.first;
        node_position position = constraint.second;

        std::pair< node_set_type::iterator, bool > inserted = node_set.insert(*node);
        node->add_ref();

        assert(inserted.second == true); /* node id already present (should be checked earlier)! */

        switch (position) {
        case before:
        case after: {
            abstract_group * parent = reference_node->parent_;
            parent->add_child(node, constraint);
            break;
        }

        case head:
        case tail: {
            abstract_group * group = static_cast<abstract_group*>(reference_node);
            group->add_child(node, position);
            break;
        }

        case insert: {
            abstract_group * group = static_cast<abstract_group*>(reference_node);
            group->add_child(node);
            break;
        }

        case replace: {
            const bool reference_node_is_group = reference_node->is_group();
            if (reference_node_is_group) {
                abstract_group * reference_group = static_cast<abstract_group*>(reference_node);
                reference_group->apply_deep_on_children( [&] (server_node & node) {
                    doOnFree(node);
                });
            }
            doOnFree(*reference_node);

            abstract_group * node_parent = reference_node->parent_;
            node_parent->replace_child(node, reference_node);

            if (reference_node_is_group)
                group_count_ -= 1;
            else
                synth_count_ -= 1;

            break;
        }

        default:
            assert(false);      /* this point should not be reached! */
        }

        if (node->is_synth())
            synth_count_ += 1;
        else
            group_count_ += 1;
    }

    void add_node(server_node * node, node_position_constraint const & constraint);
    void add_node(server_node * s);

    void remove_node(server_node * n)
    {
        remove_node(n, [](server_node &){});
    }

    template <typename Functor>
    void remove_node(server_node * node, Functor const & doOnFree)
    {
        if (!node->is_synth())
            group_free_all(static_cast<abstract_group*>(node), doOnFree);

        /** \todo recursively remove nodes from node_set
         *        for now this is done by the auto-unlink hook
         * */

        doOnFree(*node);
        abstract_group * parent = node->parent_;
        parent->remove_child(node);
        if (node->is_synth())
            synth_count_ -= 1;
        else
            group_count_ -= 1;

        release_node_id(node);
    }

    void dump(std::string const & filename);

    group * root_group(void)
    {
        return &root_group_;
    }

    typedef std::unique_ptr<dsp_thread_queue> dsp_thread_queue_ptr;

    dsp_thread_queue_ptr generate_dsp_queue(void);

    server_node * find_node(int32_t node_id)
    {
        node_set_type::iterator it = node_set.find(node_id, hash_node(), equal_node());
        if (it == node_set.end())
            return nullptr;
        return &(*it);
    }

    bool node_id_available(int32_t node_id)
    {
        node_set_type::iterator it = node_set.find(node_id, hash_node(), equal_node());
        return (it == node_set.end());
    }

    void synth_reassign_id(int32_t node_id);

    /** generate new hidden (negative) node id  */
    int32_t generate_node_id(void);

private:
    int32_t generated_id = -2;

public:
    abstract_group * find_group(int32_t node_id)
    {
        server_node * node = find_node(node_id);
        if (!node || node->is_synth())
            return nullptr;
        else
            return static_cast<abstract_group*>(node);
    }

    abstract_synth * find_synth(int32_t node_id)
    {
        server_node * node = find_node(node_id);
        if (node && node->is_synth())
            return static_cast<abstract_synth*>(node);
        else
            return nullptr;
    }

    void group_free_all(abstract_group * group)
    {
        group_free_all(group, [](server_node &){});
    }

    void group_free_deep(abstract_group * group)
    {
        group_free_deep(group, [](server_node &){});
    }

    template <typename Functor>
    void group_free_all(abstract_group * group, Functor const & doOnFree)
    {
        size_t synths = 0, groups = 0;
        group->apply_deep_on_children([&](server_node & node) {
            release_node_id(&node);
            if (node.is_synth())
                synths += 1;
            else
                groups += 1;
            doOnFree(node);
        });

        group->free_children();
        synth_count_ -= synths;
        group_count_ -= groups;
    }

    template <typename Functor>
    void group_free_deep(abstract_group * group, Functor const & doOnFree)
    {
        size_t synths = 0;
        group->apply_deep_on_children([&](server_node & node) {
             if (node.is_synth()) {
                 release_node_id(&node);
                 synths += 1;
             }
             doOnFree(node);
        });

        group->free_synths_deep();
        synth_count_ -= synths;
    }

    void release_node_id(server_node * node)
    {
        node_set.erase(*node);
        node->release();
    }

private:
    struct equal_node
    {
        bool operator()(server_node const & lhs,
                        int32_t const & rhs) const
        {
            return lhs.node_id == rhs;
        }

        bool operator()(int32_t const &lhs,
                        server_node const & rhs) const
        {
            return lhs == rhs.node_id;
        }
    };

    struct hash_node
    {
        std::size_t operator()(int32_t value)
        {
            return server_node::hash(value);
        }
    };

    typedef boost::intrusive::unordered_set< server_node,
                                             boost::intrusive::constant_time_size<false>,
                                             boost::intrusive::power_2_buckets<true>
                                           > node_set_type;

    uint32_t synth_count_ = 0, group_count_ = 1;

    node_set_type::bucket_type node_buckets[node_set_bucket_count];
    node_set_type node_set;
};

} /* namespace nova */
