
from sfctools.core.world import World
from sfctools.core.flow_matrix import FlowMatrix
from collections import defaultdict
import numpy as np
import pandas as pd
import re
import warnings


# class SfcDict:
#     """
#     this is a dictionary which stores single key-values pairs
#     but also remembers the sum of all elements. This allows the user
#     to retrieve the sum of all elements without having to sum over the values every time
#     """

#     def __init__(self):
#         self.data = defaultdict(lambda: 0.0)
#         self.sum = 0

#     def __getitem__(self, k):
#         return self.data[k]

#     def __setitem__(self, k, v):
#         old_val = self.data[k]
#         self.data[k] = v
#         self.sum += v - old_val


class SfcDict:
    """
    A dictionary that stores key-value pairs, remembers the sum of all values,
    and updates a FlowMatrix when values are modified.
    """

    def __init__(self, d, title="Unknown", agent_name="Unknown", ref_name="Unknown", accounting=(0, 1)):
        """
        Initialize the SfcDict.

        Parameters:
            d (dict): starting dict or collections.defaultdict
            title (str): Title of the dictionary.
            agent_name (str): Name of the agent or economic entity.
            ref_name (str): Name of the referenced agent or economic entity.
            accounting (tuple): Accounting identifiers (default (0, 1)).
        """
        self.data = defaultdict(lambda: 0.0)
        for k, v in d.items():
            self.data[k] = v

        self.sum = 0.0
        self.title = title
        self.name = agent_name
        self.ref = ref_name
        self.accounting = accounting

    def __getitem__(self, k):
        return self.data[k]

    def __setitem__(self, k, v):
        """
        Set a key-value pair and update the sum and FlowMatrix.

        Parameters:
            k: The key to set.
            v: The value to associate with the key.
        """

        # if isinstance(k, int): and (k in self.data):
        old_val = self.data[k]
        delta_val = v - old_val
        self.data[k] = v
        self.sum += delta_val
        # else:
        #    self.data[k] = v
        #    delta_val = v
        #    self.sum = v

        # Retrieve the agents corresponding to this dictionary.
        agent1 = None
        agent2 = None
        if self.name is not None:
            agent1 = World().get_agents_of_type(self.name)[0]
        if self.ref is not None:
            agent2 = World().get_agents_of_type(self.ref)[0]

        # Update FlowMatrix for the first agent.
        if agent1 is not None and self.accounting[0] is not None:
            if self.accounting[0] == 0:  # Asset case
                FlowMatrix(
                ).capital_flow_data[f"Δ {self.title}"][agent1] += delta_val
            else:  # Liability case
                FlowMatrix(
                ).capital_flow_data[f"Δ {self.title}"][agent1] -= delta_val

        # Update FlowMatrix for the second agent.
        if agent2 is not None and self.accounting[1] is not None:
            if self.accounting[1] == 0:  # Asset case
                FlowMatrix(
                ).capital_flow_data[f"Δ {self.title}"][agent2] += delta_val
            else:  # Liability case
                FlowMatrix(
                ).capital_flow_data[f"Δ {self.title}"][agent2] -= delta_val


