/**
    Header file for direct Ethernet frame access to the ENC28J60 controller
    @file enc28j60.h
*/

/*
    Copyright (c) 2012-2013, Thingsquare, http://www.thingsquare.com/.
    All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:
    1. Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    2. Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    3. Neither the name of the copyright holder nor the names of its
      contributors may be used to endorse or promote products derived
      from this software without specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
    FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
    COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
    STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
    OF THE POSSIBILITY OF SUCH DAMAGE.

*/

// original sources: https://github.com/njh/EtherSia/tree/master/src/enc28j60.h

#ifndef ENC28J60_H
#define ENC28J60_H

#include <SPI.h>

/**
    Send and receive Ethernet frames directly using a ENC28J60 controller.
*/
class ENC28J60
{
public:
    /**
        Constructor that uses the default hardware SPI pins
        @param cs the Arduino Chip Select / Slave Select pin (default 10 on Uno)
    */
    ENC28J60(int8_t cs = SS, SPIClass& spi = SPI, int8_t intr = -1);

    /**
        Initialise the Ethernet controller
        Must be called before sending or receiving Ethernet frames

        @param address the local MAC address for the Ethernet interface
        @return Returns true if setting up the Ethernet interface was successful
    */
    boolean begin(const uint8_t* address);

    /**
        Send an Ethernet frame
        @param data a pointer to the data to send
        @param datalen the length of the data in the packet
        @return the number of bytes transmitted
    */
    virtual uint16_t sendFrame(const uint8_t* data, uint16_t datalen);

    /**
        Read an Ethernet frame
        @param buffer a pointer to a buffer to write the packet to
        @param bufsize the available space in the buffer
        @return the length of the received packet
               or 0 if no packet was received
    */
    virtual uint16_t readFrame(uint8_t* buffer, uint16_t bufsize);

    /**
        Check physical link
        @return true when physical link is up
    */
    bool isLinked();

    /**
        Report whether ::isLinked() API is implemented
        @return true when ::isLinked() API is implemented
    */
    constexpr bool isLinkDetectable() const
    {
        return true;
    }

protected:
    static constexpr bool interruptIsPossible()
    {
        return false;
    }

    /**
        Read an Ethernet frame size
        @return the length of data do receive
               or 0 if no frame was received
    */
    uint16_t readFrameSize();

    /**
        discard an Ethernet frame
        @param framesize readFrameSize()'s result
    */
    void discardFrame(uint16_t framesize);

    /**
        Read an Ethernet frame data
           readFrameSize() must be called first,
           its result must be passed into framesize parameter
        @param buffer a pointer to a buffer to write the frame to
        @param framesize readFrameSize()'s result
        @return the length of the received frame
               or 0 if a problem occurred
    */
    uint16_t readFrameData(uint8_t* frame, uint16_t framesize);

private:
    uint8_t is_mac_mii_reg(uint8_t reg);
    uint8_t readreg(uint8_t reg);
    void    writereg(uint8_t reg, uint8_t data);
    void    setregbitfield(uint8_t reg, uint8_t mask);
    void    clearregbitfield(uint8_t reg, uint8_t mask);
    void    setregbank(uint8_t new_bank);
    void    writedata(const uint8_t* data, int datalen);
    void    writedatabyte(uint8_t byte);
    int     readdata(uint8_t* buf, int len);
    uint8_t readdatabyte(void);
    void    softreset(void);
    uint8_t readrev(void);
    bool    reset(void);

    void    enc28j60_arch_spi_init(void);
    uint8_t enc28j60_arch_spi_write(uint8_t data);
    uint8_t enc28j60_arch_spi_read(void);
    void    enc28j60_arch_spi_select(void);
    void    enc28j60_arch_spi_deselect(void);

    // Previously defined in contiki/core/sys/clock.h
    void clock_delay_usec(uint16_t dt);

    uint16_t phyread(uint8_t reg);
    void     phywrite(uint8_t reg, uint16_t value);

    uint8_t   _bank;
    int8_t    _cs;
    SPIClass& _spi;

    const uint8_t* _localMac;

    /* readFrame*() state */
    uint16_t _next, _len;
};

#endif /* ENC28J60_H */
