#include <systemc>
#include <queue>

using namespace sc_core;
using namespace sc_dt;
using namespace std;

template <typename element>
class fifo_write_if : virtual public sc_interface {
public:
    virtual void write(const element&) = 0;
};

template <typename element>
class fifo_read_if : virtual public sc_interface {
public:
    virtual element read() = 0;
    virtual int num_available() const = 0;
};

template <typename element>
class fifo: public sc_channel, public fifo_write_if<element>, public fifo_read_if<element> {
private:
    priority_queue<element> data;
    const int max_elements;
    sc_event write_event, read_event;
    bool empty() const {
        return data.size() == 0;
    }
    bool full() const {
        return data.size() == max_elements;
    }
public:
    fifo(sc_module_name name, int n): sc_channel(name), max_elements(n) {
    }
    virtual ~fifo() {
    }
    virtual void write(const element& c) override {
        if (full()) {
            wait(read_event);
        }
        data.push(c);
        write_event.notify();
    }
    virtual element read() override {
        if (empty()) {
            wait(write_event);
        }
        element c = data.top();
        data.pop();
        read_event.notify();
        return c;
    }
    virtual int num_available() const override {
        return data.size();
    }
};

SC_MODULE(Producer) {
    sc_port<fifo_write_if<char>> out;
    SC_CTOR(Producer) {
        SC_THREAD(main);
    }
private:
    void main() {
        for (char c = 'A'; c <= 'Z';  ++c) {
            wait(5, SC_NS);
            out->write(c);
        }
    }
};

SC_MODULE(Consumer) {
    sc_port<fifo_read_if<char>> in;
    SC_CTOR(Consumer) {
        SC_THREAD(main);
    }
private:
    void main() {
        char c;
        for (int i = 0; i < 26; ++i) {
            wait(6, SC_NS); // the consumer is a bit slower than the producer
            c = in->read();
            cout << "Read: " << c << " number of characters in buffer: " << in->num_available() << endl;
        };
    }
};

SC_MODULE(Top) {
    SC_CTOR(Top): consumer("consumer"), producer("producer"), fifo("fifo", 4) {
        producer.out(fifo);
        consumer.in(fifo);
    }
private:
    fifo<char> fifo;
    Producer producer;
    Consumer consumer;
};

int sc_main(int argc, char *argv[]) {
    Top top("top");
    sc_start();
    cin.get();
    return 0;
}