class SfcArrayBalanceMatrix:
    """
    This is an array-implementation of the stock-flow consistent balance sheet matrix.
    """

    def __init__(self):
        self.data1d = None
        self.data2d = None
        self.data3d = None
        self.refresh_instances()

    def refresh_instances(self):
        """Refresh the lists of instances to include all current instances of the arrays."""

        self.data3d = Sfc3DArray.instances
        self.data2d = Sfc2DArray.instances
        self.data1d = SfcArray.instances

    def fetch_balance(self, name, j, t=-1):
        """
        get a single balance sheet of one specific agent by its index j

        Args:
            name (str): name of the agent class you refer to
            j (int): index of the agent to be viewed
            t (int): time index to use
        """

        self.refresh_instances()

        if len(self.data1d) > 0 or len(self.data2d) > 0:
            raise RuntimeError(
                "Cannot build balance because 1D and/or 2D SfcArray items are present, not allowing to track down the agent-to-agent relations.\nUsage of Sfc3DAray is recommended.")

        data_assets = pd.DataFrame()
        data_1bs = pd.DataFrame()

        d = {"A": data_assets, "L": data_1bs}
        agents = []

        # Process 2D arrays
        memo_a = []
        memo_l = []

        for matrix in self.data3d:
            labels = [matrix.row_name, matrix.col_name]
            for i, array in enumerate([matrix.row_sums, matrix.col_sums]):

                if not labels[i] == name:
                    continue

                if matrix.accounting[i] == 0 and ((matrix.title, labels[i]) not in memo_a):
                    data_assets.loc[(matrix.title, labels[i])] = 0
                    memo_a.append((matrix.title, labels[i]))
                elif matrix.accounting[i] == 1 and ((matrix.title, labels[i]) not in memo_l):
                    data_1bs.loc[(matrix.title, labels[i])] = 0
                    memo_l.append((matrix.title, labels[i]))
                else:
                    continue

                if matrix.accounting[i] == 0:  # Assets
                    data_assets.loc[(matrix.title, labels[i])] += array[j, t]
                elif matrix.accounting[i] == 1:  # 1iabilities
                    data_1bs.loc[(matrix.title, labels[i])] += array[j, t]
                else:
                    continue
                # NOTE entry 2 (Equity) is not counted under Liabilities

                agents.append(labels[i])

        df = pd.concat(d.values(), axis=1, keys=["A", "L"])  # d.keys())
        df = df.swaplevel(axis=1)
        df = df[sorted(df)]

        # return df
        from sfctools.core.agent import Agent

        class Temp(Agent):
            def __init__(self):
                super().__init__()

            def __repr__(self):
                return "%s_%05i" % (name, j)

            def __str__(self):
                return str(self.__repr__())

        temp_agent = Temp()

        totA = 0
        totL = 0
        with temp_agent.bal.modify_f0:
            for i, val in enumerate(df[(name, "A")]):

                if val != 0.0 and not np.isnan(val):
                    totA += val
                    temp_agent[(str(df.index[i]), 0)] += val
            for i, val in enumerate(df[(name, "L")]):

                if val != 0.0 and not np.isnan(val):
                    totL += val
                    temp_agent[(str(df.index[i]), 1)] += val

        nw = totA - totL
        with temp_agent.bal.modify_f0:
            temp_agent[("Residual", 2)] = nw
        return temp_agent.balance_sheet

    def to_dataframe(self, add_total=True, add_nw=False, t=None):
        """
        Generates a balance sheet matrix using the data from Sfc2DArray and SfcArray instances.


        Args:
            add_total (bool, optional): add 'Total' entries. Defaults to True.
            add_nw (bool, optional): add 'Net Worth' entries. Defaults to False.
            t (int, optional): time step to evaluate - this is neede if there aer any Sfc3DArrays present, where the 3rd dimension is considered the temporal dimension.
                               Defaults to None.
        Returns:
            Pandas dataframe with the balance matrix
        """

        data_assets = pd.DataFrame()
        data_1bs = pd.DataFrame()

        d = {"A": data_assets, "L": data_1bs}
        agents = []
        agent_count = defaultdict(lambda: [])

        # Process 1D arrays
        for array in self.data1d:

            for i, name in enumerate([array.name, array.ref]):
                if array.accounting[i] == 0:  # Assets
                    data_assets.loc[(array.title, name)] = array.get_sum()
                elif array.accounting[i] == 1:  # 1bilities
                    data_1bs.loc[(array.title, name)] = array.get_sum()
                agents.append(name)
            agent_count[array.name].append(len(array))

        # Process 2D arrays
        memo_a = []
        memo_l = []

        for matrix in self.data2d:
            labels = [matrix.row_name, matrix.col_name]
            for i, array in enumerate([matrix.row_sums, matrix.col_sums]):

                if matrix.accounting[i] == 0 and ((matrix.title, labels[i]) not in memo_a):
                    data_assets.loc[(matrix.title, labels[i])] = 0
                    memo_a.append((matrix.title, labels[i]))
                elif matrix.accounting[i] == 1 and ((matrix.title, labels[i]) not in memo_l):
                    data_1bs.loc[(matrix.title, labels[i])] = 0
                    memo_l.append((matrix.title, labels[i]))

                if matrix.accounting[i] == 0:  # Assets
                    data_assets.loc[(matrix.title, labels[i])] += np.sum(array)
                elif matrix.accounting[i] == 1:  # 1bilities
                    data_1bs.loc[(matrix.title, labels[i])] += np.sum(array)

                agents.append(labels[i])
                agent_count[labels[i]].append(len(array))

        # Process 3D arrays
        # memo_a = []
        # memo_l = []
        # [:,t]
        if len(self.data3d) > 0 and t is None:
            raise RuntimeError("Sfc3DArrays are present but no t was given.")

        for matrix in self.data3d:
            labels = [matrix.row_name, matrix.col_name]
            for i, array in enumerate([matrix.row_sums, matrix.col_sums]):

                if matrix.accounting[i] == 0 and ((matrix.title, labels[i]) not in memo_a):
                    data_assets.loc[(matrix.title, labels[i])] = 0
                    memo_a.append((matrix.title, labels[i]))
                elif matrix.accounting[i] == 1 and ((matrix.title, labels[i]) not in memo_l):
                    data_1bs.loc[(matrix.title, labels[i])] = 0
                    memo_l.append((matrix.title, labels[i]))

                if matrix.accounting[i] == 0:  # Assets
                    data_assets.loc[(matrix.title, labels[i])
                                    ] += np.sum(array[:, t])
                elif matrix.accounting[i] == 1:  # 1bilities
                    data_1bs.loc[(matrix.title, labels[i])
                                 ] += np.sum(array[:, t])

                agents.append(labels[i])
                agent_count[labels[i]].append(len(array))

        for k, v in agent_count.items():
            assert all([vi == v[0]
                        for vi in v]), "Dimension mismatch for %s: %s" % (k, v)

        all_idx = list(set(list(data_assets.index) + list(data_1bs.index)))
        all_col = list(
            set(list(data_assets.columns) + list(data_1bs.columns)))

        for entry in all_idx:
            if entry not in data_assets.index:
                data_assets.loc[entry] = np.nan
            if entry not in data_1bs.index:
                data_1bs.loc[entry] = np.nan
        for entry in all_col:
            if entry not in data_assets.columns:
                data_assets[entry] = np.nan
            if entry not in data_1bs.columns:
                data_1bs[entry] = np.nan

        df = pd.concat(d.values(), axis=1, keys=["A", "L"])  # d.keys())
        df = df.swaplevel(axis=1)
        df = df[sorted(df)]

        df.columns = df.columns.set_levels(['A', 'L'], level=1)
        sum_A = df.iloc[:, df.columns.get_level_values(1) == 'A'].sum(axis=1)
        sum_L = df.iloc[:, df.columns.get_level_values(1) == 'L'].sum(axis=1)

        # total row/col
        if add_total:
            df["Total"] = sum_A - sum_L
            df.loc["Total"] = df.sum(axis=0)

        # net wealth
        if add_nw:
            cols_A = df[~df["Total"]].iloc[:,
                                           df.columns.get_level_values(1) == 'A'].columns
            cols_L = df[~df["Total"]].iloc[:,
                                           df.columns.get_level_values(1) == 'L'].columns

            for i in cols_A:
                coli_A = cols_A[i]
                coli_L = cols_L[i]
                print("coliA, coliL", coli_A, coli_L)
                df.loc["Net Wealth"][coli_A] = ""
                df.loc["Net Wealth"][coli_L] = "test"

        return df

    def to_string(self, replace_zeros=True, justify="right", round=2, flip=False, t=None, add_nw=True):
        """
        returns a string representation of the balance matrix
        """

        df = self.to_dataframe(t=t, add_total=True, add_nw=add_nw)
        df = df.replace(np.nan, 0.0)
        df = df.round(round)

        if flip:
            df = df.T

        if replace_zeros:
            df = df.replace(0.0, " .- ")

        df = df.rename(columns={"Total": "Total "})
        s = df.to_string(justify=justify)

        def convert_line(s, idx, r=" | "):

            out = ""
            for j in range(len(s)):
                out += str(s[j])
                if j in idx and j < len(s) - 1:
                    out += r
            return out

        if flip:

            lines = s.split("\n")
            hline = ["-" * len(lines[0])]
            hrule = ["-" * len(lines[0])]

            idx = [m.start() for m in re.finditer("\s+(?=\w)", lines[0])][1:]
            idx.append(max([len(str(i)) for i in df.T.index]))

            mylines = ["   " + convert_line(lines[0], idx[:-1])]
            mylines += [convert_line(hrule[0], idx, r="---")]
            i = 1
            while i < len(lines) - 2:
                mylines += [convert_line(lines[i], idx)]
                if i % 2 == 0:
                    mylines += [convert_line(hline[0], idx, r="-+-")]
                i += 1
            mylines += [convert_line(hrule[0], idx, r="---")]
            mylines = [
                "|" + i + "|" for i in [convert_line(hrule[0], idx, r="---")] + mylines]
            return "\n".join(mylines)

        # add separator lines
        lines = s.split("\n")
        idx = [m.start() for m in re.finditer("L", lines[1])]
        idx.append(max([len(str(i)) for i in df.index]))

        hline = [convert_line("-" * len(lines[0]), idx, r="-+-")]
        hrule = [convert_line("-" * len(lines[0]), idx, r="---")]

        # mylines = hrule
        mylines = [convert_line(i, idx) for i in lines[:2]]
        mylines += hline
        mylines += [convert_line(i, idx) for i in lines[2:-1]]
        mylines += hline
        mylines += [convert_line(i, idx) for i in [lines[-1]]]
        mylines += hrule
        mylines = ["|" + i + "|" for i in hrule + mylines]
        return "\n".join(mylines)


