// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
//
// Code available from: https://verilator.org
//
// Copyright 2026-2026 by Wilson Snyder. This program is free software; you can
// redistribute it and/or modify it under the terms of either the GNU
// Lesser General Public License Version 3 or the Perl Artistic License
// Version 2.0.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
///
/// \file
/// \brief Verilator: Runtime support for force/release statements
///
/// This file provides runtime data structures for efficient dynamic
/// resolution of force/release statements. A sorted list of active
/// forces is maintained that can be efficiently queried and modified
/// at runtime.
///
//*************************************************************************

#ifndef VERILATOR_VERILATED_FORCE_H_
#define VERILATOR_VERILATED_FORCE_H_

#include "verilatedos.h"

#include <algorithm>
#include <cassert>
#include <cstddef>
#include <type_traits>
#include <vector>

template <typename T>
using VlForceBaseType = typename std::remove_cv<typename std::remove_reference<T>::type>::type;

// VlForceRead - Helper functions to read a forced value
//
// These functions combine original value with forced values based on
// VlForceVec entries.
// This achieves O(k) complexity where k = number of active forces.

template <typename T>
struct VlForceTypeInfo final {
    using Type = VlForceBaseType<T>;
    static constexpr bool bitwise
        = std::is_integral<Type>::value || std::is_enum<Type>::value || VlIsVlWide<Type>::value;
    static constexpr bool unpackedArray = false;
};

template <typename T>
struct VlForceArrayIndexer final {
    static constexpr std::size_t size = 1;

    static T& elem(T& value, std::size_t) { return value; }
};

template <typename T, std::size_t N>
struct VlForceArrayIndexer<VlUnpacked<T, N>> final {
    static constexpr std::size_t size = N * VlForceArrayIndexer<T>::size;

    static auto& elem(VlUnpacked<T, N>& array, std::size_t index) {
        constexpr std::size_t subSize = VlForceArrayIndexer<T>::size;
        return VlForceArrayIndexer<T>::elem(array[index / subSize], index % subSize);
    }
};

template <typename T, std::size_t N>
struct VlForceTypeInfo<VlUnpacked<T, N>> final {
    using Type = VlUnpacked<T, N>;
    static constexpr bool bitwise = false;
    static constexpr bool unpackedArray = true;
};

template <typename T, bool = std::is_enum<T>::value>
struct VlForceStorageTypeOf final {
    using type = typename std::make_unsigned<T>::type;
};

template <typename T>
struct VlForceStorageTypeOf<T, true> final {
    using type = typename std::make_unsigned<typename std::underlying_type<T>::type>::type;
};

template <typename T>
using VlForceStorageType = typename VlForceStorageTypeOf<VlForceBaseType<T>>::type;

//=============================================================================
// VlForceVec - Vector of active force entries for a signal
//
// This class maintains a sorted vector of non-overlapping force entries.
// When a new force is added, it removes or trims existing entries that
// overlap with the new range.
//
// The generated code will:
// 1. Use addForce/release to update the active forces
// 2. Call a generated read function that iterates entries and evaluates RHS

class VlForceVec final {
private:
    struct Entry final {
        int m_lsb;  // Inclusive lower bit
        int m_msb;  // Inclusive upper bit
        int m_rhsLsb;  // Destination index that maps to RHS index 0
        const void* m_rhsDatap;  // Pointer to RHS storage

        bool operator<(const Entry& other) const { return m_msb < other.m_msb; }
    };

    std::vector<Entry> m_entries;  // Sorted by msb, non-overlapping

