/*
 * {{ soc_name }}_descriptors.hpp
 * Generated by PeakRDL-pybind11
 * 
 * C++ descriptor classes for SystemRDL register map
 */

#ifndef {{ soc_name.upper() }}_DESCRIPTORS_HPP
#define {{ soc_name.upper() }}_DESCRIPTORS_HPP

#include <cstdint>
#include <string>
#include <memory>
#include <functional>
#include <stdexcept>
#include <vector>
#include <sstream>
#include <iomanip>

namespace {{ soc_name }} {

// Forward declarations
class Master;
class RegisterBase;
class FieldBase;

/**
 * Abstract Master interface for register access
 */
class Master {
public:
    virtual ~Master() = default;
    
    /**
     * Read a value from the given address
     */
    virtual uint64_t read(uint64_t address, size_t width) = 0;
    
    /**
     * Write a value to the given address
     */
    virtual void write(uint64_t address, uint64_t value, size_t width) = 0;
    
    /**
     * String representation
     */
    virtual std::string __repr__() const {
        return "<Master>";
    }
};

/**
 * Base class for register fields
 */
class FieldBase {
public:
    FieldBase(const std::string& name, uint64_t offset, 
              uint8_t lsb, uint8_t width, bool readable, bool writable)
        : name_(name), offset_(offset), lsb_(lsb), 
          width_(width), readable_(readable), writable_(writable) {}
    
    virtual ~FieldBase() = default;
    
    const std::string& name() const { return name_; }
    uint64_t offset() const { return offset_; }
    uint8_t lsb() const { return lsb_; }
    uint8_t msb() const { return lsb_ + width_ - 1; }
    uint8_t width() const { return width_; }
    bool is_readable() const { return readable_; }
    bool is_writable() const { return writable_; }
    
    uint64_t mask() const { 
        return ((1ULL << width_) - 1) << lsb_; 
    }
    
    /**
     * String representation
     */
    std::string __repr__() const {
        std::ostringstream oss;
        oss << "<Field '" << name_ << "' @ 0x" << std::hex << offset_ 
            << " [" << std::dec << static_cast<int>(msb()) << ":" << static_cast<int>(lsb()) << "]"
            << (readable_ ? " R" : "") << (writable_ ? "W" : "") << ">";
        return oss.str();
    }
    
protected:
    std::string name_;
    uint64_t offset_;
    uint8_t lsb_;
    uint8_t width_;
    bool readable_;
    bool writable_;
};

/**
 * Base class for registers
 */
class RegisterBase {
public:
    RegisterBase(const std::string& name, uint64_t offset, size_t width)
        : name_(name), offset_(offset), width_(width), master_(nullptr), 
          in_context_(false), cached_value_(0) {}
    
    virtual ~RegisterBase() = default;
    
    const std::string& name() const { return name_; }
    uint64_t offset() const { return offset_; }
    size_t width() const { return width_; }
    
    void set_master(Master* master) { master_ = master; }
    
    /**
     * Read the full register value
     */
    uint64_t read() {
        if (!master_) {
            throw std::runtime_error("No master attached to register: " + name_);
        }
        // If in context, return cached value
        if (in_context_) {
            return cached_value_;
        }
        return master_->read(offset_, width_);
    }
    
    /**
     * Write the full register value
     */
    void write(uint64_t value) {
        if (!master_) {
            throw std::runtime_error("No master attached to register: " + name_);
        }
        // If in context, update cached value
        if (in_context_) {
            cached_value_ = value;
        } else {
            master_->write(offset_, value, width_);
        }
    }
    
    /**
     * Read-modify-write operation with mask
     */
    void modify(uint64_t value, uint64_t mask) {
        uint64_t current = read();
        uint64_t new_value = (current & ~mask) | (value & mask);
        write(new_value);
    }
    
    /**
     * Enter context manager - read current value and cache it
     */
    RegisterBase* __enter__() {
        if (in_context_) {
            throw std::runtime_error("Register " + name_ + " is already in a context");
        }
        if (!master_) {
            throw std::runtime_error("No master attached to register: " + name_);
        }
        // Read current value and cache it
        cached_value_ = master_->read(offset_, width_);
        in_context_ = true;
        return this;
    }
    
    /**
     * Exit context manager - write cached value back to register
     */
    void exit_context() {
        if (!in_context_) {
            throw std::runtime_error("Register " + name_ + " is not in a context");
        }
        // Write cached value back to register
        if (master_) {
            master_->write(offset_, cached_value_, width_);
        }
        in_context_ = false;
    }
    
    /**
     * String representation
     */
    std::string __repr__() const {
        std::ostringstream oss;
        oss << "<Register '" << name_ << "' @ 0x" << std::hex << std::setfill('0') 
            << std::setw(4) << offset_ << " (" << std::dec << width_ << " bits)>";
        return oss.str();
    }
    
protected:
    std::string name_;
    uint64_t offset_;
    size_t width_;
    Master* master_;
    bool in_context_;
    uint64_t cached_value_;
};

/**
 * Base class for address maps and register files
 */
class NodeBase {
public:
    NodeBase(const std::string& name, uint64_t offset)
        : name_(name), offset_(offset), master_(nullptr) {}
    