def agent_subclass_generator(name='Unknown'):
    """
    automatic generator for a new class of agents
    """

    def init(self, *args, **kwargs):
        super(cls, self).__init__(*args, **kwargs)

    from sfctools.core.agent import Agent
    cls = type(name, (Agent,), {'__init__': init, '__new__': Agent.__new__})
    return cls


class SfcArray(np.ndarray):
    """
    A custom NumPy ndarray for 1D arrays that maintains the sum of its elements
    and allows the sum to be retrieved by a custom name.

    Attributes:
        total_sum (float): The sum of all elements in the array.
        sum_name (str, optional): A custom name for the total sum for retrieval.

    Methods:
        update_sum: Recalculates the total sum of the array elements.
        get: Retrieves the total sum by its custom name.
    """

    instances = []
    sorted_instances = defaultdict(lambda: defaultdict(lambda: []))

    def __new__(cls, input_array, title="Unknown", agent_name="Unknown", ref_name="Unknown", accounting=(0, 1),
                register_globally=True, suppress_warnings=False):
        """
        Create a new SumArray1D instance from a 1D input array, optionally with a named total sum.

        Parameters:
            input_array (array_like): Input array to convert into SumArray1D.
            title(str, optional): Title of the entry
            agent_name, (str, optional): Name of the agent or economic entity this array refers to.
            ref_name, (str, optional): Name of the agent or economic entity this array refers to, for consistency.
            accounting (int, optional): accounting item (default 0), 0 for assets, 1 for 1bilities, 2 for equity
            register_globally (boolean, optional): register for SfcBalanceMatrix
            suppress_warnings (boolean, optional): optional boolean switch to enable warnings (default False/off)

        Returns:
            SumArray1D: A new instance of SumArray1D initialized with input_array.
        """

        obj = np.asarray(input_array).view(cls)
        obj.title = title
        obj.name = agent_name
        obj.ref = ref_name
        obj.update_sum()
        obj.accounting = accounting

        if register_globally:
            World.use_sfc_array = True
            cls.instances.append(obj)
            cls.sorted_instances[agent_name][accounting[0]].append(obj)
            if ref_name is not None:
                cls.sorted_instances[ref_name][accounting[1]].append(obj)

            for n, name in enumerate([agent_name, ref_name]):
                if name is None:
                    continue

                if len(World()[name]) == 0:
                    cls2 = agent_subclass_generator(name)

                    if n == 0:
                        if not suppress_warnings:
                            warnings.warn(
                                "Agent type '%s' was referenced but not found in World. Creating empty dummy agents "
                                "instead." % name)
                        [cls2() for jj in range(obj.shape[n])]
                    if n == 1:
                        if not suppress_warnings:
                            warnings.warn(
                                "Agent type '%s' was referenced but not found in World. Creating empty dummy agent "
                                "instead." % name)
                        # create at le0 one but its uncertain how many are actually required...
                        cls2()

        return obj

    @classmethod
    def zeros(cls, shape, *args, **kwargs):
        x = np.zeros(shape)
        return cls.__new__(cls, x, *args, **kwargs)

    @property
    def total(self):
        return self.get_sum()

    def get_sum(self):
        return self._total_sum

    def update_sum(self):
        """ Update the total sum of the array. """
        self._total_sum = np.sum(self, axis=0)  # self.sum()

    def __array_finalize__(self, obj):
        """
        Finalize method called on creation or view c0ing to ensure metadata is copied over.

        Parameters:
            obj (SumArray1D or ndarray): Source object from which to copy attributes.
        """
        if obj is None:
            return
        self._total_sum = getattr(obj, 'total_sum', None)

    def __setitem__(self, index, value):
        """
        Override to update the total sum when items are set.

        Parameters:
            index (int or slice): Index of the element to modify.
            value (numeric): Value to set at the specified index.
        """
        old_val = self[index]
        delta_val = value - old_val

        # retrieve agents and corresponding stocks NOTE only first agent is accessed.
        # if more agents are present, use 2d or 3d SfcArray structure
        agent1 = None
        agent2 = None
        if self.name is not None and self.name != "Unknown":
            agent1 = World().get_agents_of_type(self.name)[0]
        if self.ref is not None and self.name != "Unknown":
            agent2 = World().get_agents_of_type(self.ref)[0]

        if agent1 is not None and self.accounting[0] is not None:
            if self.accounting[0] == 0:
                FlowMatrix(
                ).capital_flow_data[f"Δ {self.title}"][agent1] += delta_val
            else:
                FlowMatrix(
                ).capital_flow_data[f"Δ {self.title}"][agent1] -= delta_val
            # FlowMatrix().capital_flow_data[f"Δ {self.title}"][agent1] += delta_val

        if agent2 is not None and self.accounting[1] is not None:
            if self.accounting[1] == 0:
                FlowMatrix(
                ).capital_flow_data[f"Δ {self.title}"][agent2] += delta_val
            else:
                FlowMatrix(
                ).capital_flow_data[f"Δ {self.title}"][agent2] -= delta_val
            # FlowMatrix().capital_flow_data[f"Δ {self.title}"][agent2] += delta_val

        super().__setitem__(index, value)
        self.update_sum()


