#ifndef MLOGIC_BOX_HPP_
#define MLOGIC_BOX_HPP_

#include <Machine/IDataMachine.hpp>
#include <Machine/Machine.hpp>
#include <Serialize/Utils.hpp>

class MLogicBox final :
    public Machine,
    public IDataMachine
{
public:
    enum class Operator : uint8_t
    {
        AlwaysOff = 0,
        AlwaysOn = 1,
        Copy = 2,
        Not = 3,
        And = 4,
        Or = 5,
        Xor = 6,
    };

    struct Operation
    {
        DataLink::Channel left;
        Operator op;
        DataLink::Channel right;

        bool operator==(const Operation &other);
    };

private:
    /**
     * Since there are 4 data channels, there are 16 possible input states.
     * For each state, we need 4 bits for the output state, so we pack two a single
     * byte, thus we need 8 bytes in total for the lookup table. The lookup table is
     * not serialized into the machine state; it is generated from the operation list.
     */
    std::array<uint8_t, 8> lookupTable = {};

    /**
     * Maps each output channel to the operation that computes it.
     */
    std::array<Operation, 4> channelOps = {};

    bool computeOutput(bool a, Operator op, bool b);
    uint8_t computeCombinedOutput(uint8_t input);
    void updateLookupTable();
    bool isChannelOpListSame(const std::array<Operation, 4> &ops);

public:
    MLogicBox(LocalWorld &world, glm::ivec3 pos);

    virtual void serializeState(std::vector<uint8_t> &outBytes) override;
    virtual void deserializeState(const uint8_t *data, size_t size) override;
    virtual void saveMainConfig(MachineConfig &config) override;
    virtual void loadMainConfig(const MachineConfig &config, bool consumed) override;
    virtual Side getDataInputSide() override;
    virtual Side getDataOutputSide() override;
    virtual DataLink::ChannelBitmap getActiveChannels() override;

    friend class LogicBoxController;
};

DERIVE_STATIC_CAST_SERIALIZER(MLogicBox::Operator, uint8_t);
DERIVE_STRUCT_SERIALIZER(MLogicBox::Operation, self.left, self.op, self.right);

#endif