#pragma once

#include <mutex>
#include <stdexcept>
#include <thread>
#include <vector>

#include <segment.hpp>
#include <cell_tree.hpp>
#include <stimulus.hpp>

namespace nest {
namespace mc {

/// wrapper around compartment layout information derived from a high level cell
/// description
struct compartment_model {
    cell_tree tree;
    std::vector<int> parent_index;
    std::vector<int> segment_index;
};

struct segment_location {
    segment_location(int s, double l)
    : segment(s), position(l)
    {
        EXPECTS(position>=0. && position<=1.);
    }
    int segment;
    double position;
};

int find_compartment_index(
    segment_location const& location,
    compartment_model const& graph
);

/// high-level abstract representation of a cell and its segments
class cell {
    public:

    // types
    using index_type = int;
    using value_type = double;
    using point_type = point<value_type>;

    // constructor
    cell();

    /// add a soma to the cell
    /// radius must be specified
    soma_segment* add_soma(value_type radius, point_type center=point_type());

    /// add a cable
    /// parent is the index of the parent segment for the cable section
    /// cable is the segment that will be moved into the cell
    cable_segment* add_cable(index_type parent, segment_ptr&& cable);

    /// add a cable by constructing it in place
    /// parent is the index of the parent segment for the cable section
    /// args are the arguments to be used to consruct the new cable
    template <typename... Args>
    cable_segment* add_cable(index_type parent, Args ...args);

    /// the number of segments in the cell
    int num_segments() const;

    bool has_soma() const;

    class segment* segment(int index);
    class segment const* segment(int index) const;

    /// access pointer to the soma
    /// returns nullptr if the cell has no soma
    soma_segment* soma();

    /// access pointer to a cable segment
    /// will throw an std::out_of_range exception if
    /// the cable index is not valid
    cable_segment* cable(int index);

    /// the volume of the cell
    value_type volume() const;

    /// the surface area of the cell
    value_type area() const;

    /// the total number of compartments over all segments
    size_t num_compartments() const;

    std::vector<segment_ptr> const& segments() const;

    /// return reference to array that enumerates the index of the parent of
    /// each segment
    std::vector<int> const& segment_parents() const;

    /// return a vector with the compartment count for each segment in the cell
    std::vector<int> compartment_counts() const;

    compartment_model model() const;

    void add_stimulus(segment_location loc, i_clamp stim);

    std::vector<std::pair<segment_location, i_clamp>>&
    stimulii() {
        return stimulii_;
    }

    const std::vector<std::pair<segment_location, i_clamp>>&
    stimulii() const {
        return stimulii_;
    }

    private:

    // storage for connections
    std::vector<index_type> parents_;

    // the segments
    std::vector<segment_ptr> segments_;

    // the stimulii
    std::vector<std::pair<segment_location, i_clamp>> stimulii_;
};

// create a cable by forwarding cable construction parameters provided by the user
template <typename... Args>
cable_segment* cell::add_cable(cell::index_type parent, Args ...args)
{
    // check for a valid parent id
    if(parent>=num_segments()) {
        throw std::out_of_range(
            "parent index of cell segment is out of range"
        );
    }
    segments_.push_back(make_segment<cable_segment>(std::forward<Args>(args)...));
    parents_.push_back(parent);

    return segments_.back()->as_cable();
}

} // namespace mc
} // namespace nest