class SfcNDArray(np.ndarray):
    """
    A custom NumPy ndarray that maintains the sums of its rows and columns and allows retrieval by custom names.
    """
    def __new__(cls, input_array, title="Unknown", row_name="Unknown", col_name="Unknown", accounting=(0, 1),
                register_globally=True, suppress_warnings=False):
        obj = np.asarray(input_array).view(cls)
        obj.row_name = row_name
        obj.col_name = col_name
        obj.accounting = accounting
        obj.title = title
        obj.sums_up_to_date = False
        obj.update_sums()

        if register_globally:
            World.use_sfc_array = True
            for n, name in enumerate([row_name, col_name]):
                if len(World()[name]) == 0:
                    cls2 = agent_subclass_generator(name)
                    if not suppress_warnings:
                        warnings.warn(
                            f"Agent type '{name}' was referenced but not found in World. Creating empty dummy agents.")
                    [cls2() for _ in range(obj.shape[n])]
        return obj
    
    @classmethod
    def zeros(cls, shape, *args, **kwargs):
        x = np.zeros(shape)
        return cls.__new__(cls, x, *args, **kwargs)
    
    def update_sums(self):
        """ Update row and column sums. """
        self.row_sums = np.sum(self, axis=1)
        self.col_sums = np.sum(self, axis=0)
        self.sums_up_to_date = True

    def fetch(self, name):
        # see fetch_sums
        if not self.sums_up_to_date:
            self.update_sums()

        return self.fetch_sums(name)

    def fetch_row_sum(self):
        if not self.sums_up_to_date:
            self.update_sums()
        return self.fetch_sums(self.row_name)

    def fetch_col_sum(self):
        if not self.sums_up_to_date:
            self.update_sums()
        return self.fetch_sums(self.col_name)

    def fetch_sums(self, name):
        """
        Retrieve the sum array by its custom name.
        """
        if name == self.row_name:
            return self.row_sums
        elif name == self.col_name:
            return self.col_sums
        else:
            raise KeyError(
                f"Invalid name '{name}'. Allowed names are '{self.row_name}' and '{self.col_name}'.")

    def __setitem__(self, index, value):
        super().__setitem__(index, value)
        self.sums_up_to_date = False