    std::vector<Entry>::iterator trimEntries(int lsb, int msb) {
        auto it = std::lower_bound(m_entries.begin(), m_entries.end(), lsb,
                                   [](const Entry& e, int bit) { return e.m_msb < bit; });
        while (it != m_entries.end() && it->m_lsb <= msb) {
            if (it->m_lsb < lsb && it->m_msb > msb) {
                const Entry right{msb + 1, it->m_msb, it->m_rhsLsb, it->m_rhsDatap};
                it->m_msb = lsb - 1;
                return m_entries.insert(++it, right);
            }
            if (it->m_lsb < lsb) {
                it->m_msb = lsb - 1;
                ++it;
                continue;
            }
            if (it->m_msb > msb) {
                it->m_lsb = msb + 1;
                return it;
            }
            it = m_entries.erase(it);
        }
        return it;
    }

    static QData extractRhsChunk(const Entry& entry, int rhsLsb, int width) {
        assert(width > 0 && width <= VL_QUADSIZE);
        assert(rhsLsb >= 0);

        const QData mask = static_cast<QData>(VL_MASK_Q(width));
        const int rhsWidth = entry.m_msb - entry.m_rhsLsb + 1;
        if (rhsWidth <= VL_QUADSIZE) {
            const QData rhsVal = static_cast<QData>(*static_cast<const QData*>(entry.m_rhsDatap));
            return (rhsVal >> rhsLsb) & mask;
        }

        WDataInP rhswp = WDataInP::external(static_cast<const EData*>(entry.m_rhsDatap));
        return VL_SEL_QWII(rhsWidth, rhswp, rhsLsb, width) & mask;
    }

    template <typename T>
    static T applyBits(T cur, const Entry& entry, int lsb, int width, int rhsLsb) {
        const T lowMask = static_cast<T>(VL_MASK_Q(width));
        const T mask = static_cast<T>(lowMask << lsb);
        const T rhsBits = static_cast<T>(
            (static_cast<T>(extractRhsChunk(entry, rhsLsb, width)) & lowMask) << lsb);
        return static_cast<T>((cur & ~mask) | (rhsBits & mask));
    }

    static void applyEntry(WDataOutP reswp, const Entry& entry, int entryLsb, int entryMsb,
                           int lsbOffset) {
        const int resLsb = entryLsb - lsbOffset;
        const int resMsb = entryMsb - lsbOffset;
        const int lword = VL_BITWORD_E(resLsb);
        const int hword = VL_BITWORD_E(resMsb);
        for (int word = lword; word <= hword; ++word) {
            const int wordLsb = word * VL_EDATASIZE;
            const int segLsb = std::max(resLsb, wordLsb);
            const int segMsb = std::min(resMsb, wordLsb + VL_EDATASIZE - 1);
            const int segWidth = segMsb - segLsb + 1;
            const int bitOffset = segLsb - wordLsb;
            const int rhsLsb = lsbOffset + segLsb - entry.m_rhsLsb;
            reswp[word] = applyBits(reswp[word], entry, bitOffset, segWidth, rhsLsb);
        }
    }

    template <typename T>
    static typename std::enable_if<!VlIsVlWide<T>::value && VlForceTypeInfo<T>::bitwise, T>::type
    applyEntry(T result, const Entry& entry) {
        using U = VlForceStorageType<T>;
        const int width = entry.m_msb - entry.m_lsb + 1;
        const int bits = static_cast<int>(sizeof(U) * 8);
        const int rhsLsb = entry.m_lsb - entry.m_rhsLsb;
        const QData rhsChunk = extractRhsChunk(entry, rhsLsb, width);
        if (width >= bits) return static_cast<T>(static_cast<U>(rhsChunk));
        return static_cast<T>(
            applyBits(static_cast<U>(result), entry, entry.m_lsb, width, rhsLsb));
    }

    template <typename T>
    static typename std::enable_if<!VlForceTypeInfo<T>::bitwise, T>::type
    applyEntry(T result, const Entry& entry) {
        static_cast<void>(result);
        return *static_cast<const VlForceBaseType<T>*>(entry.m_rhsDatap);
    }

    template <typename T>
    typename std::enable_if<VlIsVlWide<T>::value>::type applyEntries(T& val) const {
        for (const auto& entry : m_entries) {
            applyEntry(val, entry, entry.m_lsb, entry.m_msb, 0);
        }
    }

