#ifndef IEC_H
#define IEC_H

// This code uses code from the Meatloaf Project:
// Meatloaf - A Commodore 64/128 multi-device emulator
// https://github.com/idolpx/meatloaf
// Copyright(C) 2020 James Johnston
//
// Meatloaf is free software : you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Meatloaf is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Meatloaf. If not, see <http://www.gnu.org/licenses/>.

//
// https://www.pagetable.com/?p=1135
// http://unusedino.de/ec64/technical/misc/c1541/romlisting.html#E85B
// https://eden.mose.org.uk/gitweb/?p=rom-reverse.git;a=blob;f=src/vic-1541-sfd.asm;hb=HEAD
// https://www.pagetable.com/docs/Inside%20Commodore%20DOS.pdf
// http://www.ffd2.com/fridge/docs/1541dis.html#E853
// http://unusedino.de/ec64/technical/aay/c1541/
// http://unusedino.de/ec64/technical/aay/c1581/
//

#include <cstdint>
#include <forward_list>
// #include <freertos/FreeRTOS.h>
// #include <freertos/queue.h>
#include <utility>
#include <string>
#include <map>
#include <queue>
#include <memory>
//#include <driver/gpio.h>
//#include "fnSystem.h"

// #include "protocol/_protocol.h"
// #include "protocol/jiffydos.h"
// #ifdef MEATLOAF_MAX
// #include "protocol/saucedos.h"
// #endif
// #ifdef PARALLEL_BUS
// #include "protocol/dolphindos.h"
// #endif

//#include <soc/gpio_reg.h>

#include "../../../include/debug.h"
#include "../../../include/cbm_defines.h"
//#include "../../../include/pinmap.h"


/**
 * @brief The command frame
 */
union cmdFrame_t
{
    struct
    {
        uint8_t device;
        uint8_t comnd;
        uint8_t aux1;
        uint8_t aux2;
        uint8_t cksum;
    };
    struct
    {
        uint32_t commanddata;
        uint8_t checksum;
    } __attribute__((packed));
};

// Return values for service:
typedef enum
{
    BUS_OFFLINE = -3, // Bus is empty
    BUS_RESET = -2,   // The bus is in a reset state (RESET line).
    BUS_ERROR = -1,   // A problem occoured, reset communication
    BUS_IDLE = 0,     // Nothing recieved of our concern
    BUS_ACTIVE = 1,   // ATN is pulled and a command byte is expected
    BUS_PROCESS = 2,  // A command is ready to be processed
} bus_state_t;

/**
 * @enum bus command
 */
typedef enum
{
    IEC_GLOBAL = 0x00,    // 0x00 + cmd (global command)
    IEC_LISTEN = 0x20,    // 0x20 + device_id (LISTEN) (0-30)
    IEC_UNLISTEN = 0x3F,  // 0x3F (UNLISTEN)
    IEC_TALK = 0x40,      // 0x40 + device_id (TALK) (0-30)
    IEC_UNTALK = 0x5F,    // 0x5F (UNTALK)
    IEC_REOPEN = 0x60,    // 0x60 + channel (OPEN CHANNEL) (0-15)
    IEC_REOPEN_JD = 0x61, // 0x61 + channel (OPEN CHANNEL) (0-15) - JIFFYDOS LOAD
    IEC_CLOSE = 0xE0,     // 0xE0 + channel (CLOSE NAMED CHANNEL) (0-15)
    IEC_OPEN = 0xF0       // 0xF0 + channel (OPEN NAMED CHANNEL) (0-15)
} bus_command_t;

typedef enum
{
    DEVICE_ERROR = -1,
    DEVICE_IDLE = 0,    // Ready and waiting
    DEVICE_ACTIVE = 1,
    DEVICE_LISTEN = 2,  // A command is recieved and data is coming to us
    DEVICE_TALK = 3,    // A command is recieved and we must talk now
    DEVICE_PROCESS = 4, // Execute device command
} device_state_t;

typedef enum {
    PROTOCOL_SERIAL,
    PROTOCOL_FAST_SERIAL,
    PROTOCOL_SAUCEDOS,
    PROTOCOL_JIFFYDOS,
    PROTOCOL_EPYXFASTLOAD,
    PROTOCOL_WARPSPEED,
    PROTOCOL_SPEEDDOS,
    PROTOCOL_DOLPHINDOS,
    PROTOCOL_WIC64,
    PROTOCOL_IEEE488
} bus_protocol_t;

//using namespace Protocol;

/**
 * @class IECData
 * @brief the IEC command data passed to devices
 */