    virtual ~NodeBase() = default;
    
    const std::string& name() const { return name_; }
    uint64_t offset() const { return offset_; }
    
    virtual void set_master(Master* master) { 
        master_ = master;
    }
    
    /**
     * String representation
     */
    virtual std::string __repr__() const {
        std::ostringstream oss;
        oss << "<Node '" << name_ << "' @ 0x" << std::hex << std::setfill('0') 
            << std::setw(4) << offset_ << ">";
        return oss.str();
    }
    
protected:
    std::string name_;
    uint64_t offset_;
    Master* master_;
};

/**
 * Base class for memory arrays
 * Provides list-like interface for accessing memory entries
 */
template<typename EntryType>
class MemoryBase : public NodeBase {
public:
    MemoryBase(const std::string& name, uint64_t base_offset, size_t num_entries, size_t entry_size)
        : NodeBase(name, base_offset), num_entries_(num_entries), entry_size_(entry_size) {
        // Pre-allocate all entries
        entries_.reserve(num_entries);
        for (size_t i = 0; i < num_entries; ++i) {
            entries_.emplace_back(base_offset + i * entry_size);
        }
    }
    
    virtual ~MemoryBase() = default;
    
    void set_master(Master* master) override {
        NodeBase::set_master(master);
        for (auto& entry : entries_) {
            entry.set_master(master);
        }
    }
    
    /**
     * Get entry at specific index
     */
    EntryType& operator[](size_t index) {
        if (index >= num_entries_) {
            throw std::out_of_range("Memory index out of range: " + std::to_string(index));
        }
        return entries_[index];
    }
    
    const EntryType& operator[](size_t index) const {
        if (index >= num_entries_) {
            throw std::out_of_range("Memory index out of range: " + std::to_string(index));
        }
        return entries_[index];
    }
    
    /**
     * Get number of entries
     */
    size_t size() const { return num_entries_; }
    
    /**
     * Iterator support
     */
    typename std::vector<EntryType>::iterator begin() { return entries_.begin(); }
    typename std::vector<EntryType>::iterator end() { return entries_.end(); }
    typename std::vector<EntryType>::const_iterator begin() const { return entries_.begin(); }
    typename std::vector<EntryType>::const_iterator end() const { return entries_.end(); }
    
    /**
     * String representation
     */
    std::string __repr__() const override {
        std::ostringstream oss;
        oss << "<Memory '" << name_ << "' @ 0x" << std::hex << std::setfill('0') 
            << std::setw(4) << offset_ << " [" << std::dec << num_entries_ << " entries]>";
        return oss.str();
    }
    
protected:
    std::vector<EntryType> entries_;
    size_t num_entries_;
    size_t entry_size_;
};

{% for addrmap in nodes.addrmaps %}
{% if addrmap != top_node %}
// Forward declaration for {{ addrmap.inst_name }}
class {{ addrmap.inst_name }}_t;
{% endif %}
{% endfor %}

{% for regfile in nodes.regfiles %}
// Forward declaration for {{ regfile.inst_name }}
class {{ regfile.inst_name }}_t;
{% endfor %}

{% for mem in nodes.mems %}
// Forward declaration for memory {{ mem.inst_name }}
class {{ mem.inst_name }}_t;
{% endfor %}

{% for reg in nodes.regs %}
// Register: {{ reg.inst_name }}
class {{ reg.inst_name }}_t : public RegisterBase {
public:
    {{ reg.inst_name }}_t(uint64_t base_offset) 
        : RegisterBase("{{ reg.inst_name }}", base_offset + 0x{{ "%x" | format(reg.absolute_address) }}, {{ reg.size }}) {}
    
    {% for field in reg.fields() %}
    // Field: {{ field.inst_name }}
    class {{ field.inst_name }}_field : public FieldBase {
    public:
        {{ field.inst_name }}_field({{ reg.inst_name }}_t* parent)
            : FieldBase("{{ field.inst_name }}", parent->offset(), 
                       {{ field.low }}, {{ field.width }},
                       {{ 'true' if field.is_hw_readable or field.is_sw_readable else 'false' }},
                       {{ 'true' if field.is_hw_writable or field.is_sw_writable else 'false' }}),
              parent_(parent) {}
        
        uint64_t read() {
            if (!is_readable()) {
                throw std::runtime_error("Field {{ field.inst_name }} is not readable");
            }
            uint64_t reg_val = parent_->read();
            return (reg_val & mask()) >> lsb_;
        }
        