    template <typename T>
    typename std::enable_if<!VlIsVlWide<T>::value>::type applyEntries(T& val) const {
        for (const auto& entry : m_entries) val = applyEntry(val, entry);
    }

    void readSel(int lbits, WDataInP valp, WDataOutP reswp, int lsb, int width) const {
        VL_SEL_WWII(width, lbits, reswp, valp, lsb, width);
        const int msb = lsb + width - 1;
        auto it = std::lower_bound(m_entries.begin(), m_entries.end(), lsb,
                                   [](const Entry& e, int bit) { return e.m_msb < bit; });
        while (it != m_entries.end() && it->m_lsb <= msb) {
            applyEntry(reswp, *it, std::max(it->m_lsb, lsb), std::min(it->m_msb, msb), lsb);
            ++it;
        }
    }

public:
    VlForceVec() = default;

    template <typename T>
    T read(const T& val) const {
        T result = val;
        if VL_CONSTEXPR_CXX17 (VlForceTypeInfo<T>::unpackedArray) {
            // Handling the case of a nested flattened array using recursion
            using ElemRef
                = decltype(VlForceArrayIndexer<T>::elem(result, static_cast<std::size_t>(0)));
            using Elem = VlForceBaseType<ElemRef>;
            const int total = static_cast<int>(VlForceArrayIndexer<T>::size);
            for (const auto& entry : m_entries) {
                const Elem* const rhsBasep = static_cast<const Elem*>(entry.m_rhsDatap);
                const int startIdx = entry.m_lsb;
                const int endIdx = entry.m_msb;
                for (int idx = startIdx; idx <= endIdx; idx++) {
                    const int rhsIndex = idx - entry.m_rhsLsb;
                    const std::size_t uidx = static_cast<std::size_t>(idx);
                    VlForceArrayIndexer<T>::elem(result, uidx) = rhsBasep[rhsIndex];
                }
            }
            return result;
        }
        applyEntries(result);
        return result;
    }

    template <typename T>
    T readIndex(T origVal, int index) const {
        if (m_entries.empty()) return origVal;

        const auto it = std::lower_bound(m_entries.begin(), m_entries.end(), index,
                                         [](const Entry& e, int idx) { return e.m_msb < idx; });
        if (it != m_entries.end() && it->m_lsb <= index) {
            const int rhsIndex = index - it->m_rhsLsb;
            const T* const rhsBasep = static_cast<const T*>(it->m_rhsDatap);
            return rhsBasep[rhsIndex];
        }
        return origVal;
    }

    IData readSelI(int lbits, WDataInP valp, int lsb, int width) const {
        IData result;
        readSel(lbits, valp, WDataOutP::external(reinterpret_cast<EData*>(&result)), lsb, width);
        result &= VL_MASK_I(width);
        return result;
    }

    QData readSelQ(int lbits, WDataInP valp, int lsb, int width) const {
        QData result;
        readSel(lbits, valp, WDataOutP::external(reinterpret_cast<EData*>(&result)), lsb, width);
        result &= VL_MASK_Q(width);
        return result;
    }

    template <std::size_t N_Words>
    VlWide<N_Words> readSelW(int lbits, WDataInP valp, int lsb, int width) const {
        VlWide<N_Words> result;
        readSel(lbits, valp, result, lsb, width);
        result[N_Words - 1] &= VL_MASK_E(width);
        return result;
    }

    void addForce(int lsb, int msb, const void* rhsDatap, int rhsLsb) {
        assert(lsb <= msb);
        assert(rhsDatap);
        assert(rhsLsb <= lsb);

        auto it = trimEntries(lsb, msb);
        m_entries.insert(it, {lsb, msb, rhsLsb, rhsDatap});
    }

    void release(int lsb, int msb) {
        assert(lsb <= msb);
        trimEntries(lsb, msb);
    }

    void touch() {}
};

#endif  // guard
