#ifndef LIBTRELLIS_CRAM_HPP
#define LIBTRELLIS_CRAM_HPP

#include <cstdint>
#include <memory>
#include <vector>

using namespace std;
namespace Trellis {
// This represents the difference between two CRAMViews
struct ChangedBit {
    int frame;
    int bit;
    int delta; // -1, 0 or +1 (false-true, equal, true-false)
    inline bool operator==(const ChangedBit &other) {
        return (frame == other.frame) && (bit == other.bit) && (delta == other.delta);
    }
};

typedef vector<ChangedBit> CRAMDelta;

// This represents a view into the configuration memory, typically used to represent a tile
class CRAMView {
public:
    // Access a bit inside the CRAM view by frame and bit offset within the view
    char &bit(int frame, int bit) const;

    // Primarily for Python use
    bool get_bit(int frame, int biti) const;

    void set_bit(int frame, int biti, bool value);

    // Return the number of frames inside the view
    int frames() const;

    // Return the number of bits inside the view
    int bits() const;

    // Clear the CRAM region
    void clear();

    friend CRAMDelta operator-(const CRAMView &a, const CRAMView &b);

private:
    // Private constructor, CRAM::make_view should always be used
    CRAMView(shared_ptr<vector<vector<char>>> data, int frame_offset, int bit_offset, int frame_count, int bit_count);

    int frame_offset;
    int bit_offset;
    int frame_count;
    int bit_count;

    friend class CRAM;

    shared_ptr<vector<vector<char>>> cram_data;
};

CRAMDelta operator-(const CRAMView &a, const CRAMView &b);

// This represents the chip configuration RAM, and allows views of it to be made (for tile accesses)
// N.B. all accesses are in the format (frame, bit)
class CRAM {
public:
    // Construct empty CRAM given size
    CRAM(int frames, int bits);

    // Access a bit in the CRAM given frame and bit offset
    char &bit(int frame, int bit) const;

    // Primarily for Python use
    bool get_bit(int frame, int biti) const;

    void set_bit(int frame, int biti, bool value);

    // Return number of frames in CRAM
    int frames() const;

    // Return number of bits per frame in CRAM
    int bits() const;

    // Make a view to the CRAM given frame and bit offset; and frames and bits per frame in the view
    CRAMView make_view(int frame_offset, int bit_offset, int frame_count, int bit_count);

    // Using a shared_ptr so views are not invalidated even if the CRAM itself is deleted
    // A vector of type char is used as the optimisations in vector<bool> are not worth the loss of bool& etc
    shared_ptr<vector<vector<char>>> data;
};
}
#endif //LIBTRELLIS_CRAM_HPP
