// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#pragma once

#include "iterators.h"
#include "macros.h"
#include "type_traits.h"
#include "string.h"
#include "path.h"

#include <cereal/cereal.hpp>

#include <algorithm>
#include <assert.h>
#include <cmath>
#include <gsl/gsl>
#include <iterator>
#include <map>
#include <sstream>
#include <string>
#include <vector>

namespace mira
{
    struct property_bag;

    struct property_interface
    {
        property_interface(const char* type, const char* name)
            : m_type{ type }
            , m_name{ name }
        {}

        virtual ~property_interface()
        {}

        const char* name() const
        {
            return m_name;
        }

        const char* type() const
        {
            return m_type;
        }

    private:
        const char* const m_type;
        const char* const m_name;
    };

    struct simple_property : public property_interface
    {
        virtual void from_string(const std::string& str) = 0;
        virtual void from_stream(std::istream& str) = 0;

        virtual void from_other(const property_interface& other) = 0;

        virtual std::type_index serializable_type() const = 0;

        virtual std::string to_string() const = 0;

        simple_property(const char* type, const char* name)
            : property_interface(type, name)
        {}
    };

    template<typename SerializableType>
    struct serializable_property : public simple_property
    {
        serializable_property(const char* type, const char* name)
            : simple_property(type, name)
        {}

        virtual ~serializable_property() {}

        std::type_index serializable_type() const override
        {
            return { typeid(SerializableType) };
        }

        // serialization functions
        template<class Archive>
        SerializableType save_minimal(Archive const&) const
        {
            return get_serializable();
        }

        template<class Archive>
        void load_minimal(Archive const&, SerializableType const& val)
        {
            set_serializable(val);
        }

    protected:
        virtual SerializableType get_serializable() const = 0;
        virtual void set_serializable(const SerializableType& value) = 0;
    };

    template<typename T> struct property;

    struct property_bag : public property_interface
    {
        bool set(const std::string& name, const std::string& value)
        {
            auto itr = m_properties.find(name.c_str());
            if (itr == m_properties.end())
                return false;

            itr->second->from_string(value);

            return true;
        }

        void from_other(const property_bag& other)
        {
            assert(strcmp(name(), other.name()) == 0);
            assert(other.m_properties.size() == m_properties.size());
            assert(other.m_groups.size() == m_groups.size());

            for (auto prop : m_properties)
            {
                prop.second->from_other(*other.m_properties.at(prop.first));
            }

            for (size_t i = 0; i < m_groups.size(); ++i)
            {
                assert(strcmp(m_groups[i]->name(), other.m_groups[i]->name()) == 0);
                m_groups[i]->from_other(*other.m_groups[i]);
            }
        }

        std::string get(const std::string& name) const
        {
            return m_properties.find(name.c_str())->second->to_string();
        }

        std::vector<simple_property*> properties()
        {
            std::vector<simple_property*> props;
            std::transform(m_properties.begin(), m_properties.end(), std::back_inserter(props), [](const auto& entry) {
                return entry.second;
            });
            return props;
        }

        std::vector<const simple_property*> properties() const
        {
            std::vector<const simple_property*> props;
            std::transform(m_properties.begin(), m_properties.end(), std::back_inserter(props), [](const auto& entry) {
                return entry.second;
            });
            return props;
        }

        gsl::span<property_bag*> groups()
        {
            return m_groups;
        }

        gsl::span<const property_bag*> groups() const
        {
            return { const_cast<property_bag const**>(m_groups.data()), (gsl::span<const property_bag*>::size_type)m_groups.size() };
        }

        // serialization function
        template<typename ArchiveT>
        void serialize(ArchiveT& ar)
        {
            using arch_func = void(ArchiveT & ar, property_interface * prop);

            static std::unordered_map<std::type_index, arch_func*> converters{
                { typeid(bool), &archive_prop<ArchiveT, serializable_property<bool>> },
                { typeid(int), &archive_prop<ArchiveT, serializable_property<int>> },
                { typeid(unsigned int), &archive_prop<ArchiveT, serializable_property<unsigned int>> },
                { typeid(uint32_t), &archive_prop<ArchiveT, serializable_property<uint32_t>> },
                { typeid(uint64_t), &archive_prop<ArchiveT, serializable_property<uint64_t>> },
                { typeid(size_t), &archive_prop<ArchiveT, serializable_property<size_t>> },
                { typeid(float), &archive_prop<ArchiveT, serializable_property<float>> },
                { typeid(std::string), &archive_prop<ArchiveT, serializable_property<std::string>> },
                { typeid(double), &archive_prop<ArchiveT, serializable_property<double>> },
            };

            for (auto prop : m_properties)
            {
                auto found = converters.find(prop.second->serializable_type());
                if (found == converters.end())
                    throw std::invalid_argument("unable to convert type, conversion function not found");

                found->second(ar, prop.second);
            }

            for (auto group : m_groups)
            {
                ar(cereal::make_nvp(group->name(), *group));
            }
        }

    protected:
        property_bag(const char* name)
            : property_bag{ name, nullptr }
        {}

        property_bag(const char* name, property_bag* owner)
            : property_interface{ "bag", name }
        {
            if (owner != nullptr)
            {
                owner->add(this);
            }
        }

        property_bag(const property_bag& other) = delete;
        property_bag& operator=(const property_bag& other) = delete;

    private:
        template<typename ArchiveT, typename T>
        static void archive_prop(ArchiveT& ar, property_interface* prop)
        {
            ar(cereal::make_nvp(prop->name(), *static_cast<T*>(prop)));
        }

        template<typename T>
        friend struct property;

        void add(simple_property* prop)
        {
            m_properties.insert({ prop->name(), prop });
        }

        void add(property_bag* prop)
        {
            m_groups.push_back(prop);
        }

        std::map<const char*, simple_property*, string_compare> m_properties;

        std::vector<property_bag*> m_groups;
    };

    template<typename T, bool isEnum = std::is_enum<T>::value>
    struct property_serialization_traits;

    template<typename T>
    struct property_serialization_traits<T, true>
    {
        using serialization_type = std::underlying_type_t<T>;

        static serialization_type to_serializable(const T& value)
        {
            return static_cast<serialization_type>(value);
        }

        static T from_serializable(const serialization_type& value)
        {
            return static_cast<T>(value);
        }

        static void from_stream(std::istream& stream, T& value)
        {
            serialization_type underlyingValue{};

            stream >> underlyingValue;

            assert(!stream.fail() && "Property bag failed to convert string to value");

            value = static_cast<T>(underlyingValue);
        }

        static void to_stream(std::ostream& stream, const T& value)
        {
            auto underlying = static_cast<serialization_type>(value);

            std::stringstream ss;
            stream.precision(std::numeric_limits<serialization_type>::max_digits10);
            stream << underlying;

            assert(!stream.fail() && "Property bag failed to convert value to string");
        }
    };

    template<typename T>
    struct property_serialization_traits<T, false>
    {
        using serialization_type = T;

        static serialization_type to_serializable(const T& value)
        {
            return value;
        }

        static T from_serializable(const serialization_type& value)
        {
            return value;
        }

        static void from_stream(std::istream& stream, T& value)
        {
            if (std::is_same<T, bool>())
            {
                stream >> std::boolalpha >> value;
            }
            else
            {
                stream >> value;
            }

            assert((is_string<T>() || !stream.fail()) && "Property bag failed to convert string to value");
        }

        static void to_stream(std::ostream& stream, const T& value)
        {
            if (std::is_same<T, bool>())
            {
                stream << std::boolalpha << value;
            }
            else
            {
                stream.precision(std::numeric_limits<T>::max_digits10);
                stream << value;
            }
            assert(!stream.fail() && "Property bag failed to convert value to string");
        }
    };

    template<>
    struct property_serialization_traits<path, false>
    {
        using serialization_type = std::string;

        static serialization_type to_serializable(const path& value)
        {
            return value.string();
        }

        static path from_serializable(const serialization_type& value)
        {
            return { value };
        }

        static void from_stream(std::istream& stream, path& value)
        {
            stream >> value;
        }

        static void to_stream(std::ostream& stream, const path& value)
        {
            stream << value;
        }
    };

    template<typename T>
    struct property : public serializable_property<typename property_serialization_traits<T>::serialization_type>
    {
        using traits = property_serialization_traits<T>;

        using type = std::decay_t<T>;

        property(property_bag* lookup, const char* name, const char* type, const T& defaultValue)
            : serializable_property<typename traits::serialization_type>{ type, name }
            , value{ defaultValue }
        {
            lookup->add(this);
        }

        property& operator=(const property& other)
        {
            value = other.value;
            return *this;
        }

        virtual void from_string(const std::string& str) override
        {
            std::stringstream ss{ str };
            from_stream(ss);
        }

        virtual void from_stream(std::istream& stream) override
        {
            traits::from_stream(stream, value);
        }

        virtual void from_other(const property_interface& other) override
        {
            assert(dynamic_cast<const property<T>*>(&other) && "invalid type");
            value = static_cast<const property<T>&>(other).value;
        }

        virtual std::string to_string() const override
        {
            std::stringstream ss;
            traits::to_stream(ss, value);
            return ss.str();
        }

        operator T() const
        {
            return value;
        }

        property& operator=(const T& val)
        {
            value = val;
            return *this;
        }

        T value;

    protected:
        typename traits::serialization_type get_serializable() const override
        {
            return traits::to_serializable(value);
        }

        void set_serializable(const typename traits::serialization_type& val) override
        {
            value = traits::from_serializable(val);
        }
    };

    template<typename T>
    std::ostream& operator<<(std::ostream& os, const property<T>& prop)
    {
        return os << prop.value;
    }
}

#define PROPERTY(TYPE, NAME, DEFAULT) ::mira::property<TYPE> NAME{ this, #NAME, #TYPE, DEFAULT };

