#pragma once

#include "CircuitElement.h"
#include "TruthTable.h"
#include "Wire.h"

#include <memory>
#include <vector>

class LogicGate : public CircuitElement
{
public:
    enum class GateType
    {
        NOT,
        AND,
        OR,
        XOR,
        UNKNOWN
    };
    virtual ~LogicGate() = default;

    virtual std::size_t getNumInputs() const = 0;

    virtual std::size_t getNumOutputs() const = 0;

    virtual Wire* getInput(std::size_t idx) const = 0;

    virtual Wire* getOutput(std::size_t idx) const = 0;

    virtual const TruthTable& getTruthTable() = 0;

    virtual GateType getGateType() const = 0;

    Type getType() const override
    {
        return Type::LOGIC_GATE;
    }
};

class NInMOutLogicGate : public LogicGate
{
public:
    NInMOutLogicGate(std::size_t numIn, std::size_t numOut, std::vector<Wire*> inputs = {}, std::vector<Wire*> outputs = {});

    virtual ~NInMOutLogicGate() = default;

    std::size_t getNumInputs() const override;

    std::size_t getNumOutputs() const override;

    Wire* getInput(std::size_t idx) const override;

    Wire* getOutput(std::size_t idx) const override;

    void setAtInput(std::size_t idx, Wire* value);

    void setAtOutput(std::size_t idx, Wire* value);

private:
    std::size_t mNumIn{ 1 };
    std::size_t mNumOut{ 1 };

    std::vector<Wire*> mInputs;
    std::vector<Wire*> mOutputs;
};

class TwoInOneOutLogicGate : public NInMOutLogicGate
{
public:
    TwoInOneOutLogicGate(Wire* input0 = nullptr, Wire* input1 = nullptr, Wire* output = nullptr);

    void setInput0(Wire* input);

    void setInput1(Wire* input);

    void setOutput(Wire* output);
};