        void write(uint64_t value) {
            if (!is_writable()) {
                throw std::runtime_error("Field {{ field.inst_name }} is not writable");
            }
            uint64_t field_val = (value << lsb_) & mask();
            parent_->modify(field_val, mask());
        }
        
    private:
        {{ reg.inst_name }}_t* parent_;
    };
    
    {{ field.inst_name }}_field {{ field.inst_name }}{this};
    {% endfor %}
};

{% endfor %}

{% for regfile in nodes.regfiles %}
// Regfile: {{ regfile.inst_name }}
class {{ regfile.inst_name }}_t : public NodeBase {
public:
    {{ regfile.inst_name }}_t(uint64_t base_offset) 
        : NodeBase("{{ regfile.inst_name }}", base_offset) {
        {% for child in regfile.children() %}
        {% if child.inst_name %}
        {{ child.inst_name }}.set_master(master_);
        {% endif %}
        {% endfor %}
    }
    
    void set_master(Master* master) override {
        NodeBase::set_master(master);
        {% for child in regfile.children() %}
        {% if child.inst_name %}
        {{ child.inst_name }}.set_master(master);
        {% endif %}
        {% endfor %}
    }
    
    void set_offset(uint64_t base_offset) {
        offset_ = base_offset;
    }
    
    {% for child in regfile.children() %}
    {% if child.inst_name %}
    {{ child.inst_name }}_t {{ child.inst_name }}{offset_};
    {% endif %}
    {% endfor %}
};

{% endfor %}

{% for mem in nodes.mems %}
// Memory: {{ mem.inst_name }}
{%- set entry_reg = mem.children()|list|first %}

class {{ mem.inst_name }}_t : public MemoryBase<{{ entry_reg.inst_name }}_t> {
public:
    {{ mem.inst_name }}_t(uint64_t base_offset) 
        : MemoryBase<{{ entry_reg.inst_name }}_t>("{{ mem.inst_name }}", base_offset, {{ mem.get_property('mementries') }}, {{ entry_reg.size }}) {}
    
    void set_offset(uint64_t base_offset) {
        offset_ = base_offset;
        // Update all entry offsets
        size_t entry_size = {{ entry_reg.size }};
        for (size_t i = 0; i < entries_.size(); ++i) {
            entries_[i] = {{ entry_reg.inst_name }}_t(base_offset + i * entry_size);
        }
        // Re-apply master if set
        if (master_) {
            set_master(master_);
        }
    }
};

{% endfor %}

{% for addrmap in nodes.addrmaps %}
{% if addrmap != top_node %}
// Addrmap: {{ addrmap.inst_name }}
class {{ addrmap.inst_name }}_t : public NodeBase {
public:
    {{ addrmap.inst_name }}_t(uint64_t base_offset) 
        : NodeBase("{{ addrmap.inst_name }}", base_offset) {
        {% for child in addrmap.children() %}
        {% if child.inst_name %}
        {{ child.inst_name }}.set_master(master_);
        {% endif %}
        {% endfor %}
    }
    
    void set_master(Master* master) override {
        NodeBase::set_master(master);
        {% for child in addrmap.children() %}
        {% if child.inst_name %}
        {{ child.inst_name }}.set_master(master);
        {% endif %}
        {% endfor %}
    }
    
    void set_offset(uint64_t base_offset) {
        offset_ = base_offset;
    }
    
    {% for child in addrmap.children() %}
    {% if child.inst_name %}
    {{ child.inst_name }}_t {{ child.inst_name }}{offset_};
    {% endif %}
    {% endfor %}
};

{% endif %}
{% endfor %}

// Top-level SoC class
class {{ soc_name }}_t : public NodeBase {
public:
    {{ soc_name }}_t() : NodeBase("{{ soc_name }}", 0) {
        {% for child in top_node.children() %}
        {% if child.inst_name and not child.__class__.__name__ == 'RegNode' %}
        {{ child.inst_name }}.set_offset(offset_);
        {% endif %}
        {% endfor %}
    }
    
    void attach_master(Master* master) {
        set_master(master);
        propagate_master(master);
    }
    
    void set_offset(uint64_t offset) {
        offset_ = offset;
        {% for child in top_node.children() %}
        {% if child.inst_name and not child.__class__.__name__ == 'RegNode' %}
        {{ child.inst_name }}.set_offset(offset_);
        {% endif %}
        {% endfor %}
    }
    
    void propagate_master(Master* master) {
        {% for child in top_node.children() %}
        {% if child.inst_name %}
        {{ child.inst_name }}.set_master(master);
        {% endif %}
        {% endfor %}
    }
    
    {% for child in top_node.children() %}
    {% if child.inst_name %}
    {{ child.inst_name }}_t {{ child.inst_name }}{0x{{ "%x" | format(child.absolute_address if child.absolute_address else 0) }}};
    {% endif %}
    {% endfor %}
};

} // namespace {{ soc_name }}

#endif // {{ soc_name.upper() }}_DESCRIPTORS_HPP