#define WEIRDLY_NAMED_PROPERTY(TYPE, STRNAME, DEFAULT)                                                                 \
    ::mira::property<TYPE> CONCATENATE_MACRO(setting_, __COUNTER__){ this, STRNAME, #TYPE, DEFAULT };

#define BAG_PROPERTY(TYPE) TYPE TYPE{ this };

#define NAMED_BAG_PROPERTY(TYPE, NAME) TYPE NAME{ this, #NAME };

#define PROPERTYBAG(NAME, ...)                                                                                \
    struct NAME : public ::mira::property_bag                                                                          \
    {                                                                                \
        NAME(const NAME& other)                                                                                \
            : NAME{ other.name() }                                                                                \
        {                                                                                \
            from_other(other);                                                                                \
        }                                                                                \
        NAME& operator=(const NAME& other)                                                                             \
        {                                                                                \
            from_other(other);                                                                                \
            return *this;                                                                                \
        }                                                                                \
        NAME()                                                                                \
            : ::mira::property_bag{ #NAME }                                                                            \
        {}                                                                                \
        NAME(::mira::property_bag* owner)                                                                              \
            : ::mira::property_bag{ #NAME, owner }                                                                     \
        {}                                                                                \
        NAME(const char* name)                                                                                \
            : ::mira::property_bag{ name }                                                                             \
        {}                                                                                \
        NAME(::mira::property_bag* owner, const char* name)                                                            \
            : ::mira::property_bag{ name, owner }                                                                      \
        {}                                                                                \
        __VA_ARGS__                                                                                \
    }


/*
   Copyright [2017-2021] [IBM Corporation]
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
       http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

#ifndef MCAS_HSTORE_HEAP_MC_EPHEMERAL_H
#define MCAS_HSTORE_HEAP_MC_EPHEMERAL_H

#include "heap_mm_ephemeral.h"

#include "hstore_config.h"
#include "histogram_log2.h"
#include "hop_hash_log.h"
#include <mm_plugin_itf.h>
#include "persistent.h"

#include <ccpm/cca.h> /* ctor_args */
#include <ccpm/interfaces.h> /* ownership_callback, (IHeap_expandable, region_vector_t) */
#include <common/byte_span.h>
#include <common/string_view.h>
#include <nupm/region_descriptor.h>
#include <gsl/span>

#include <algorithm> /* min, swap */
#include <cstddef> /* size_t */
#include <functional> /* function */
#include <memory> /* unique_ptr */
#include <vector>

namespace impl
{
	struct allocation_state_pin;
	struct allocation_state_emplace;
	struct allocation_state_extend;
}

struct heap_mc_ephemeral
  : public heap_mm_ephemeral
{
private:
	using byte_span = common::byte_span;
	using string_view = common::string_view;
	std::unique_ptr<
		ccpm::IHeap_expandable
	> _heap;
	impl::allocation_state_emplace *_ase;
	impl::allocation_state_pin *_aspd;
	impl::allocation_state_pin *_aspk;
	impl::allocation_state_extend *_asx;

	static constexpr unsigned log_min_alignment = 3U; /* log (sizeof(void *)) */
	static_assert(sizeof(void *) == 1U << log_min_alignment, "log_min_alignment does not match sizeof(void *)");
	/* Rca_LB seems not to allocate at or above about 2GiB. Limit reporting to 16 GiB. */
	static constexpr unsigned hist_report_upper_bound = 34U;
	explicit heap_mc_ephemeral(
		unsigned debug_level_
		, bool restore_not_clear
		, impl::allocation_state_emplace *ase
		, impl::allocation_state_pin *aspd
		, impl::allocation_state_pin *aspk
		, impl::allocation_state_extend *asx
		, std::unique_ptr<ccpm::IHeap_expandable> p
		, string_view id
		, string_view backing_file
		, const std::vector<byte_span> rv_full
		, const byte_span pool0_heap
	);

public:
	friend struct heap_mc;
	friend struct heap_mm;

	using common::log_source::debug_level;

	/* heap_mm version */
	explicit heap_mc_ephemeral(
		unsigned debug_level
		, bool restore_not_clear
		, MM_plugin_wrapper &&pw
		, impl::allocation_state_emplace *ase
		, impl::allocation_state_pin *aspd
		, impl::allocation_state_pin *aspk
		, impl::allocation_state_extend *asx
		, string_view id
		, string_view backing_file
		, const std::vector<byte_span> rv_full
		, byte_span pool0_heap_
	);

	std::size_t allocated() const override; // { return _allocated; }
	std::size_t capacity() const override; // { return _capacity; }
	void allocate(persistent_t<void *> &p, std::size_t sz, std::size_t alignment) override;
	std::size_t free(persistent_t<void *> &p_, std::size_t sz_) override;
	void free_tracked(const void *p, std::size_t sz) override;
	heap_mc_ephemeral(const heap_mc_ephemeral &) = delete;
	heap_mc_ephemeral& operator=(const heap_mc_ephemeral &) = delete;
	void add_managed_region_to_heap(byte_span r_heap) override;
	void reconstitute_managed_region_to_heap(byte_span r_heap, ccpm::ownership_callback_t f) override;
	bool is_crash_consistent() const override;
	bool can_reconstitute() const override;
};

#endif


#pragma once

#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <tuple>
#include <utility>
#include <vector>

#include <gsl/gsl>
#include <nonstd/optional.hpp>

#include "chainerx/array_body.h"
#include "chainerx/array_fwd.h"
#include "chainerx/device.h"
#include "chainerx/dtype.h"
#include "chainerx/graph.h"
#include "chainerx/macro.h"
#include "chainerx/shape.h"

namespace chainerx {

class BackwardContext;
class Device;

using BackwardFunction = std::function<void(BackwardContext&)>;

namespace internal {

class ArrayNode;
class OpNode;

struct ArrayProps {
    explicit ArrayProps(const Array& array);
    explicit ArrayProps(const ArrayNode& array_node);
    explicit ArrayProps(const ArrayBody& array_body);

    Shape shape;
    Dtype dtype;
    Device& device;
};

class OpNodeBackwardEntry {
public:
    OpNodeBackwardEntry(OpNode& op_node, std::vector<size_t> input_array_node_indices, BackwardFunction backward_func);

    OpNode& op_node() const { return op_node_; }

    size_t input_array_node_count() const { return input_array_node_indices_.size(); }

    const std::vector<size_t>& input_array_node_indices() const { return input_array_node_indices_; }

    const BackwardFunction& backward_func() const { return backward_func_; }

private:
    friend class OpNode;

    OpNode& op_node_;

    // The index mapping from local (this backward function) to global (op node).
    // Can be unset if the input array does not require grad.
    std::vector<size_t> input_array_node_indices_;

    BackwardFunction backward_func_;
};

// Creates an output array node at the specified index and adds edges between the output array node and the op node.
// Undefined behavior if the output array node already exists.
// This function is used by BackwardContext::GetRetainedOutput().
std::shared_ptr<ArrayNode> FabricateOutputArrayNode(std::shared_ptr<OpNode> op_node, size_t output_array_node_index);

class OpNode {
public:
    // Creates a new op node that has output array nodes corresponding to the given outputs.
    static std::shared_ptr<OpNode> CreateWithOutputArrayNodes(
            std::string name, BackpropId backprop_id, size_t input_count, const std::vector<ConstArrayRef>& outputs);

    OpNode(const OpNode&) = delete;
    OpNode(OpNode&&) = delete;
    OpNode& operator=(const OpNode&) = delete;
    OpNode& operator=(OpNode&&) = delete;

    OpNodeBackwardEntry& RegisterBackwardFunction(
            std::vector<std::tuple<size_t, std::shared_ptr<ArrayNode>>> input_array_nodes, BackwardFunction backward_func);

    // Adds links to input array nodes of other graphs.
    // The size of the vector must be equal to the number of inputs.
    void AddEdgesToInputArrayNodesOfOuterGraph(
            const BackpropId& outer_backprop_id, std::vector<std::shared_ptr<ArrayNode>> outer_graphs_input_array_nodes);

    // Adds links to output array nodes of other graphs.
    // The size of the vector must be equal to the number of outputs.
    void AddEdgesToOutputArrayNodesOfOuterGraph(
            const BackpropId& outer_backprop_id, std::vector<std::shared_ptr<ArrayNode>> outer_graphs_output_array_nodes);

    void Unchain() {
        backward_entries_.clear();
        std::fill(input_array_nodes_.begin(), input_array_nodes_.end(), std::shared_ptr<ArrayNode>{});
        AssertConsistency();
    }

    bool HasInputArrayNode(size_t input_index) const { return input_array_nodes_[input_index] != nullptr; }

    std::string name() const { return name_; }

    std::vector<std::shared_ptr<ArrayNode>>& input_array_nodes();

    const std::vector<std::shared_ptr<ArrayNode>>& input_array_nodes() const;

    gsl::span<OpNodeBackwardEntry> backward_entries() { return backward_entries_; }

    gsl::span<const OpNodeBackwardEntry> backward_entries() const { return backward_entries_; }

    size_t input_array_node_count() const { return input_array_nodes_.size(); }

    size_t output_array_node_count() const { return output_array_props_.size(); }

    int64_t rank() const { return rank_; }

    BackpropId backprop_id() const { return backprop_id_; }

    const ArrayProps& GetOutputArrayProps(size_t i) const {
        CHAINERX_ASSERT(i < output_array_props_.size());
        return output_array_props_[i];
    }

    // Returns the list of output array nodes on "this" graph.
    const std::vector<nonstd::optional<std::weak_ptr<ArrayNode>>>& output_array_nodes() const { return output_array_nodes_; }

    // Returns the list of output array nodes on "this" graph.
    std::vector<nonstd::optional<std::weak_ptr<ArrayNode>>>& output_array_nodes() { return output_array_nodes_; }

    // Returns the input array nodes of all graphs.
    const std::vector<std::tuple<BackpropId, std::vector<std::shared_ptr<ArrayNode>>>>& outer_graphs_input_array_nodes() const {
        return outer_graphs_input_array_nodes_;
    }

    // Returns the output array nodes of all graphs.
    const std::vector<std::tuple<BackpropId, std::vector<std::shared_ptr<ArrayNode>>>>& outer_graphs_output_array_nodes() const {
        return outer_graphs_output_array_nodes_;
    }

private:
    OpNode(std::string name, BackpropId backprop_id, size_t input_array_node_count);

    void AssertConsistency() const;

    std::string name_;

    // Backprop ID.
    // Backprop ID is also held in the first entry of output_array_nodes_, but the reference to it may be invalidated, whereas this member
    // is stable during the lifetime of this OpNode instance.
    BackpropId backprop_id_;

    int64_t rank_{0};

    // List of input array nodes.
    std::vector<std::shared_ptr<ArrayNode>> input_array_nodes_;

    // List of output array nodes of this graph.
    std::vector<nonstd::optional<std::weak_ptr<ArrayNode>>> output_array_nodes_;

    // List of input/output array nodes of outer graphs.
    // Outer graphs refer to graphs with lower ordinals.
    // Each entry is a pair of backprop ID and list of input/output array nodes.
    std::vector<std::tuple<BackpropId, std::vector<std::shared_ptr<ArrayNode>>>> outer_graphs_input_array_nodes_;
    std::vector<std::tuple<BackpropId, std::vector<std::shared_ptr<ArrayNode>>>> outer_graphs_output_array_nodes_;

    // Array props of output array nodes. This is used for creating dummy gradients.
    std::vector<ArrayProps> output_array_props_;

    std::vector<OpNodeBackwardEntry> backward_entries_;
};

}  // namespace internal
}  // namespace chainerx


/* block/gsl_block_uint.h
 *
 * Copyright (C) 1996, 1997, 1998, 1999, 2000, 2007 Gerard Jungman, Brian Gough
 *
 * 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 3 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; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#ifndef __GSL_BLOCK_UINT_H__
#define __GSL_BLOCK_UINT_H__

#if !defined( GSL_FUN )
#  if !defined( GSL_DLL )
#    define GSL_FUN extern
#  elif defined( BUILD_GSL_DLL )
#    define GSL_FUN extern __declspec(dllexport)
#  else
#    define GSL_FUN extern __declspec(dllimport)
#  endif
#endif

#include <stdlib.h>
#include <gsl/gsl_errno.h>

#undef __BEGIN_DECLS
#undef __END_DECLS
#ifdef __cplusplus
# define __BEGIN_DECLS extern "C" {
# define __END_DECLS }
#else
# define __BEGIN_DECLS /* empty */
# define __END_DECLS /* empty */
#endif

__BEGIN_DECLS

struct gsl_block_uint_struct
{
  size_t size;
  unsigned int *data;
};

typedef struct gsl_block_uint_struct gsl_block_uint;

GSL_FUN gsl_block_uint *gsl_block_uint_alloc (const size_t n);
GSL_FUN gsl_block_uint *gsl_block_uint_calloc (const size_t n);
GSL_FUN void gsl_block_uint_free (gsl_block_uint * b);

GSL_FUN int gsl_block_uint_fread (FILE * stream, gsl_block_uint * b);
GSL_FUN int gsl_block_uint_fwrite (FILE * stream, const gsl_block_uint * b);
GSL_FUN int gsl_block_uint_fscanf (FILE * stream, gsl_block_uint * b);
GSL_FUN int gsl_block_uint_fprintf (FILE * stream, const gsl_block_uint * b, const char *format);

GSL_FUN int gsl_block_uint_raw_fread (FILE * stream, unsigned int * b, const size_t n, const size_t stride);
GSL_FUN int gsl_block_uint_raw_fwrite (FILE * stream, const unsigned int * b, const size_t n, const size_t stride);
GSL_FUN int gsl_block_uint_raw_fscanf (FILE * stream, unsigned int * b, const size_t n, const size_t stride);
GSL_FUN int gsl_block_uint_raw_fprintf (FILE * stream, const unsigned int * b, const size_t n, const size_t stride, const char *format);

GSL_FUN size_t gsl_block_uint_size (const gsl_block_uint * b);
GSL_FUN unsigned int * gsl_block_uint_data (const gsl_block_uint * b);

__END_DECLS

#endif /* __GSL_BLOCK_UINT_H__ */


#pragma once

#define NOMINMAX

#ifndef _WIN32
#include <wsl/winadapter.h>
#include "directml_guids.h"
#endif

#include <unordered_map>
#include <vector>
#include <iostream>
#include <filesystem>
#include <variant>
#include <fstream>
#include <deque>
#include <optional>
#include <set>
#include <string_view>
#include <wrl/client.h>
#include <wil/result.h>
#include <gsl/gsl>
#include <DirectML.h>
#include "DirectMLX.h"
#include <rapidjson/document.h>
#include <rapidjson/istreamwrapper.h>
#include <fmt/format.h>
#include <half.hpp>

template<class... Ts> struct overload : Ts... { using Ts::operator()...; };
template<class... Ts> overload(Ts...) -> overload<Ts...>;


#pragma once

#include <nextalign/nextalign.h>

#include <gsl/string_span>
#include <string>

#include "../nextalign_private.h"
#include "../utils/to_underlying.h"

using AminoacidSequenceSpan = SequenceSpan<Aminoacid>;

Aminoacid charToAa(char aa);

char aaToChar(Aminoacid aa);

inline std::ostream& operator<<(std::ostream& os, const Aminoacid& aminoacid) {
  os << std::string{to_underlying(aminoacid)};
  return os;
}


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
/* inet_aton -> per utilizzarla in gcc: __USE_MISC */
#define __USE_MISC
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include "buffer.h"
#include "list.h"
#include "router.h"
#include <gsl/gsl_matrix.h>

#define DIM_BUFFER 1000
#define POISON_ID -1

struct msg_t {
    int msg_id;
    int msg_time;
    float msg_x;
    float msg_z;
};

struct routing_table_t {
    int dimension;
    list list_table;
};

struct router_t {
    int router_id;
    struct routing_table_t routing_table;
    pthread_mutex_t router_mutex;
};

struct parametri_accepter_t {
    int accepter_agents_number;
    char *accepter_address;
    int accepter_port;
    list accepter_lista_buffer;
    int accepter_in_out;
};

struct parametri_dispatcher_t {
    buffer dispatcher_buffer_in;
    list dispatcher_lista_buffer_out;
};

struct parametri_receiver_t {
    int receiver_id;
    int receiver_socket;
    buffer receiver_buffer;
};

struct parametri_sender_t {
    int sender_id;
    int sender_socket;
    buffer sender_buffer;
};

typedef struct msg_t *msg;

typedef struct parametri_accepter_t *parametri_accepter;
typedef struct parametri_dispatcher_t *parametri_dispatcher;
typedef struct parametri_receiver_t *parametri_receiver;
typedef struct parametri_sender_t *parametri_sender;

static pthread_mutex_t router_class_mutex;
static int routerObjectsNumber = 0;

static int numero_receiver_vivi = 0;
static int numero_dispatcher_vivi = 0;
static int numero_sender_vivi = 0;

static void initClassRouter() {
    pthread_mutex_init(&(router_class_mutex), NULL);
}

static void cleanClassRouter() {
    pthread_mutex_destroy(&(router_class_mutex));

}

router allocRouter() {

    router router_M_;

    if (routerObjectsNumber == 0) initClassRouter();

    routerObjectsNumber++;

    router_M_ = (router) malloc(sizeof (struct router_t));

    pthread_mutex_init(&(router_M_->router_mutex), NULL);

    return router_M_;

}

void freeRouter(router _F_router) {

    pthread_mutex_destroy(&(_F_router->router_mutex));

    free(_F_router);

    _F_router = NULL;

    routerObjectsNumber--;

    if (routerObjectsNumber == 0) cleanClassRouter();
}

static int listenFrom(char * _ip_address_local, int _ip_port_local) {

    int socket_M_;

    struct sockaddr_in sockaddr_in_local_;

    memset(&sockaddr_in_local_, 0, sizeof (sockaddr_in_local_));
    sockaddr_in_local_.sin_family = AF_INET;
    sockaddr_in_local_.sin_port = htons(_ip_port_local);
    inet_aton(_ip_address_local, &(sockaddr_in_local_.sin_addr));

    if ((socket_M_ = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("listenFrom: socket() Error\n");
        exit(1);
    }

    if (bind(socket_M_, (struct sockaddr *) & sockaddr_in_local_, sizeof (struct sockaddr_in)) == -1) {
        perror("listenFrom: bind() Error\n");
        exit(1);
    }

    if (listen(socket_M_, 1) == -1) {
        perror("listenFrom: listen() Error\n");
        exit(1);
    }

    return socket_M_;

}

static void sendMsg(int _socket, msg _msg) {

    if (send(_socket, _msg, sizeof (struct msg_t), 0) == -1) {
        perror("sendMsg: send() Error\n");
        exit(1);
    };
    /*
        int net_id;
        int net_x;
        int net_z;

        net_id = htonl(_msg->msg_id);
        if (send(_socket, &net_id, sizeof (net_id), 0) == -1) {
            perror("sendMsg: send() Error\n");
            exit(1);
        };

        net_x = htonl(_msg->msg_x);
        if (send(_socket, &net_x, sizeof (net_x), 0) == -1) {
            perror("sendMsg: send() Error\n");
            exit(1);
        };

        net_z = htonl(_msg->msg_z);
        if (send(_socket, &net_z, sizeof (net_z), 0) == -1) {
            perror("sendMsg: send() Error\n");
            exit(1);
        };
     */
}

static msg recvMsg(int _socket) {

    msg _M_msg = (struct msg_t *) malloc(sizeof (struct msg_t));

    if (recv(_socket, _M_msg, sizeof (struct msg_t), 0) == -1) {
        perror("recvMsg: recv() Error\n");
        exit(1);
    }
    /*
        int net_id;
        int net_x;
        int net_z;

        msg _M_msg = (struct msg_t *) malloc(sizeof (struct msg_t));

        if (recv(_socket, &net_id, sizeof (net_id), 0) == -1) {
            perror("recvMsg: recv() Error\n");
            exit(1);
        }
        _M_msg->msg_id = ntohl(net_id);

        if (recv(_socket, &net_x, sizeof (net_x), 0) == -1) {
            perror("recvMsg: recv() Error\n");
            exit(1);
        }
        _M_msg->msg_x = ntohl(net_x);

        if (recv(_socket, &net_z, sizeof (net_z), 0) == -1) {
            perror("recvMsg: recv() Error\n");
            exit(1);
        }
        _M_msg->msg_z = ntohl(net_z);
     */
    return _M_msg;
}

static parametri_receiver allocParamReceiver(int _receiver_socket, buffer _receiver_buffer) {

    parametri_receiver _M_parametri_receiver = (parametri_receiver) malloc(sizeof (struct parametri_receiver_t));

    _M_parametri_receiver->receiver_socket = _receiver_socket;
    _M_parametri_receiver->receiver_buffer = _receiver_buffer;

    return _M_parametri_receiver;
}

/*
 * Quando il Receiver riceve dall'agent un messaggio con id = POISON_ID allora:
 * - lo inoltra nel buffer
 * - chiude la connessione con l'agent
 * - fa il fflush del file di log e lo chiude
 * - decrementa numero_receiver_vivi
 * - termina
 */

static void *runReceiver(void *_parametri_receiver) {

    parametri_receiver _parametri = (parametri_receiver) _parametri_receiver;

    int _receiver_socket = _parametri->receiver_socket;
    buffer _receiver_buffer = _parametri->receiver_buffer;

    msg _msg;
    int _receiver_id;

    FILE *receiver_log;

    char receiver_log_file_name[FILENAME_MAX];

    /*
     * Riceve il messaggio di presentazione dall'agent
     * - imposta l'id del receiver
     * - imposta l'id del _receiver_buffer
     * - reinvia all'agent il messaggio ricevuto
     */

    _msg = recvMsg(_receiver_socket);
    _receiver_id = _msg->msg_id;
    setBufferID(_receiver_buffer, _receiver_id);

    /*
     * Apre il file di log
     */

    sprintf(receiver_log_file_name, "receiver_%d.log", _receiver_id);

    receiver_log = fopen(receiver_log_file_name, "w");

    if (receiver_log == NULL) {
        perror("Errore apertura file: \"receiver.log\"\n");
        exit(1);
    };

    setbuf(receiver_log, NULL);

    fprintf(receiver_log, "R%d: Open log\n", _receiver_id);

    /*sleep(1);*/

    fprintf(receiver_log, "R%d: Parametri invocazione:\n", _receiver_id);

    fprintf(receiver_log, "R%d: Wait: rcv\n", _receiver_id);

    fprintf(receiver_log, "R%d: > (%d,%d,%f,%f)\n", _receiver_id, _msg->msg_id, _msg->msg_time, _msg->msg_x, _msg->msg_z);

    fprintf(receiver_log, "R%d: Wait: rcv\n", _receiver_id);

    /*
     * Riceve un messaggio e lo pone nel buffer finchè non riceve il POISON_ID
     */

    _msg = recvMsg(_receiver_socket);

    fprintf(receiver_log, "R%d: > (%d,%d,%f,%f)\n", _receiver_id, _msg->msg_id, _msg->msg_time, _msg->msg_x, _msg->msg_z);

    fprintf(receiver_log, "R%d: Loop:\n", _receiver_id);

    fprintf(receiver_log, "R%d: > while(msg != POISON_ID)\n", _receiver_id);

    fprintf(receiver_log, "R%d: --- Loop Start ---\n", _receiver_id);

    while (_msg->msg_id != POISON_ID) {

        putInBuffer_blocking(_receiver_buffer, _msg);

        fprintf(receiver_log, "R%d: >>> putInBuffer(%d,%d,%f,%f)\n", _receiver_id, _msg->msg_id, _msg->msg_time, _msg->msg_x, _msg->msg_z);

        fprintf(receiver_log, "R%d: >>> Wait: rcv\n", _receiver_id);

        _msg = recvMsg(_receiver_socket);
    };

    fprintf(receiver_log, "R%d: ---- Loop End ----\n", _receiver_id);

    fprintf(receiver_log, "R%d: Signal:\n", _receiver_id);

    putInBuffer_blocking(_receiver_buffer, _msg);

    fprintf(receiver_log, "R%d: > putInBuffer(%d,%d,%f,%f) \n", _receiver_id, _msg->msg_id, _msg->msg_time, _msg->msg_x, _msg->msg_z);

    fprintf(receiver_log, "R%d: Socket:\n", _receiver_id);

    /*
     * Chiude la connessione con l'agent
     */

    send(_receiver_socket, "", 1, 0);

    close(_receiver_socket);

    fprintf(receiver_log, "R%d: > close receiver socket\n", _receiver_id);

    fprintf(receiver_log, "R%d: Memoria\n", _receiver_id);

    free(_parametri_receiver);

    fprintf(receiver_log, "R%d: > parametri\n", _receiver_id);

    /*
     * fflush e chiusura del logfile
     */

    fprintf(receiver_log, "R%d: Close log\n", _receiver_id);

    fflush(receiver_log);

    fclose(receiver_log);

    /* Decrementa numero_receiver_vivi e termina*/

    numero_receiver_vivi--;

    return NULL;
}

static parametri_sender allocParamSender(int _sender_socket, buffer _sender_buffer) {

    parametri_sender _M_parametri_sender = (parametri_sender) malloc(sizeof (struct parametri_sender_t));

    _M_parametri_sender->sender_socket = _sender_socket;
    _M_parametri_sender->sender_buffer = _sender_buffer;

    return _M_parametri_sender;
}

/*
 * Quando il Sender prende dal buffer un messaggio con id = POISON_ID
 * vuol dire che quello è l'ultimo messaggio e il buffer non ne contiene altri:
 * - attende che i dispatcher siano morti
 * - elimina il sender buffer associato
 * - chiude la connessione con l'agent
 * - fa il fflush del file di log e lo chiude
 * - decrementa numero_sender_buffer
 * - termina
 */

static void *runSender(void *_parametri_sender) {

    parametri_sender _parametri = (parametri_sender) _parametri_sender;

    int _sender_socket = _parametri->sender_socket;
    buffer _sender_buffer = _parametri->sender_buffer;

    int _sender_id;

    msg _msg;

    FILE *sender_log;

    char sender_log_file_name[FILENAME_MAX];

    /*
     * Riceve il messaggio di presentazione dall'agent:
     * - imposta l'id del sender
     * - imposta l'id del _sender_buffer
     * - reinvia all'agent il messaggio ricevuto
     */

    _msg = recvMsg(_sender_socket);
    _sender_id = _msg->msg_id;
    setBufferID(_sender_buffer, _sender_id);

    /*
     * Apre il file di log
     */

    sprintf(sender_log_file_name, "sender_%d.log", _sender_id);

    sender_log = fopen(sender_log_file_name, "w");

    if (sender_log == NULL) {
        perror("Errore apertura file: \"sender.log\"\n");
        exit(1);
    };

    setbuf(sender_log, NULL);

    fprintf(sender_log, "S%d: Open log\n", _sender_id);

    /*sleep(1);*/

    fprintf(sender_log, "S%d: Parametri di invocazione:\n", _sender_id);

    fprintf(sender_log, "S%d: Wait: rcv\n", _sender_id);

    fprintf(sender_log, "S%d: > (%d,%d,%f,%f)\n", _sender_id, _msg->msg_id, _msg->msg_time, _msg->msg_x, _msg->msg_z);

    fprintf(sender_log, "S%d: Wait: getFromBuffer\n", _sender_id);

    /*
     * Riceve un messaggio e lo pone nel buffer finchè non riceve il POISON_ID
     */

    _msg = getFromBuffer_blocking(_sender_buffer);

    fprintf(sender_log, "S%d: > (%d,%d,%f,%f)\n", _sender_id, _msg->msg_id, _msg->msg_time, _msg->msg_x, _msg->msg_z);

    fprintf(sender_log, "S%d: Loop:\n", _sender_id);

    fprintf(sender_log, "S%d: > while(msg != POISON_ID)\n", _sender_id);

    fprintf(sender_log, "S%d: --- Loop Start ---\n", _sender_id);

    while (_msg->msg_id != POISON_ID) {

        sendMsg(_sender_socket, _msg);

        fprintf(sender_log, "S%d: >>> snd (%d,%d,%f,%f)\n", _sender_id, _msg->msg_id, _msg->msg_time, _msg->msg_x, _msg->msg_z);

        fprintf(sender_log, "S%d: >>> Wait: getFromBuffer\n", _sender_id);

        _msg = getFromBuffer_blocking(_sender_buffer);
    };

    fprintf(sender_log, "S%d: ---- Loop End ----\n", _sender_id);

    fprintf(sender_log, "S%d: Wait:\n", _sender_id);

    /*
     * Attende che tutti i dispatcher siano morti
     */

    fprintf(sender_log, "S%d: > while(dispatcher > 0)\n", _sender_id);

    while (numero_dispatcher_vivi > 0);

    fprintf(sender_log, "S%d: Socket:\n", _sender_id);

    /*
     * Chiude la connessione con l'agent
     */

    send(_sender_socket, "", 1, 0);

    close(_sender_socket);

    fprintf(sender_log, "S%d: > close sender socket\n", _sender_id);

    fprintf(sender_log, "S%d: Memoria:\n", _sender_id);

    /*
     * Elimina il receiver buffer associato
     */

    freeBuffer(_sender_buffer);

    fprintf(sender_log, "S%d: > free(sender_buffer)\n", _sender_id);

    free(_parametri_sender);

    fprintf(sender_log, "S%d: > parametri\n", _sender_id);

    /*
     * fflush e chiusura del logfile
     */

    fprintf(sender_log, "S%d: Close log\n", _sender_id);

    fflush(sender_log);

    fclose(sender_log);

    /* Decrementa numero_sender_vivi e termina*/

    numero_sender_vivi--;

    return NULL;
}

static parametri_accepter allocParamAccepter(int _accepter_agents_number, char *_accepter_address, int _accepter_port, list _accepter_lista_buffer, int _accepter_in_out) {

    parametri_accepter _M_parametri_accepter = (parametri_accepter) malloc(sizeof (struct parametri_accepter_t));

    _M_parametri_accepter->accepter_agents_number = _accepter_agents_number;
    _M_parametri_accepter->accepter_address = _accepter_address;
    _M_parametri_accepter->accepter_port = _accepter_port;
    _M_parametri_accepter->accepter_lista_buffer = _accepter_lista_buffer;
    _M_parametri_accepter->accepter_in_out = _accepter_in_out;

    return _M_parametri_accepter;

}

/*
 * Quando l'accepter riceve _accepter_agents_number connessioni in input e in output
 * vuol dire che tutti gli agents si sono collegati:
 * - chiude le due porte in listening
 * - fa il fflush del file di log e lo chiude
 * - termina
 */

static void *runAccepter(void *_parametri_accepter) {

    parametri_accepter _parametri = (parametri_accepter) _parametri_accepter;

    int _accepter_agents_number;
    char *_accepter_address;
    int _accepter_port;
    list _accepter_lista_buffer;
    int _accepter_in_out;

    int listening_socket;
    int communication_socket;

    FILE *accepter_log;

    char *accepter_log_file_name;
    char *in_out;
    char *accepter_error;

    /* Apre il file di log */

    _accepter_in_out = _parametri->accepter_in_out;

    if (_accepter_in_out == 0) {
        accepter_log_file_name = "accepter_in.log";
        accepter_error = "Errore apertura file: \"accepter_in.log\"\n";
        in_out = "AI";
    } else {
        accepter_log_file_name = "accepter_out.log";
        accepter_error = "Errore apertura file: \"accepter_out.log\"\n";
        in_out = "AO";
    };

    accepter_log = fopen(accepter_log_file_name, "w");

    if (accepter_log == NULL) {
        perror(accepter_error);
        exit(1);
    };

    setbuf(accepter_log, NULL);

    fprintf(accepter_log, "%s: Open log\n", in_out);

    fprintf(accepter_log, "%s: Parametri di invocazione:\n", in_out);

    _accepter_agents_number = _parametri->accepter_agents_number;

    fprintf(accepter_log, "%s: > %d\n", in_out, _accepter_agents_number);

    _accepter_address = _parametri->accepter_address;

    fprintf(accepter_log, "%s: > %s\n", in_out, _accepter_address);

    _accepter_port = _parametri->accepter_port;

    fprintf(accepter_log, "%s: > %d\n", in_out, _accepter_port);

    _accepter_lista_buffer = _parametri->accepter_lista_buffer;

    fprintf(accepter_log, "%s: > %d\n", in_out, _accepter_in_out);

    fprintf(accepter_log, "%s: Socket:\n", in_out);

    fprintf(accepter_log, "%s: > listenFrom(%s,%d)\n", in_out, _accepter_address, _accepter_port);

    listening_socket = listenFrom(_accepter_address, _accepter_port);

    if (_accepter_in_out == 0) {

        fprintf(accepter_log, "%s: Loop:\n", in_out);

        fprintf(accepter_log, "%s: > while(receiver < agents)\n", in_out);

        fprintf(accepter_log, "%s: ---------- Loop Start ----------\n", in_out);

        while (numero_receiver_vivi < _accepter_agents_number) {

            buffer _M_buffer = allocBuffer(DIM_BUFFER);

            pthread_t receiver_;

            if ((communication_socket = accept(listening_socket, NULL, NULL)) == -1) {
                perror("Accepter accept() Error\n");
                exit(1);
            }

            fprintf(accepter_log, "%s: >>> accept connection\n", in_out);

            addElementToList(_accepter_lista_buffer, _M_buffer);

            numero_receiver_vivi++;

            pthread_create(&receiver_, NULL, &runReceiver, allocParamReceiver(communication_socket, _M_buffer));
        }
        fprintf(accepter_log, "%s: ----------- Loop End -----------\n", in_out);

    } else {

        fprintf(accepter_log, "%s: Loop:\n", in_out);

        fprintf(accepter_log, "%s: > while(sender < agents)\n", in_out);

        fprintf(accepter_log, "%s: --------- Loop Start ---------\n", in_out);

        while (numero_sender_vivi < _accepter_agents_number) {

            buffer _M_buffer = allocBuffer(DIM_BUFFER);

            pthread_t sender_;

            if ((communication_socket = accept(listening_socket, NULL, NULL)) == -1) {
                perror("Accepter accept() Error\n");
                exit(1);
            }

            fprintf(accepter_log, "%s: >>> accept connection\n", in_out);

            addElementToList(_accepter_lista_buffer, _M_buffer);

            numero_sender_vivi++;

            pthread_create(&sender_, NULL, &runSender, allocParamSender(communication_socket, _M_buffer));
        }

        fprintf(accepter_log, "%s: ---------- Loop End ----------\n", in_out);

    }

    fprintf(accepter_log, "%s: Socket:\n", in_out);

    /*
     * Chiude le due porte in listening
     */

    close(listening_socket);

    fprintf(accepter_log, "%s: > close listen socket\n", in_out);

    fprintf(accepter_log, "%s: Memoria:\n", in_out);

    free(_parametri_accepter);

    fprintf(accepter_log, "%s: > parametri\n", in_out);

    /*
     * fflush e chiusura del logfile
     */

    fprintf(accepter_log, "%s: Close log\n", in_out);

    fflush(accepter_log);

    fclose(accepter_log);

    return NULL;

}

static parametri_dispatcher allocParamDispatcher(buffer _dispatcher_buffer_in, list _dispatcher_lista_buffer_out) {

    parametri_dispatcher _M_parametri_dispatcher = (parametri_dispatcher) malloc(sizeof (struct parametri_dispatcher_t));

    _M_parametri_dispatcher->dispatcher_buffer_in = _dispatcher_buffer_in;
    _M_parametri_dispatcher->dispatcher_lista_buffer_out = _dispatcher_lista_buffer_out;

    return _M_parametri_dispatcher;

}

/*
 * Quando il dispatcher riceve il POISON_ID allora:
 * - lo inoltra nel sender buffer associato e solo in quello
 * - attende che tutti i receiver siano morti
 * - elimina il receiver buffer associato
 * - fa il fflush del file di log e lo chiude
 * - decrementa numero_dispatcher_vivi
 * - termina
 */

static void *runDispatcher(void *_parametri_dispatcher) {

    parametri_dispatcher _parametri = (parametri_dispatcher) _parametri_dispatcher;

    buffer _dispatcher_buffer_in = _parametri->dispatcher_buffer_in;
    list _dispatcher_lista_buffer_out;

    int _dispatcher_id;
    int _sender_buffer_id;

    list_iterator it_out;
    buffer _sender_buffer;

    msg _msg;

    gsl_matrix *_dispatcher_adjacency_matrix;

    FILE *dispatcher_adjacency_matrix_file;

    FILE *dispatcher_log;

    char dispatcher_log_file_name[FILENAME_MAX];

    _dispatcher_adjacency_matrix = gsl_matrix_alloc(6, 6);
    dispatcher_adjacency_matrix_file = fopen("adjacency_matrix.txt", "r");
    gsl_matrix_fscanf(dispatcher_adjacency_matrix_file, _dispatcher_adjacency_matrix);
    fclose(dispatcher_adjacency_matrix_file);

    /*
    gsl_matrix_fprintf(stdout, adjacency_matrix, "%f");
    printf("valore 0,0 = %f\n",gsl_matrix_get(adjacency_matrix,0,0));
    printf("valore 1,0 = %f\n",gsl_matrix_get(adjacency_matrix,1,0));
    printf("valore 0,1 = %f\n",gsl_matrix_get(adjacency_matrix,0,1));
    printf("valore 1,1 = %f\n",gsl_matrix_get(adjacency_matrix,1,1));
     */

    /* Apre il file di log impostando il suo id a quello del buffer associato */

    _dispatcher_id = getBufferID(_dispatcher_buffer_in);

    sprintf(dispatcher_log_file_name, "dispatcher_%d.log", _dispatcher_id);

    dispatcher_log = fopen(dispatcher_log_file_name, "w");

    if (dispatcher_log == NULL) {
        perror("Errore apertura file: \"dispatcher.log\"\n");
        exit(1);
    };

    setbuf(dispatcher_log, NULL);

    fprintf(dispatcher_log, "D%d: Open log\n", _dispatcher_id);

    /*sleep(1);*/

    fprintf(dispatcher_log, "D%d: Parametri di invocazione:\n", _dispatcher_id);

    _dispatcher_lista_buffer_out = _parametri->dispatcher_lista_buffer_out;

    fprintf(dispatcher_log, "D%d: Wait:\n", _dispatcher_id);

    fprintf(dispatcher_log, "D%d: > getFromBuffer(receiver_buffer)\n", _dispatcher_id);

    /*
     * Riceve un messaggio e lo pone in tutti i sender buffer finchè non riceve il POISON_ID
     */

    _msg = getFromBuffer_blocking(_dispatcher_buffer_in);

    fprintf(dispatcher_log, "D%d: ----------- Loop Start -----------\n", _dispatcher_id);

    while (_msg->msg_id != POISON_ID) {

        it_out = allocListIterator(_dispatcher_lista_buffer_out);

        while (hasNextList(it_out) == 1) {

            int i;

            _sender_buffer = nextElementFromList(it_out);

            _sender_buffer_id = getBufferID(_sender_buffer);

            i = gsl_matrix_get(_dispatcher_adjacency_matrix, _sender_buffer_id - 1, _dispatcher_id - 1);

            /* i = 1;*/

            /* fprintf(dispatcher_log, "sender %d receiver %d matrix_get %d\n", _sender_buffer_id, _dispatcher_id, i); */

            if (i == 1) {

                putInBuffer_blocking(_sender_buffer, _msg);

                fprintf(dispatcher_log, "D%d: > (%d,%d,%f,%f) > %d\n", _dispatcher_id, _msg->msg_id, _msg->msg_time, _msg->msg_x, _msg->msg_z, _sender_buffer_id);
            }
        }

        freeListIterator(it_out);

        fprintf(dispatcher_log, "D%d: ----------------------------------\n", _dispatcher_id);

        _msg = getFromBuffer_blocking(_dispatcher_buffer_in);
    }

    fprintf(dispatcher_log, "D%d: ------------ Loop End ------------\n", _dispatcher_id);

    /*
     * Inoltra nel sender buffer associato e solo in quello il POISON_ID
     */

    it_out = allocListIterator(_dispatcher_lista_buffer_out);

    _sender_buffer = nextElementFromList(it_out);

    _sender_buffer_id = getBufferID(_sender_buffer);

    while (_sender_buffer_id != _dispatcher_id) {

        _sender_buffer = nextElementFromList(it_out);

        _sender_buffer_id = getBufferID(_sender_buffer);
    }

    putInBuffer_blocking(_sender_buffer, _msg);

    fprintf(dispatcher_log, "D%d: Signal:\n", _dispatcher_id);

    fprintf(dispatcher_log, "D%d: > snd (%d,%d,%f,%f) > %d\n", _dispatcher_id, _msg->msg_id, _msg->msg_time, _msg->msg_x, _msg->msg_z, _sender_buffer_id);

    fprintf(dispatcher_log, "D%d: Wait:\n", _dispatcher_id);

    fprintf(dispatcher_log, "D%d: > numero_receiver_vivi > 0\n", _dispatcher_id);

    /*
     * Attende che tutti i receiver siano morti
     */

    while (numero_receiver_vivi > 0);

    fprintf(dispatcher_log, "D%d: Memoria:\n", _dispatcher_id);

    /*
     * free delle variabili usate
     */

    freeBuffer(_dispatcher_buffer_in);

    fprintf(dispatcher_log, "D%d: > free(receiver_buffer)\n", _dispatcher_id);

    free(_parametri_dispatcher);

    fprintf(dispatcher_log, "D%d: > parametri\n", _dispatcher_id);

    /*
     * Fa il fflush del file di log e lo chiude
     */

    fprintf(dispatcher_log, "D%d: Close log\n", _dispatcher_id);

    fflush(dispatcher_log);

    fclose(dispatcher_log);

    /*
     * Decrementa numero_dispatcher_vivi e termina
     */

    numero_dispatcher_vivi--;

    return NULL;

}

parametri_router allocParamRouter(char *_router_ip_to_listen, int _port_client_to_router, int _port_router_to_client) {

    parametri_router _M_parametri_router = (struct parametri_router_t *) malloc(sizeof (struct parametri_router_t));

    _M_parametri_router->router_ip_to_listen = _router_ip_to_listen;
    _M_parametri_router->port_router_to_client = _port_router_to_client;
    _M_parametri_router->port_client_to_router = _port_client_to_router;

    return _M_parametri_router;
}

void *runRouter(void *_parametri_router) {

    parametri_router _parametri = (parametri_router) _parametri_router;

    char *_router_ip_to_listen;
    int _port_client_to_router;
    int _port_router_to_client;

    int _router_agents_number;

    list lista_buffer_in_M_;
    list_iterator it_in;
    buffer _buffer_in;

    list lista_buffer_out_M_;

    pthread_t dispatcher_T_;
    pthread_t accepter_in_T_;
    pthread_t accepter_out_T_;

    FILE *router_log;

    /*
     * Apre il file di log
     */

    router_log = fopen("router.log", "w");

    if (router_log == NULL) {
        perror("Errore apertura file: \"router.log\"\n");
        exit(1);
    };

    setbuf(router_log, NULL);

    fprintf(router_log, ": Open log\n");

    fprintf(router_log, ": Parametri di invocazione:\n");

    _router_ip_to_listen = _parametri->router_ip_to_listen;

    fprintf(router_log, ": > _router_ip_to_listen = %s\n", _router_ip_to_listen);

    _port_client_to_router = _parametri->port_client_to_router;

    fprintf(router_log, ": > _port_client_to_router = %d\n", _port_client_to_router);

    _port_router_to_client = _parametri->port_router_to_client;

    fprintf(router_log, ": > _port_router_to_client = %d\n", _port_router_to_client);

    fprintf(router_log, ": Memoria:\n");

    _router_agents_number = 6;

    fprintf(router_log, ": > _router_agents_number = %d\n", _router_agents_number);

    lista_buffer_in_M_ = allocList();

    fprintf(router_log, ": > alloc(lista_buffer_in)\n");

    lista_buffer_out_M_ = allocList();

    fprintf(router_log, ": > alloc(lista_buffer_out)\n");

    /*
     * Fa partire i due accepter (threads)
     */

    pthread_create(&accepter_in_T_, NULL, &runAccepter, allocParamAccepter(_router_agents_number, _router_ip_to_listen, _port_client_to_router, lista_buffer_in_M_, 0));

    fprintf(router_log, ": Thread -< Accepter():\n");

    fprintf(router_log, ": > _accepter_agents_number = %d\n", _router_agents_number);

    fprintf(router_log, ": > _accepter_address = %s\n", _router_ip_to_listen);

    fprintf(router_log, ": > _accepter_port = %d\n", _port_client_to_router);

    fprintf(router_log, ": > _accepter_in_out = %d\n", 0);

    pthread_create(&accepter_out_T_, NULL, &runAccepter, allocParamAccepter(_router_agents_number, _router_ip_to_listen, _port_router_to_client, lista_buffer_out_M_, 1));

    fprintf(router_log, ": Thread -< Accepter():\n");

    fprintf(router_log, ": > _accepter_agents_number = %d\n", _router_agents_number);

    fprintf(router_log, ": > _accepter_address = %s\n", _router_ip_to_listen);

    fprintf(router_log, ": > _accepter_port = %d\n", _port_router_to_client);

    fprintf(router_log, ": > _accepter_in_out = %d\n", 1);

    /*
     * Attende che i due accepter abbiano terminato il loro compito
     */

    fprintf(router_log, ": Wait:\n");

    fprintf(router_log, ": > pthread_join(accepter_out)\n");

    pthread_join(accepter_out_T_, NULL);

    fprintf(router_log, ": > pthread_join(accepter_in)\n");

    pthread_join(accepter_in_T_, NULL);

    fprintf(router_log, ": Loop:\n");

    fprintf(router_log, ": > while hasNext(lista_buffer_in)\n");

    /*
     * Associa ad ogni receiver buffer un dispatcher
     */

    it_in = allocListIterator(lista_buffer_in_M_);

    fprintf(router_log, ": ----------- Loop Start -----------\n");

    while (hasNextList(it_in) == 1) {

        _buffer_in = nextElementFromList(it_in);

        numero_dispatcher_vivi++;

        pthread_create(&dispatcher_T_, NULL, &runDispatcher, allocParamDispatcher(_buffer_in, lista_buffer_out_M_));

        fprintf(router_log, ": >>> create dispatcher thread\n");
    }

    freeListIterator(it_in);

    fprintf(router_log, ": ------------ Loop End ------------\n");

    /* Attende che tutti i sender thread creati siano morti, infatti loro sono gli ultimi a morire */

    fprintf(router_log, ": Wait:\n");

    fprintf(router_log, ": > numero_sender_vivi > 0\n");

    while (numero_sender_vivi > 0);

    fprintf(router_log, ": Memoria:\n");

    /*
     * free delle variabili usate
     */

    freeList(lista_buffer_in_M_);
    lista_buffer_in_M_ = NULL;

    fprintf(router_log, ": > free(lista_buffer_in)\n");

    freeList(lista_buffer_out_M_);
    lista_buffer_out_M_ = NULL;

    fprintf(router_log, ": > free(lista_buffer_out)\n");

    free(_parametri_router);

    fprintf(router_log, ": > parametri\n");

    /*
     * fflush e chiusura del logfile
     */

    fprintf(router_log, ": Close log\n");

    fflush(router_log);

    fclose(router_log);

    return NULL;
}


#include "solver_intern.h"
#include <pygsl/general_helpers.h>
#include <pygsl/block_helpers.h>
#if 0
#include <pygsl/function_helpers.h>
#endif
#include <setjmp.h>
#include <gsl/gsl_math.h>
#include <pygsl/error_helpers.h>
#include <strings.h>
#include "solver_doc.ic"

PyObject * module = NULL;
const char *filename = __FILE__;

static int
PyGSL_solver_set_called(PyGSL_solver *self)
{
     FUNC_MESS_BEGIN();
     if(self->set_called == 1)
	  return GSL_SUCCESS;
     DEBUG_MESS(2, "self->set_called was %d", self->set_called);
     pygsl_error("The set() method must be called before using the other methods!",
	       filename, __LINE__, GSL_EINVAL);
     FUNC_MESS_END();
     return GSL_EINVAL;
}

static PyObject*
PyGSL_solver_restart(PyGSL_solver *self, PyObject *args)
{
     FUNC_MESS_BEGIN();
     if (PyGSL_SOLVER_SET_CALLED(self) != GSL_SUCCESS)
	  return NULL;

     if(self->mstatic->cmethods.restart == NULL)
	  PyGSL_ERROR_NULL("Can not restart a solver of this type!", GSL_ESANITY);
     self->mstatic->cmethods.restart(self->solver);
     Py_INCREF(Py_None);
     FUNC_MESS_END();
     return Py_None;
}

static PyObject*
PyGSL_solver_name(PyGSL_solver *self, PyObject *args)
{
     PyObject * tmp;
     const char * ctmp;
     FUNC_MESS_BEGIN();
     if(self->mstatic->cmethods.name == NULL)
	  PyGSL_ERROR_NULL("Can not restart a solver of this type!", GSL_ESANITY);
     ctmp = self->mstatic->cmethods.name(self->solver);
     tmp =  PyString_FromString(ctmp);
     FUNC_MESS_END();
     return tmp;
}

static PyObject*
PyGSL_solver_iterate(PyGSL_solver *self, PyObject *args)
{
     int tmp;
     FUNC_MESS_BEGIN();
     if (PyGSL_SOLVER_SET_CALLED(self) != GSL_SUCCESS)
	  return NULL;

     if(self->mstatic->cmethods.iterate == NULL)
	  PyGSL_ERROR_NULL("Can not restart a solver of this type!", GSL_ESANITY);

     assert(self->mstatic->cmethods.iterate);
     assert(self->solver);
     tmp = (self->mstatic->cmethods.iterate(self->solver));
     if(PyGSL_ERROR_FLAG(tmp) != GSL_SUCCESS)
	  return NULL;

     return PyInt_FromLong((long) tmp);
}

static void
PyGSL_solver_dealloc(PyGSL_solver * self)
{
     /* struct pygsl_array_cache * cache_ptr;
     int i, count;
     PyObject * ob;
     PyArrayObject *tmp;
     */
     FUNC_MESS_BEGIN();
     assert(self);
     assert(self->mstatic);

     if(self->mstatic->cmethods.free == NULL){
	  DEBUG_MESS(3, "Could not free solver @ %p. No free method specified!", self->solver);
     }else{
	  DEBUG_MESS(3, "Freeing a solver of type %s", self->mstatic->type_name);
	  if(self->solver != NULL){
	       self->mstatic->cmethods.free(self->solver);
	       self->solver = NULL;
	  }
     }

     Py_XDECREF(self->args);
     self->args = NULL;
     if(self->c_sys){
	  DEBUG_MESS(3, "Freeing c_sys @ %p", self->c_sys);
	  free(self->c_sys);
	  self->c_sys = NULL;
     }

     /*
      * remove the cached arrays
      */
     if(self->cache == NULL){
	  DEBUG_MESS(2, "No cache was used cache = %p", self->cache);
     }else{
#if 0
	  cache_ptr = self->cache;
	  for(i = 0; i< PyGSL_SOLVER_N_ARRAYS; ++i){
	       tmp =  cache_ptr[i].ref;
	       ob = (PyObject *) tmp;
	       if(ob == NULL){
		    break;
	       }
	       count =  ob->ob_refcnt;
	       if(count == 0){
		    DEBUG_MESS(3, "object[%d] @ %p has a zero reference count!"
			       " array should be deposed already and ptr set to zero!", i, ob);
	       }else if(count == 1){
		    /* no one referencing the array; good */
		    DEBUG_MESS(3, "Dereferencing  object[%d] @ %p", i, ob);
		    Py_DECREF(ob);
		    cache_ptr[i].ref = NULL;
	       }else if(count < 0){
		    DEBUG_MESS(2, "I found an array (object) at %p which had "
			       "a count [%d] smaller than zero", ob, count);

	       }else{
		    /* count > 1 */
		    fprintf(stderr, "In %s at %d array %d had %d refcouts.\n"
			    "This means you reference an array which was\n"
			    "passed to you while evaluating your callback.\n"
			    "This produces a memory leak!\n",
			    __FILE__, __LINE__, i, count);

	       }
	  } /* handeled cached objects */
	  fprintf(stderr, "Freed %d cached objects\n", i);
	  /* free the cache */
	  free(self->cache);
	  self->cache = NULL;
#endif

     } /* freeing cached objects */
     PyObject_Del(self);
     self = NULL;
     FUNC_MESS_END();

}

static PyObject *
PyGSL_solver_type(PyGSL_solver * self, PyObject *unused)
{
     assert(self->mstatic->type_name);
     return PyString_FromString(self->mstatic->type_name);
}

static PyMethodDef solver_methods[] = {
     {"name",    (PyCFunction) PyGSL_solver_name,    METH_NOARGS, NULL},
     {"restart", (PyCFunction) PyGSL_solver_restart, METH_NOARGS, NULL},
     {"iterate", (PyCFunction) PyGSL_solver_iterate, METH_NOARGS, NULL},
     {"type",    (PyCFunction) PyGSL_solver_type,    METH_NOARGS, NULL},
     {NULL, NULL, 0, NULL}
};
static PyObject *
PyGSL_solver_getattr(PyGSL_solver * self, char * name)
{
     PyObject *tmp = NULL;
     FUNC_MESS_BEGIN();
     if(self->mstatic->pymethods)
	  tmp = Py_FindMethod(self->mstatic->pymethods, (PyObject *) self, name);
     if(tmp == NULL){
	  PyErr_Clear();
	  tmp = Py_FindMethod(solver_methods, (PyObject *) self, name);
     }
     FUNC_MESS_END();
     return tmp;
}

static PyTypeObject PyGSL_solver_pytype = {
  PyObject_HEAD_INIT(NULL)	 /* fix up the type slot in initcrng */
  0,				 /* ob_size */
  "PyGSL_solver",		 /* tp_name */
  sizeof(PyGSL_solver),	 /* tp_basicsize */
  0,				 /* tp_itemsize */

  /* standard methods */
  (destructor)  PyGSL_solver_dealloc, /* tp_dealloc  ref-count==0  */
  (printfunc)   0,		 /* tp_print    "print x"     */
  (getattrfunc) PyGSL_solver_getattr,/* tp_getattr  "x.attr"      */
  (setattrfunc) 0,		 /* tp_setattr  "x.attr=v"    */
  (cmpfunc)     0,		   /* tp_compare  "x > y"       */
  (reprfunc)    0,                 /* tp_repr     `x`, print x  */

  /* type categories */
  0,				/* tp_as_number   +,-,*,/,%,&,>>,pow...*/
  0,				/* tp_as_sequence +,[i],[i:j],len, ...*/
  0,				/* tp_as_mapping  [key], len, ...*/

  /* more methods */
  (hashfunc)     0,		/* tp_hash    "dict[x]" */
  (ternaryfunc)  0,      /* tp_call    "x()"     */
  (reprfunc)     0,             /* tp_str     "str(x)"  */
  (getattrofunc) 0,		/* tp_getattro */
  (setattrofunc) 0,		/* tp_setattro */
  0,				/* tp_as_buffer */
  0L,				/* tp_flags */
  (char *) PyGSL_solver_type_doc		/* tp_doc */
};

PyGSL_solver*
_PyGSL_solver_init(const struct _SolverStatic *mstatic)
{
     PyGSL_solver *solver_o=NULL;
     int line = -1;
     int i;

     FUNC_MESS_BEGIN();
     if(mstatic->n_cbs > PyGSL_SOLVER_NCBS_MAX){
	  line = __LINE__ - 1;
	  pygsl_error("More callbacks requested than possible!", __FILE__,
		    line, GSL_ESANITY);
	  goto fail;
     }
     solver_o =  (PyGSL_solver *) PyObject_NEW(PyGSL_solver, &PyGSL_solver_pytype);
     if(solver_o == NULL){
	  line = __LINE__ -1;
	  goto fail;
     }

     solver_o->args = NULL;
     solver_o->cache = NULL;
     solver_o->mstatic = NULL;
     solver_o->mstatic = mstatic;
     solver_o->solver = NULL;
     solver_o->c_sys = NULL;
     solver_o->set_called = 0;
     solver_o->isset = 0;

     for(i = 0; i < PyGSL_SOLVER_NCBS_MAX; ++i){
	  solver_o->cbs[i] = NULL;
     }

     for(i = 0; i < PyGSL_SOLVER_PB_ND_MAX; ++i){
	  solver_o->problem_dimensions[i] = -1;
     }

     DEBUG_MESS(3, "refcount = %d", solver_o->ob_refcnt);
     FUNC_MESS_END();
     return solver_o;
 fail:
     FUNC_MESS("Fail");
     PyGSL_add_traceback(module, __FILE__, __FUNCTION__, line);
     return NULL;
}

static PyObject *
PyGSL_solver_dn_init(PyObject *self, PyObject *args, const solver_alloc_struct * alloc, int nd)
{
     PyGSL_solver *solver_o=NULL;
     int n1=1, n2=1;
     int line = -1;
     int flag=0;

     FUNC_MESS_BEGIN();
     assert(alloc);
     solver_o =  _PyGSL_solver_init(alloc->mstatic);
     if(solver_o == NULL){
	  line = __LINE__ - 2;
	  goto fail;
     }
     switch(nd){
     case 0: flag = 1; break;
     case 1: flag = PyArg_ParseTuple(args,"i", &n1); break;
     case 2: flag = PyArg_ParseTuple(args,"ii", &n1, &n2); break;
     case 3:
	  /* odeiv */
	  flag = 1; break;
     default:
	  line = __LINE__;
	  pygsl_error("Only 1 or two for number of problem_dimensions implemented!",
		    __FILE__, line, GSL_ESANITY);
	  goto fail;
     }
     if (0==flag){
	  /* Successful parsing of the arguments ?*/
	  line = __LINE__ - 1;
	  goto fail;
     }
     if (n1<=0) {
	  PyErr_SetString(PyExc_RuntimeError, "dimension 1 must be >0");
	  line = __LINE__ - 2;
	  goto fail;
     }
     if (n2<=0) {
	  PyErr_SetString(PyExc_RuntimeError, "dimension 2 must be >0");
	  line = __LINE__ - 2;
	  goto fail;
     }
     {
	  void *tmp = alloc->alloc;
	  switch(nd){
	  case 0:
	       solver_o->solver =  ((void_a_t)(tmp))(alloc->type);
	       break;
	  case 1:
	       solver_o->solver =  (void *) ((void_an_t) tmp)(alloc->type, n1);
	       break;
	  case 2:
	       DEBUG_MESS(3, "Allocating solver with N=%d, p=%d", n1, n2);
	       solver_o->solver =  (void *) ((void_anp_t) tmp)(alloc->type, n1, n2);
	       break;
	  case 3:
	       /* odeiv handles that itself */
	       break;
	  default:
	       pygsl_error("Only 0,1 or 2 for number of problem_dimensions implemented!",
			 __FILE__, __LINE__, GSL_ESANITY);
	       goto fail;
	  }
     }

     switch(nd){
     case 1:
     case 2:
	  if(solver_o->solver == NULL){
	       line = __LINE__ - 1;
	       goto fail;
	  }
	  break;
     default:
	  ;
     }
     switch(nd){
     case 1:
	  solver_o->problem_dimensions[0] = n1;
	  break;
     case 2:
	  solver_o->problem_dimensions[0] = n2;
	  solver_o->problem_dimensions[1] = n1;
	  break;
     default:
	  ;
     }

     solver_o->cache = (struct pygsl_array_cache *) calloc(PyGSL_SOLVER_N_ARRAYS, sizeof(struct pygsl_array_cache));
     if(solver_o->cache == NULL){
	  PyErr_NoMemory();
	  line = __LINE__ - 1;
	  goto fail;
     }

     FUNC_MESS_END();
     return (PyObject *) solver_o;
 fail:
     FUNC_MESS("Fail");
     DEBUG_MESS(3, "line was %d", line);
     PyGSL_add_traceback(module, __FILE__, __FUNCTION__, line);
     Py_XDECREF(solver_o);
     return NULL;
}

#if 0
static PyObject *
_PyGSL_solver_np_init(PyObject *self, PyObject *args, const solver_alloc_struct * alloc)
{
    FUNC_MESS_BEGIN();
    return PyGSL_solver_dn_init(self, args, alloc, 2);
    FUNC_MESS_END();
}

static PyObject *
PyGSL_solver_n_init(PyObject *self, PyObject *args, const solver_alloc_struct * alloc)
{
     FUNC_MESS_BEGIN();
     return PyGSL_solver_dn_init(self, args, alloc, 1);
     FUNC_MESS_END();
}

static PyObject *
_PyGSL_solver_1_init(PyObject *self, PyObject *args, const solver_alloc_struct * alloc)
{
     FUNC_MESS_BEGIN();
     return PyGSL_solver_dn_init(self, args, alloc, 0);
     FUNC_MESS_END();
}

#endif
static double
PyGSL_gsl_function(double x, void * params)
{
     PyGSL_solver *s;
     double result = GSL_NAN;
     int flag = GSL_EFAILED;

     FUNC_MESS_BEGIN();
     assert(params);
     assert(PyGSL_solver_check((PyObject *) params));
     s = (PyGSL_solver *) params;

     flag = PyGSL_function_wrap_helper(x, &result, NULL, s->cbs[0], s->args, __FUNCTION__);
     if(flag == GSL_SUCCESS){
	  FUNC_MESS_END();
	  return result;
     }
     FUNC_MESS("Fail");
     if(s->isset)
	  longjmp(s->buffer, flag);
     DEBUG_MESS(2, "Found an error of %d but could not jump!", flag);
     return GSL_NAN;
}

static double
PyGSL_gsl_function_df(double x, void * params)
{
     PyGSL_solver *s;
     double result = GSL_NAN;
     int flag = GSL_EFAILED;

     FUNC_MESS_BEGIN();
     assert(params);
     assert(PyGSL_solver_check((PyObject *) params));
     s = (PyGSL_solver *) params;

     flag = PyGSL_function_wrap_helper(x, &result, NULL, s->cbs[1], s->args, __FUNCTION__);
     if(flag == GSL_SUCCESS){
	  FUNC_MESS_END();
	  return result;
     }
     FUNC_MESS("Fail");
     if(s->isset)
	  longjmp(s->buffer, flag);
     DEBUG_MESS(2, "Found an error of %d but could not jump!", flag);
     return GSL_NAN;
}

static void
PyGSL_gsl_function_fdf(double x, void * params,  double *f, double *df)
{
     PyGSL_solver *s;
     int flag = GSL_EFAILED;

     FUNC_MESS_BEGIN();
     assert(params);
     assert(PyGSL_solver_check((PyObject *) params));
     s = (PyGSL_solver *) params;

     assert(s->cbs[2]);
     assert(PyCallable_Check(s->cbs[2]));
     flag = PyGSL_function_wrap_helper(x, f, df, s->cbs[2], s->args, __FUNCTION__);
     if(flag == GSL_SUCCESS){
	  FUNC_MESS_END();
	  return;
     }
     FUNC_MESS("Fail");
     if(s->isset)
	  longjmp(s->buffer, flag);
     DEBUG_MESS(2, "Found an error of %d but could not jump!", flag);
     *f = GSL_NAN;
     *df = GSL_NAN;
}

static PyObject*
PyGSL_solver_set_f(PyGSL_solver *self, PyObject *pyargs, PyObject *kw,
		    void *fptr, int isfdf)
{

     PyObject *f = NULL, *df = NULL, *fdf = NULL, *args = Py_None;
     int  flag=GSL_EFAILED;
     void *c_sys = NULL;
     gsl_function * f_sys = NULL;
     gsl_function_fdf * fdf_sys = NULL;
     double x0, lower=0, upper=0;

     static const char *f_kwlist[]   = {"f", "x0", "upper", "lower", "args", NULL};
     static const char *fdf_kwlist[] = {"f", "df", "fdf", "x0", "args", NULL};

     FUNC_MESS_BEGIN();
     assert(PyGSL_solver_check(self));
     if (self->solver == NULL) {
	  pygsl_error("Got a NULL Pointer of min.f",  filename, __LINE__ - 3, GSL_EFAULT);
	  return NULL;
     }

     assert(pyargs);
     /* arguments PyFunction, Parameters, start Vector, step Vector */

     if(isfdf == 0){
	  if (0==PyArg_ParseTupleAndKeywords(pyargs,kw,"OdddO", (char **)f_kwlist, &f,
					     &x0,&lower,&upper,&args))
	       return NULL;
     }else{
	  if (0==PyArg_ParseTupleAndKeywords(pyargs,kw,"OOOdO", (char **)fdf_kwlist, &f,&df,&fdf,
					     &x0,&args))
	       return NULL;
     }

     if(!PyCallable_Check(f)){
	  pygsl_error("First argument must be callable",  filename, __LINE__ - 3, GSL_EBADFUNC);
	  return NULL;
     }
     if(isfdf == 1){
	  if(!PyCallable_Check(df)){
	       pygsl_error("Second argument must be callable",  filename, __LINE__ - 3, GSL_EBADFUNC);
	       return NULL;
	  }
	  if(!PyCallable_Check(fdf)){
	       pygsl_error("Third argument must be callable",  filename, __LINE__ - 3, GSL_EBADFUNC);
	       return NULL;
	  }
     }
     if (self->c_sys != NULL) {
	  /* free the previous function and args */
	  c_sys = self->c_sys;
     } else {
	  /* allocate function space */
	  if(isfdf == 0)
	       c_sys=calloc(1, sizeof(gsl_function));
	  else
	       c_sys=calloc(1, sizeof(gsl_function_fdf));
	  if (c_sys == NULL) {
	       pygsl_error("Could not allocate the object for the minimizer function",
			 filename, __LINE__ - 3, GSL_ENOMEM);
	       goto fail;
	  }
     }
     DEBUG_MESS(3, "Everything allocated args = %p", args);
     /* add new function  and parameters */

     if(PyGSL_solver_func_set(self, args, f, df, fdf) != GSL_SUCCESS)
	  goto fail;

     /* initialize the function struct */
     if(isfdf == 0){
	  f_sys = c_sys;
	  f_sys->function=PyGSL_gsl_function;
	  f_sys->params=(void*)self;
     }else{
	  fdf_sys = c_sys;
	  fdf_sys->f=PyGSL_gsl_function;
	  fdf_sys->df=PyGSL_gsl_function_df;
	  fdf_sys->fdf=PyGSL_gsl_function_fdf;
	  fdf_sys->params=(void*)self;
     }
     DEBUG_MESS(3, "Setting jmp buffer isset = % d", self->isset);
     if((flag = setjmp(self->buffer)) == 0){
	  self->isset = 1;
	  if(isfdf == 0){
	       DEBUG_MESS(3, "Calling f isfdf = %d", isfdf);
	       flag = ((set_m_ddd_t) fptr)(self->solver, c_sys, x0, lower, upper);
	  }else{
	       DEBUG_MESS(3, "Calling fdf isfdf = %d", isfdf);
	       flag = ((set_m_d_t) fptr)(self->solver, c_sys, x0);
	  }
	  if(PyGSL_ERROR_FLAG(flag) != GSL_SUCCESS){
	       goto fail;
	  }
     } else {
	  goto fail;
     }
     DEBUG_MESS(4, "Set evaluated. flag = %d", flag);
     self->c_sys = c_sys;
     self->set_called = 1;

     self->isset = 0;
     Py_INCREF(Py_None);
     FUNC_MESS_END();
     return Py_None;

 fail:
     FUNC_MESS("Fail");
     PyGSL_ERROR_FLAG(flag);
     self->isset = 0;
     return NULL;

}

/*
 * Set the solver
 */
PyGSL_API_EXTERN PyObject *
PyGSL_solver_n_set(PyGSL_solver *self, PyObject *pyargs, PyObject *kw,
		    const struct pygsl_solver_n_set * info)
{
     int n, flag=GSL_EFAILED;
     PyGSL_array_index_t stride;

     PyObject *args=Py_None, *f=NULL, *df=NULL, *fdf=NULL, *x;
     PyArrayObject * xa = NULL;
     gsl_vector_view gsl_x;
     void *c_sys;
     int line = -1;
     static const char *f_kwlist[]   = {"f", "x0", "args", NULL};
     static const char *fdf_kwlist[] = {"f", "df", "fdf", "x0", "args", NULL};

     FUNC_MESS_BEGIN();
     assert(PyGSL_solver_check(self));
     if (self->solver == NULL) {
	  pygsl_error("solver ==  NULL at solver_n_set", filename, __LINE__ - 3, GSL_EFAULT);
	  return NULL;
     }

     /* arguments PyFunction, Parameters, start Vector, step Vector */
     if(info->is_fdf == 0){
	  if (0==PyArg_ParseTupleAndKeywords(pyargs, kw, "OO|O", (char **)f_kwlist,
					     &f, &x, &args))
	       return NULL;
     } else {
	  if (0==PyArg_ParseTupleAndKeywords(pyargs, kw, "OOOO|O", (char **)fdf_kwlist,
					     &f, &df, &fdf, &x, &args))
	       return NULL;
     }

     n=self->problem_dimensions[0];
     DEBUG_MESS(3, "len(x) should be %d", n);
     xa  = PyGSL_vector_check(x, n, PyGSL_DARRAY_INPUT(2), &stride, NULL);
     if (xa == NULL){
	  line = __LINE__ - 2;
	  goto fail;
     }
     gsl_x = gsl_vector_view_array_with_stride((double *)(xa->data), stride,
					       xa->dimensions[0]);

     if (self->c_sys != NULL) {
	  c_sys = self->c_sys;
     } else {
	  c_sys=info->c_sys;
     }

     if(PyGSL_solver_func_set(self, args, f, df, fdf) != GSL_SUCCESS){
	  line = __LINE__ - 1;
	  goto fail;
     }
     if((flag = setjmp(self->buffer)) == 0){
	  self->isset = 1;
	  flag = info->set(self->solver, c_sys, &gsl_x.vector);
	  if((PyGSL_ERROR_FLAG(flag)) != GSL_SUCCESS){
	  line = __LINE__ - 2;
	       goto fail;
	  }
     }else{
	  line = __LINE__ - 9;
	  goto fail;
     }
     self->c_sys = c_sys;
     self->isset = 0;
     Py_DECREF(xa);
     self->set_called = 1;

     Py_INCREF(Py_None);
     FUNC_MESS_END();
     return Py_None;

 fail:
     PyGSL_add_traceback(module, __FILE__, __FUNCTION__, line);
     self->isset = 0;
     self->set_called = 0;
     Py_XDECREF(xa);
     return NULL;

}

static PyObject*
PyGSL_solver_ret_size_t(PyGSL_solver *self, PyObject *args,
			size_t_m_t func)
{
     size_t result;
     FUNC_MESS_BEGIN();
     assert(PyGSL_solver_check(self));
     result=func(self->solver);
     FUNC_MESS_END();
     return (PyObject *) PyLong_FromLong((long)result);
}

static PyObject*
PyGSL_solver_ret_int(PyGSL_solver *self, PyObject *args,
			int_m_t func)
{
     int result;
     FUNC_MESS_BEGIN();
     assert(PyGSL_solver_check(self));
     result=func(self->solver);
     FUNC_MESS_END();
     return (PyObject *) PyLong_FromLong((long)result);
}

static PyObject*
PyGSL_solver_ret_double(PyGSL_solver *self, PyObject *args,
			double_m_t func)
{
     double result;
     FUNC_MESS_BEGIN();
     assert(PyGSL_solver_check(self));
     result=func(self->solver);
     FUNC_MESS_END();
     return (PyObject *) PyFloat_FromDouble(result);
}

static PyObject*
PyGSL_solver_ret_vec(PyGSL_solver *self, PyObject *args,
		     ret_vec func)
{
     gsl_vector *result;
     FUNC_MESS_BEGIN();
     assert(PyGSL_solver_check(self));
     result=func(self->solver);
     if(result == NULL)
	  PyGSL_ERROR_NULL("Could not retrive vector ...", GSL_ESANITY);
     FUNC_MESS_END();
     return (PyObject *) PyGSL_copy_gslvector_to_pyarray(result);
}

/*
 * evaluates a C function taking an vector and a double as input and returning a status.
 */
static PyObject*
PyGSL_solver_vd_i(PyObject * self, PyObject *args, int_f_vd_t func)
{
     PyObject *g=NULL;
     PyArrayObject *ga=NULL;
     gsl_vector_view gradient;

     double epsabs;
     int flag = GSL_EFAILED;
     PyGSL_array_index_t stride_recalc=-1;

     FUNC_MESS_BEGIN();
     if (0==PyArg_ParseTuple(args,"Od", &g, &epsabs))
	  return NULL;

     ga  = PyGSL_vector_check(g, -1, PyGSL_DARRAY_INPUT(1), &stride_recalc, NULL);
     if (ga == NULL){
	  PyGSL_add_traceback(module, filename, __FUNCTION__, __LINE__ - 1);
	  return NULL;
     }
     gradient = gsl_vector_view_array_with_stride((double *)(ga->data), stride_recalc,
						  ga->dimensions[0]);
     flag = func(&gradient.vector, epsabs);
     FUNC_MESS_END();
     return PyGSL_ERROR_FLAG_TO_PYINT(flag);

}

static PyObject *
PyGSL_solver_vvdd_i(PyObject * self, PyObject * args, int_f_vvdd_t func)
{
     int flag;
     int line = -1;
     double epsabs, epsrel;
     PyObject *dx_o, *x_o;
     PyArrayObject *dx_a = NULL, *x_a = NULL;
     gsl_vector_view x, dx;
     PyGSL_array_index_t dimension, stride;

     FUNC_MESS_BEGIN();
     if(!PyArg_ParseTuple(args, "OOdd", &dx_o, &x_o, &epsabs, &epsrel))
	  return NULL;

     dx_a = PyGSL_vector_check(dx_o, -1, PyGSL_DARRAY_INPUT(1), &stride, NULL);
     if(dx_a == NULL){
	  line = __LINE__ - 4;
	  goto fail;
     }
     dx = gsl_vector_view_array_with_stride((double *)(dx_a->data), stride, dx_a->dimensions[0]);

     dimension = dx_a->dimensions[0];
     x_a = PyGSL_vector_check(x_o, dimension, PyGSL_DARRAY_CINPUT(2), &stride,  NULL);
     if(x_a == NULL){
	  line = __LINE__ - 4;
	  goto fail;
     }
     x = gsl_vector_view_array_with_stride((double *)(x_a->data), stride, x_a->dimensions[0]);
     flag = func(&(dx.vector), &(x.vector), epsabs, epsrel);
     Py_DECREF(x_a);
     Py_DECREF(dx_a);
     FUNC_MESS_END();
     return PyGSL_ERROR_FLAG_TO_PYINT(flag);

 fail:
     PyGSL_add_traceback(module, __FILE__, __FUNCTION__, line);
     Py_XDECREF(dx_a);
     Py_XDECREF(x_a);
     return NULL;
}

PyGSL_API_EXTERN int
PyGSL_Callable_Check(PyObject *f, const char * myname)
{
     FUNC_MESS_BEGIN();
     if(!PyCallable_Check(f)){
	  char str[256];
	  snprintf(str, 254, "Callback named %s is not callable!", myname);
	  PyGSL_ERROR(str, GSL_EINVAL);
     }
     FUNC_MESS_END();
     return GSL_SUCCESS;
}

PyGSL_API_EXTERN int
PyGSL_solver_func_set(PyGSL_solver *self, PyObject *args, PyObject *f,
		       PyObject *df, PyObject *fdf)
{
     int flag = GSL_EFAILED;
     if(df){
	  if(!fdf)
	       PyGSL_ERROR("If df is given, fdf must be given as well!", GSL_ESANITY);
	  Py_XDECREF(self->cbs[1]);
	  Py_XDECREF(self->cbs[2]);
	  self->cbs[1] = NULL;
	  self->cbs[2] = NULL;
     }
     Py_XDECREF(self->args);
     Py_XDECREF(self->cbs[0]);
     self->args = NULL;
     self->cbs[0] = NULL;

     DEBUG_MESS(3, "args = %p", (void *) args);
     self->args=args; Py_XINCREF(args);
     assert(f);
     if((flag = PyGSL_CALLABLE_CHECK(f, "f")) != GSL_SUCCESS)
	  return flag;
     self->cbs[0] = f; Py_INCREF(f);
     if(df){
	  assert(fdf);
	  if((flag = PyGSL_CALLABLE_CHECK(df, "df")) != GSL_SUCCESS)
	       return flag;
	  if((flag = PyGSL_CALLABLE_CHECK(fdf, "fdf")) != GSL_SUCCESS)
	       return flag;
	  self->cbs[1]=df;   Py_INCREF(df);
	  self->cbs[2]=fdf;  Py_INCREF(fdf);
     }
     return GSL_SUCCESS;
}

static PyObject *
PyGSL_solver_GetSet(PyObject *self, PyObject *args, void * address, enum PyGSL_GETSET_typemode mode)
{
     PyObject *ret=NULL, *input=NULL;
     unsigned long ultmp;
     int flag;

     if (!PyArg_ParseTuple(args, "|O", &input))
	  return NULL;

     if(input){
	  switch(mode){
	  case PyGSL_MODE_DOUBLE:
	       flag = PyGSL_PYFLOAT_TO_DOUBLE(input, (double *) address, NULL);
	       break;
	  case PyGSL_MODE_INT:
	       flag = PyGSL_PYINT_TO_INT(input, (int *) address, NULL);
	       break;
	  case PyGSL_MODE_SIZE_T:
	       flag = PyGSL_PYLONG_TO_ULONG(input, &ultmp, NULL);
	       *((size_t *) address) = ultmp;
	       break;
	  default:
	       PyGSL_ERROR_NULL("Unknown mode",GSL_ESANITY);
	  }
	  if(PyGSL_ERROR_FLAG(flag) != GSL_SUCCESS)
	       return NULL;

	  Py_INCREF(Py_None);
	  ret = Py_None;
	  return ret;
     }

     switch(mode){
     case PyGSL_MODE_DOUBLE:
	  ret = PyFloat_FromDouble(*((double *) address));
	  break;
     case PyGSL_MODE_INT:
	  ret = PyInt_FromLong(((long) *((int *) address)));
	  break;
     case PyGSL_MODE_SIZE_T:
	  ret = PyLong_FromUnsignedLong(((unsigned long) *((size_t *) address)));
	  break;
     default:
	  PyGSL_ERROR_NULL("Unknown mode",GSL_ESANITY);
     }
     return ret;
}

#ifdef ONEFILE

#include "chars.c"
#include "function_helpers2.c"
/*

#include "function_helpers.c"
#include "integrate.c"
#include "odeiv.c"
#include "roots.c"
#include "minimize.c"
#include "multifit_nlin.c"
#include "multimin.c"
#include "multiroot.c"
*/
#endif /* ONEFILE */

static PyMethodDef solverMethods[] = {
     {NULL, NULL, 0, NULL}
};

void
init_api(void)
{
     FUNC_MESS_BEGIN();
     PyGSL_API[PyGSL_solver_type_NUM            ] = (void *) &PyGSL_solver_pytype     ;
     PyGSL_API[PyGSL_solver_ret_int_NUM         ] = (void *) &PyGSL_solver_ret_int    ;
     PyGSL_API[PyGSL_solver_ret_double_NUM      ] = (void *) &PyGSL_solver_ret_double ;
     PyGSL_API[PyGSL_solver_ret_size_t_NUM      ] = (void *) &PyGSL_solver_ret_size_t ;
     PyGSL_API[PyGSL_solver_ret_vec_NUM         ] = (void *) &PyGSL_solver_ret_vec    ;
     PyGSL_API[PyGSL_solver_dn_init_NUM         ] = (void *) &PyGSL_solver_dn_init    ;
     PyGSL_API[PyGSL_solver_vd_i_NUM            ] = (void *) &PyGSL_solver_vd_i       ;
     PyGSL_API[PyGSL_solver_vvdd_i_NUM          ] = (void *) &PyGSL_solver_vvdd_i     ;
     PyGSL_API[PyGSL_Callable_Check_NUM         ] = (void *) &PyGSL_Callable_Check    ;
     PyGSL_API[PyGSL_solver_func_set_NUM        ] = (void *) &PyGSL_solver_func_set   ;
     PyGSL_API[PyGSL_function_wrap_OnOn_On_NUM  ] = (void *) &PyGSL_function_wrap_OnOn_On;
     PyGSL_API[PyGSL_function_wrap_On_O_NUM     ] = (void *) &PyGSL_function_wrap_On_O;
     PyGSL_API[PyGSL_function_wrap_Op_On_NUM    ] = (void *) &PyGSL_function_wrap_Op_On;
     PyGSL_API[PyGSL_function_wrap_Op_Opn_NUM   ] = (void *) &PyGSL_function_wrap_Op_Opn;
     PyGSL_API[PyGSL_function_wrap_Op_On_Opn_NUM] = (void *) &PyGSL_function_wrap_Op_On_Opn;
     PyGSL_API[PyGSL_solver_n_set_NUM           ] = (void *) &PyGSL_solver_n_set      ;
     PyGSL_API[PyGSL_solver_set_f_NUM           ] = (void *) &PyGSL_solver_set_f      ;
     PyGSL_API[PyGSL_solver_getset_NUM          ] = (void *) &PyGSL_solver_GetSet     ;
     FUNC_MESS_END();
}

void
initsolver(void)
{
  PyObject* m, *dict, *item;

  FUNC_MESS_BEGIN();
  m=Py_InitModule("solver", solverMethods);
  init_pygsl();

  /* init multimin type */
  PyGSL_solver_pytype.ob_type  = &PyType_Type;

  init_api();

  module = m;

  Py_INCREF((PyObject*)&PyGSL_solver_pytype);

  dict = PyModule_GetDict(m);
  if(!dict)
       goto fail;

  if (!(item = PyString_FromString((char*)PyGSL_solver_module_doc))){
       PyErr_SetString(PyExc_ImportError,
		       "I could not generate module doc string!");
       goto fail;
  }
  if (PyDict_SetItemString(dict, "__doc__", item) != 0){
       PyErr_SetString(PyExc_ImportError,
		       "I could not init doc string!");
       goto fail;
  }
  FUNC_MESS_END();

 fail:
  FUNC_MESS("FAIL");
  return;

}


#ifndef __GSL_BLOCK_H__
#define __GSL_BLOCK_H__

#if !defined( GSL_FUN )
#  if !defined( GSL_DLL )
#    define GSL_FUN extern
#  elif defined( BUILD_GSL_DLL )
#    define GSL_FUN extern __declspec(dllexport)
#  else
#    define GSL_FUN extern __declspec(dllimport)
#  endif
#endif

#include <gsl/gsl_block_complex_long_double.h>
#include <gsl/gsl_block_complex_double.h>
#include <gsl/gsl_block_complex_float.h>

#include <gsl/gsl_block_long_double.h>
#include <gsl/gsl_block_double.h>
#include <gsl/gsl_block_float.h>

#include <gsl/gsl_block_ulong.h>
#include <gsl/gsl_block_long.h>

#include <gsl/gsl_block_uint.h>
#include <gsl/gsl_block_int.h>

#include <gsl/gsl_block_ushort.h>
#include <gsl/gsl_block_short.h>

#include <gsl/gsl_block_uchar.h>
#include <gsl/gsl_block_char.h>

#endif /* __GSL_BLOCK_H__ */


#ifndef OPENMC_TALLIES_FILTER_MATERIAL_H
#define OPENMC_TALLIES_FILTER_MATERIAL_H

#include <cstdint>
#include <unordered_map>

#include <gsl/gsl>

#include "openmc/tallies/filter.h"
#include "openmc/vector.h"

namespace openmc {

//==============================================================================
//! Specifies which material tally events reside in.
//==============================================================================

class MaterialFilter : public Filter
{
public:
  //----------------------------------------------------------------------------
  // Constructors, destructors

  ~MaterialFilter() = default;

  //----------------------------------------------------------------------------
  // Methods

  std::string type() const override {return "material";}

  void from_xml(pugi::xml_node node) override;

  void get_all_bins(const Particle& p, TallyEstimator estimator, FilterMatch& match)
  const override;

  void to_statepoint(hid_t filter_group) const override;

  std::string text_label(int bin) const override;

  //----------------------------------------------------------------------------
  // Accessors

  vector<int32_t>& materials() { return materials_; }

  const vector<int32_t>& materials() const { return materials_; }

  void set_materials(gsl::span<const int32_t> materials);

private:
  //----------------------------------------------------------------------------
  // Data members

  //! The indices of the materials binned by this filter.
  vector<int32_t> materials_;

  //! A map from material indices to filter bin indices.
  std::unordered_map<int32_t, int> map_;
};

} // namespace openmc
#endif // OPENMC_TALLIES_FILTER_MATERIAL_H


/* vector/gsl_vector_uchar.h
 *
 * Copyright (C) 1996, 1997, 1998, 1999, 2000 Gerard Jungman, Brian Gough
 *
 * 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; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#ifndef __GSL_VECTOR_UCHAR_H__
#define __GSL_VECTOR_UCHAR_H__

#include <stdlib.h>
#include <gsl/gsl_types.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_check_range.h>
#include <gsl/gsl_block_uchar.h>

#undef __BEGIN_DECLS
#undef __END_DECLS
#ifdef __cplusplus
# define __BEGIN_DECLS extern "C" {
# define __END_DECLS }
#else
# define __BEGIN_DECLS /* empty */
# define __END_DECLS /* empty */
#endif

__BEGIN_DECLS

typedef struct
{
  size_t size;
  size_t stride;
  unsigned char *data;
  gsl_block_uchar *block;
  int owner;
}
gsl_vector_uchar;

typedef struct
{
  gsl_vector_uchar vector;
} _gsl_vector_uchar_view;

typedef _gsl_vector_uchar_view gsl_vector_uchar_view;

typedef struct
{
  gsl_vector_uchar vector;
} _gsl_vector_uchar_const_view;

typedef const _gsl_vector_uchar_const_view gsl_vector_uchar_const_view;

/* Allocation */

GSL_EXPORT gsl_vector_uchar *gsl_vector_uchar_alloc (const size_t n);
GSL_EXPORT gsl_vector_uchar *gsl_vector_uchar_calloc (const size_t n);

GSL_EXPORT gsl_vector_uchar *gsl_vector_uchar_alloc_from_block (gsl_block_uchar * b,
                                                                const size_t offset,
                                                                const size_t n,
                                                                const size_t stride);

GSL_EXPORT gsl_vector_uchar *gsl_vector_uchar_alloc_from_vector (gsl_vector_uchar * v,
                                                                 const size_t offset,
                                                                 const size_t n,
                                                                 const size_t stride);

GSL_EXPORT void gsl_vector_uchar_free (gsl_vector_uchar * v);

/* Views */

GSL_EXPORT
_gsl_vector_uchar_view
gsl_vector_uchar_view_array (unsigned char *v, size_t n);

GSL_EXPORT
_gsl_vector_uchar_view
gsl_vector_uchar_view_array_with_stride (unsigned char *base,
                                         size_t stride,
                                         size_t n);

GSL_EXPORT
_gsl_vector_uchar_const_view
gsl_vector_uchar_const_view_array (const unsigned char *v, size_t n);

GSL_EXPORT
_gsl_vector_uchar_const_view
gsl_vector_uchar_const_view_array_with_stride (const unsigned char *base,
                                               size_t stride,
                                               size_t n);

GSL_EXPORT
_gsl_vector_uchar_view
gsl_vector_uchar_subvector (gsl_vector_uchar *v,
                            size_t i,
                            size_t n);

GSL_EXPORT
_gsl_vector_uchar_view
gsl_vector_uchar_subvector_with_stride (gsl_vector_uchar *v,
                                        size_t i,
                                        size_t stride,
                                        size_t n);

GSL_EXPORT
_gsl_vector_uchar_const_view
gsl_vector_uchar_const_subvector (const gsl_vector_uchar *v,
                                  size_t i,
                                  size_t n);

GSL_EXPORT
_gsl_vector_uchar_const_view
gsl_vector_uchar_const_subvector_with_stride (const gsl_vector_uchar *v,
                                              size_t i,
                                              size_t stride,
                                              size_t n);

/* Operations */

GSL_EXPORT unsigned char gsl_vector_uchar_get (const gsl_vector_uchar * v, const size_t i);
GSL_EXPORT void gsl_vector_uchar_set (gsl_vector_uchar * v, const size_t i, unsigned char x);

GSL_EXPORT unsigned char *gsl_vector_uchar_ptr (gsl_vector_uchar * v, const size_t i);
GSL_EXPORT const unsigned char *gsl_vector_uchar_const_ptr (const gsl_vector_uchar * v, const size_t i);

GSL_EXPORT void gsl_vector_uchar_set_zero (gsl_vector_uchar * v);
GSL_EXPORT void gsl_vector_uchar_set_all (gsl_vector_uchar * v, unsigned char x);
GSL_EXPORT int gsl_vector_uchar_set_basis (gsl_vector_uchar * v, size_t i);

GSL_EXPORT int gsl_vector_uchar_fread (FILE * stream, gsl_vector_uchar * v);
GSL_EXPORT int gsl_vector_uchar_fwrite (FILE * stream, const gsl_vector_uchar * v);
GSL_EXPORT int gsl_vector_uchar_fscanf (FILE * stream, gsl_vector_uchar * v);
GSL_EXPORT int gsl_vector_uchar_fprintf (FILE * stream, const gsl_vector_uchar * v,
                                         const char *format);

GSL_EXPORT int gsl_vector_uchar_memcpy (gsl_vector_uchar * dest, const gsl_vector_uchar * src);

GSL_EXPORT int gsl_vector_uchar_reverse (gsl_vector_uchar * v);

GSL_EXPORT int gsl_vector_uchar_swap (gsl_vector_uchar * v, gsl_vector_uchar * w);
GSL_EXPORT int gsl_vector_uchar_swap_elements (gsl_vector_uchar * v, const size_t i, const size_t j);

GSL_EXPORT unsigned char gsl_vector_uchar_max (const gsl_vector_uchar * v);
GSL_EXPORT unsigned char gsl_vector_uchar_min (const gsl_vector_uchar * v);
GSL_EXPORT void gsl_vector_uchar_minmax (const gsl_vector_uchar * v, unsigned char * min_out, unsigned char * max_out);

GSL_EXPORT size_t gsl_vector_uchar_max_index (const gsl_vector_uchar * v);
GSL_EXPORT size_t gsl_vector_uchar_min_index (const gsl_vector_uchar * v);
GSL_EXPORT void gsl_vector_uchar_minmax_index (const gsl_vector_uchar * v, size_t * imin, size_t * imax);

GSL_EXPORT int gsl_vector_uchar_add (gsl_vector_uchar * a, const gsl_vector_uchar * b);
GSL_EXPORT int gsl_vector_uchar_sub (gsl_vector_uchar * a, const gsl_vector_uchar * b);
GSL_EXPORT int gsl_vector_uchar_mul (gsl_vector_uchar * a, const gsl_vector_uchar * b);
GSL_EXPORT int gsl_vector_uchar_div (gsl_vector_uchar * a, const gsl_vector_uchar * b);
GSL_EXPORT int gsl_vector_uchar_scale (gsl_vector_uchar * a, const double x);
GSL_EXPORT int gsl_vector_uchar_add_constant (gsl_vector_uchar * a, const double x);

GSL_EXPORT int gsl_vector_uchar_isnull (const gsl_vector_uchar * v);

#ifdef HAVE_INLINE

extern inline
unsigned char
gsl_vector_uchar_get (const gsl_vector_uchar * v, const size_t i)
{
#if GSL_RANGE_CHECK
  if (i >= v->size)
    {
      GSL_ERROR_VAL ("index out of range", GSL_EINVAL, 0);
    }
#endif
  return v->data[i * v->stride];
}

extern inline
void
gsl_vector_uchar_set (gsl_vector_uchar * v, const size_t i, unsigned char x)
{
#if GSL_RANGE_CHECK
  if (i >= v->size)
    {
      GSL_ERROR_VOID ("index out of range", GSL_EINVAL);
    }
#endif
  v->data[i * v->stride] = x;
}

extern inline
unsigned char *
gsl_vector_uchar_ptr (gsl_vector_uchar * v, const size_t i)
{
#if GSL_RANGE_CHECK
  if (i >= v->size)
    {
      GSL_ERROR_NULL ("index out of range", GSL_EINVAL);
    }
#endif
  return (unsigned char *) (v->data + i * v->stride);
}

extern inline
const unsigned char *
gsl_vector_uchar_const_ptr (const gsl_vector_uchar * v, const size_t i)
{
#if GSL_RANGE_CHECK
  if (i >= v->size)
    {
      GSL_ERROR_NULL ("index out of range", GSL_EINVAL);
    }
#endif
  return (const unsigned char *) (v->data + i * v->stride);
}

#endif /* HAVE_INLINE */

__END_DECLS

#endif /* __GSL_VECTOR_UCHAR_H__ */