class IECData
{
public:
    /**
     * @brief the primary command byte
     */
    uint8_t primary = 0;
    /**
     * @brief the primary device number
     */
    uint8_t device = 0;
    /**
     * @brief the secondary command byte
     */
    uint8_t secondary = 0;
    /**
     * @brief the secondary command channel
     */
    uint8_t channel = 0;
    /**
     * @brief the device command
     */
    std::string payload = "";

    /**
     * @brief clear and initialize IEC command data
     */
    void init(void)
    {
        primary = 0;
        device = 0;
        secondary = 0;
        channel = 0;
        payload = "";
    }
};

/**
 * @class Forward declaration of System Bus
 */
class systemBus;

/**
 * @class virtualDevice
 * @brief All #FujiNet devices derive from this.
 */
class virtualDevice
{
private:

protected:
    friend systemBus; /* Because we connect to it. */

    /**
     * @brief The device number (ID)
     */
    int _devnum;

    /**
     * @brief The passed in command frame, copied.
     */
    cmdFrame_t cmdFrame;

    /**
     * @brief The current device command
     */
    std::string payload;

    /**
     * @brief The current device command in raw PETSCII. Used when payload is converted to ASCII for Basic commands
     */
    std::string payloadRaw;

    /**
     * @brief pointer to the current command data
     */
    IECData commanddata;

    /**
     * @brief current device state.
     */
    device_state_t device_state;

    /**
     * @brief response queue (e.g. INPUT)
     * @deprecated remove as soon as it's out of fuji.
     */
    std::queue<std::string> response_queue;

    /**
     * @brief status override (for binary commands)
     */
    std::string status_override;

    /**
     * @brief tokenized payload
     */
    std::vector<std::string> pt;
    std::vector<uint8_t> pti;

    /**
     * @brief The status information to send back on cmd input
     * @param bw = # of bytes waiting
     * @param msg = most recent status message
     * @param connected = is most recent channel connected?
     * @param channel = channel of most recent status msg.
     */
    struct _iecStatus
    {
        uint8_t error;
        std::string msg;
        bool connected;
        int channel;
    } iecStatus;

    /**
     * @brief Get device ready to handle next phase of command.
     */
    device_state_t queue_command(const IECData &data)
    {
        commanddata = data;

        if (commanddata.primary == IEC_LISTEN)
            device_state = DEVICE_LISTEN;
        else if (commanddata.primary == IEC_TALK)
            device_state = DEVICE_TALK;

        return device_state;
    }

    /**
     * @brief All IEC devices repeatedly call this routine to fan out to other methods for each command.
     *        This is typcially implemented as a switch() statement.
     * @return new device state.
     */
    virtual device_state_t process();

    /**
     * @brief poll whether interrupt should be wiggled
     * @param c secondary channel (0-15)
     */
    virtual void poll_interrupt(unsigned char c) {}

    /**
     * @brief Dump the current IEC frame to terminal.
     */
    void dumpData();

    /**
     * @brief If response queue is empty, Return 1 if ANY receive buffer has data in it, else 0
     */
    virtual void iec_talk_command_buffer_status();

    // Optional shutdown/reboot cleanup routine
    virtual void shutdown(){};

public:
    /**
     * @brief get the IEC device Number (1-31)
     * @return The device number registered for this device
     */
    int id() { return _devnum; };

    /**
     * @brief Is this virtualDevice holding the virtual disk drive used to boot CONFIG?
     */
    bool is_config_device = false;

    /**
     * @brief is device active (turned on?)
     */
    bool device_active = true;

    /**
     * The spinlock for the ESP32 hardware timers. Used for interrupt rate limiting.
     */
//    portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

    /**
     * @brief Get the systemBus object that this virtualDevice is attached to.
     */
    systemBus get_bus();
};

/**
 * @class systemBus
 * @brief the system bus that all virtualDevices attach to.
 */
class systemBus
{
private:
    /**
     * @brief The chain of devices on the bus.
     */
    std::forward_list<virtualDevice *> _daisyChain;

    /**
     * @brief Number of devices on bus
     */
    int _num_devices = 0;

    /**
     * @brief the active device being process()'ed
     */
    virtualDevice *_activeDev = nullptr;

    /**
     * @brief is device shutting down?
     */
    bool shuttingDown = false;

    /**
     * @brief the detected bus protocol
     */
    bus_protocol_t detected_protocol = PROTOCOL_SERIAL;  // default is IEC Serial

    /**
     * @brief the active bus protocol
     */
    std::shared_ptr<std::string> protocol = nullptr;

    /**
     * @brief Switch to detected bus protocol
     */
    std::shared_ptr<std::string> selectProtocol();

    /**
     * IEC LISTEN received
     */
    void deviceListen();