class Sfc2DArray(SfcNDArray):
    """
    A specialized 2D version of SfcNDArray that maintains global registration and categorization by row/column names.
    """
    instances = []
    sorted_instances = defaultdict(lambda: defaultdict(list))

    def __new__(cls, input_array, title="Unknown", row_name="Unknown", col_name="Unknown", accounting=(0, 1),
                register_globally=True):
        input_array = np.array(input_array)
        assert len(
            input_array.shape) == 2, f"Input must be 2D, but got shape {input_array.shape}."
        obj = super().__new__(cls, input_array, title, row_name,
                              col_name, accounting, register_globally)

        if register_globally:
            cls.instances.append(obj)
            cls.sorted_instances[row_name][accounting[0]].append(obj)
            cls.sorted_instances[col_name][accounting[1]].append(obj)

        return obj

    def __setitem__(self, key, value):
        old_val = self[key]
        row_indices = resolve_index(key[0], self.shape[0])
        col_indices = resolve_index(key[1], self.shape[1])

        for r_idx in row_indices:
            for c_idx in col_indices:
                delta_val = (
                    float(value - old_val) if np.isscalar(value) else float(
                        value[r_idx][c_idx] - old_val[r_idx][c_idx])
                )

                agent_row = World().get_agents_of_type(self.row_name)[r_idx]
                agent_col = World().get_agents_of_type(self.col_name)[c_idx]

                if self.accounting[0] == 0:
                    FlowMatrix(
                    ).capital_flow_data[f"Δ {self.title}"][agent_row] += delta_val
                else:
                    FlowMatrix(
                    ).capital_flow_data[f"Δ {self.title}"][agent_row] -= delta_val

                if self.accounting[1] == 0:
                    FlowMatrix(
                    ).capital_flow_data[f"Δ {self.title}"][agent_col] += delta_val
                else:
                    FlowMatrix(
                    ).capital_flow_data[f"Δ {self.title}"][agent_col] -= delta_val
                # FlowMatrix().capital_flow_data[f"Δ {self.title}"][agent_row] += delta_val
                # FlowMatrix().capital_flow_data[f"Δ {self.title}"][agent_col] += delta_val

        super().__setitem__(key, value)


