// SPDX-FileCopyrightText: 2023 Kushview, LLC
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include <element/juce/data_structures.hpp>

namespace element {

class MatrixState
{
public:
    MatrixState()
    {
        numRows = numColumns = 0;
    }

    MatrixState (const int rows, const int cols)
    {
        numRows = rows;
        numColumns = cols;
        jassert (rows >= 0 && cols >= 0);
        toggled.setRange (0, numRows * numColumns, false);
    }

    virtual ~MatrixState() {}

    inline const bool isEmpty() const { return numRows <= 0 && numColumns <= 0; }
    inline const bool isNotEmpty() const { return ! isEmpty(); }
    inline const bool isValid (int row, int column)
    {
        return juce::isPositiveAndBelow (row, numRows) || juce::isPositiveAndBelow (column, numColumns);
    }

    inline int getIndexForCell (int row, int column) const { return (row * numColumns) + column; }
    inline int getNumRows() const { return numRows; }
    inline int getNumColumns() const { return numColumns; }

    inline void connect (int row, int column)
    {
        if (isValid (row, column))
        {
            const int index = getIndexForCell (row, column);
            toggled.setBit (index, true);
        }
    }

    inline void set (int r, int c, bool on)
    {
        if (isValid (r, c))
        {
            const int index = getIndexForCell (r, c);
            toggled.setBit (index, on);
        }
    }

    void setFrom (const MatrixState&);

    inline void disconnect (int row, int column)
    {
        if (! isValid (row, column))
            return;
        const int index = getIndexForCell (row, column);
        toggled.setBit (index, false);
    }

    inline bool connected (const int row, const int col) const
    {
        return toggled[getIndexForCell (row, col)];
    }

    inline bool isCellToggled (int r, int c) const { return connected (r, c); }
    inline bool toggleCell (int row, int column)
    {
        if (isValid (row, column))
        {
            const int index = getIndexForCell (row, column);
            toggled.setBit (index, ! toggled[index]);
            return true;
        }

        return false;
    }

    inline bool connectedAtIndex (const int index) const { return toggled[index]; }

#if JUCE_MODULE_AVAILABLE_juce_data_structures
    inline juce::ValueTree createValueTree (const juce::String& type = "matrix") const
    {
        juce::ValueTree tree (juce::Identifier::isValidIdentifier (type) ? type : "matrix");
        tree.setProperty ("numRows", numRows, nullptr);
        tree.setProperty ("numColumns", numColumns, nullptr);
        tree.setProperty ("toggled", toggled.toString (2), nullptr);
        return tree;
    }

    inline void restoreFromValueTree (const juce::ValueTree& tree)
    {
        numRows = tree.getProperty ("numRows", 0);
        numColumns = tree.getProperty ("numColumns", 0);
        toggled.parseString (tree.getProperty ("toggled").toString(), 2);
    }
#endif

    /** Resize the matrix to the specified rows and column sizes */
    void resize (int newNumRows, int newNumColumns, bool retain = false);

    /** Returns true if the matrices are the same size */
    inline bool sameSizeAs (const MatrixState& o) const
    {
        return this->numRows == o.numRows && this->numColumns == o.numColumns;
    }

    MatrixState (const MatrixState& o)
    {
        this->operator= (o);
    }

    MatrixState& operator= (const MatrixState& o)
    {
        this->numRows = o.numRows;
        this->numColumns = o.numColumns;
        this->toggled = o.toggled;
        return *this;
    }

    const bool operator== (const MatrixState& o) const
    {
        return this->sameSizeAs (o) && this->toggled == o.toggled;
    }

private:
    juce::BigInteger toggled;
    int numRows, numColumns;
};

} // namespace element