    /**
     * IEC TALK requested
     */
    void deviceTalk();

    /**
     * BUS TURNAROUND (switch from listener to talker)
     */
    bool turnAround();

    /**
     * @brief called to process the next command
     */
    void process_cmd();

    /**
     * @brief called to process a queue item (such as disk swap)
     */
    void process_queue();

    /**
     * @brief called to read bus command bytes
    */
    void read_command();

    /**
     * @brief called to read bus payload bytes
    */
    void read_payload();

    /**
     * ESP timer handle for the Interrupt rate limiting timer
     */
    // esp_timer_handle_t rateTimerHandle = nullptr;

    /**
     * Timer Rate for interrupt timer
     */
    int timerRate = 100;

    /**
     * @brief Start the Interrupt rate limiting timer
     */
    void timer_start();

    /**
     * @brief Stop the Interrupt rate limiting timer
     */
    void timer_stop();

public:
    /**
     * @brief bus flags
     */
    uint16_t flags = CLEAR;

    /**
     * @brief current bus state
     */
    bus_state_t bus_state;

    /**
     * Toggled by the rate limiting timer to indicate that the SRQ interrupt should
     * be pulsed.
     */
    bool interruptSRQ = false;    

    /**
     * @brief data about current bus transaction
     */
    IECData data;

    /**
     * @brief Enabled device bits
     */
    uint32_t enabledDevices;

    /**
     * @brief called in main.cpp to set up the bus.
     */
    void setup();

    /**
     * @brief Run one iteration of the bus service loop
     */
    void service();

    /**
     * @brief Called to pulse the PROCEED interrupt, rate limited by the interrupt timer.
     */
    void assert_interrupt();

    /**
     * @brief Release the bus lines, we're done.
     */
    void releaseLines(bool wait = false);

    /**
     * @brief Set 2bit fast loader pair timing
     * @param set Send 's', Receive 'r'
     * @param p1 Pair 1
     * @param p2 Pair 2
     * @param p3 Pair 3
     * @param p4 Pair 4
     */
    void setBitTiming(std::string set, int p1 = 0, int p2 = 0, int p3 = 0, int p4 = 0);

    /**
     * @brief send single byte
     * @param c byte to send
     * @param eoi Send EOI?
     * @return true on success, false on error
    */
    bool sendByte(const char c, bool eoi = false);

    /**
     * @brief Send bytes to bus
     * @param buf buffer to send
     * @param len length of buffer
     * @param eoi Send EOI?
     * @return true on success, false on error
     */
    bool sendBytes(const char *buf, size_t len, bool eoi = false);

    /**
     * @brief Send string to bus
     * @param s std::string to send
     * @param eoi Send EOI?
     * @return true on success, false on error
     */
    bool sendBytes(std::string s, bool eoi = false);

    /**
     * @brief Receive Byte from bus
     * @return Byte received from bus, or -1 for error
     */
    int8_t receiveByte();

    /**
     * @brief Receive String from bus
     * @return std::string received from bus
     */
    std::string receiveBytes();

    /**
     * @brief called in response to RESET pin being asserted.
     */
    void reset_all_our_devices();

    /**
     * @brief called from main shutdown to clean up the device.
     */
    void shutdown();

    /**
     * @brief Return number of devices on bus.
     * @return # of devices on bus.
     */
    int numDevices() { return _num_devices; };

    /**
     * @brief Add device to bus.
     * @param pDevice Pointer to virtualDevice
     * @param device_id The ID to assign to virtualDevice
     */
    void addDevice(virtualDevice *pDevice, int device_id);

    /**
     * @brief Remove device from bus
     * @param pDevice pointer to virtualDevice
     */
    void remDevice(virtualDevice *pDevice);

    /**
     * @brief Check if device is enabled
     * @param deviceNumber The device ID to check
     */
    bool isDeviceEnabled ( const uint8_t device_id );

    /**
     * @brief Return pointer to device given ID
     * @param device_id ID of device to return.
     * @return pointer to virtualDevice
     */
    virtualDevice *deviceById(int device_id);

    /**
     * @brief Change ID of a particular virtualDevice
     * @param pDevice pointer to virtualDevice
     * @param device_id new device ID
     */
    void changeDeviceId(virtualDevice *pDevice, int device_id);

    /**
     * @brief Are we shutting down?
     * @return value of shuttingDown
     */
    bool getShuttingDown() { return shuttingDown; }

    /**
     * @brief signal to bus that we timed out.
     */
    void senderTimeout();


    void pull ( int _pin );
    void release ( int _pin );
    bool status ( int _pin );
    //int status();
};
/**
 * @brief Return
 */
extern systemBus IEC;

#endif /* IEC_H */