class Sfc3DArray(SfcNDArray):
    """
    A specialized 3D version of SfcNDArray with similar global registration, supporting interactions in 3 dimensions.
    """
    instances = []
    sorted_instances = defaultdict(lambda: defaultdict(list))

    def __new__(cls, input_array, title="Unknown", row_name="Unknown", col_name="Unknown", accounting=(0, 1),
                register_globally=True):
        input_array = np.array(input_array)
        assert len(
            input_array.shape) == 3, f"Input must be 3D, but got shape {input_array.shape}."
        obj = super().__new__(cls, input_array, title, row_name,
                              col_name, accounting, register_globally)

        if register_globally:
            cls.instances.append(obj)
            cls.sorted_instances[row_name][accounting[0]].append(obj)
            cls.sorted_instances[col_name][accounting[1]].append(obj)

        return obj

    def __getitem__(self, key):
        if isinstance(key, int):
            # print("getitem", super().__getitem__((0, 0, key)))
            return super().__getitem__((0, 0, key))
        else:
            return super().__getitem__(key)

    def __setitem__(self, key, value):
        if isinstance(key, int):
            row_indices, col_indices, depth_indices = [0], [0], [key]
        else:
            row_indices = resolve_index(key[0], self.shape[0])
            col_indices = resolve_index(key[1], self.shape[1])
            depth_indices = resolve_index(
                key[2], self.shape[2]) if len(key) > 2 else [0]

        for r_idx in row_indices:
            for c_idx in col_indices:
                for d_idx in depth_indices:

                    old_val = self[r_idx, c_idx, d_idx]
                    if np.isscalar(value):
                        delta_val = float(value - old_val)
                    else:
                        delta_val = float(
                            value[r_idx][c_idx][d_idx] - old_val[r_idx][c_idx][d_idx])

                    agent_row = World().get_agents_of_type(
                        self.row_name)[r_idx]
                    agent_col = World().get_agents_of_type(
                        self.col_name)[c_idx]
                    # print("change entry", agent_row, agent_col, key, value)

                    if self.accounting[0] == 0:
                        FlowMatrix(
                        ).capital_flow_data[f"Δ {self.title}"][agent_row] += delta_val
                    else:
                        FlowMatrix(
                        ).capital_flow_data[f"Δ {self.title}"][agent_row] -= delta_val

                    if self.accounting[1] == 0:
                        FlowMatrix(
                        ).capital_flow_data[f"Δ {self.title}"][agent_col] += delta_val
                    else:
                        FlowMatrix(
                        ).capital_flow_data[f"Δ {self.title}"][agent_col] -= delta_val

                    # FlowMatrix().capital_flow_data[f"Δ {self.title}"][agent_row] += delta_val
                    # FlowMatrix().capital_flow_data[f"Δ {self.title}"][agent_col] += delta_val

                    idx = (r_idx, c_idx, d_idx)
                    super().__setitem__(idx, value)


def resolve_index(index, dimension_size):
    """
    Resolves various types of indices (int, slice, or tuple) to a list of integer indices for easy iteration.
    """
    if isinstance(index, int):
        return [index]
    elif isinstance(index, slice):
        return list(range(dimension_size)[index])
    elif isinstance(index, tuple):
        indices = []
        for item in index:
            if isinstance(item, int):
                indices.append(item)
            elif isinstance(item, slice):
                indices.extend(range(dimension_size)[item])
        return indices
    else:
        raise ValueError(
            "Unsupported index type; must be int, slice, or tuple.")
