#include <Wire.h>

#define FIFO_RX_REGISTER            0x00
#define FIFO_TX_REGISTER            0x00
#define FIFO_TX_AVAILABLE_REGISTER  0x01
#define FIFO_RX_PENDING_REGISTER    0x02
#define RESET_REGISTER              0x7b

///////////////////////////////////////////////////////////////////////////////////////////////////
class HardwareSerialPIC {
  public:

    enum BaudRate
    {
      BaudRate_300  = 10,
      BaudRate_1200 = 11,
      BaudRate_2400 = 12,
      BaudRate_4800 = 13,
      BaudRate_9600 = 14,
      BaudRate_19200  = 15,
      BaudRate_38400  = 16,
      BaudRate_57600  = 17,
      BaudRate_74880  = 18,
      BaudRate_115200 = 19,
      BaudRate_230400 = 20,
      BaudRate_250000 = 21,
    };

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void begin(uint8_t address, uint8_t baudrate)
    {

      i2cAddr = address;
      Wire.begin();

      // Reset
      Wire.beginTransmission(i2cAddr);
      Wire.write(RESET_REGISTER);
      Wire.endTransmission();

      delay(100);

      // Set baudrate
      setBaudrate(baudrate);
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void setBaudrate(uint8_t baud)
    {
      Wire.beginTransmission(i2cAddr);
      Wire.write(baud);
      Wire.endTransmission();
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    uint8_t available(void)
    {
      Wire.beginTransmission(i2cAddr);
      Wire.write(FIFO_RX_PENDING_REGISTER);
      if (Wire.endTransmission()) {
        return 0;
      }

      Wire.requestFrom((uint8_t) i2cAddr, (uint8_t) 2);
      if (Wire.available() != 2) {
        return 0;
      }

      uint16_t ret = Wire.read();
      ret |= Wire.read() << 8;

      return ret;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    uint8_t read(void)
    {
      Wire.beginTransmission(i2cAddr);
      Wire.write(FIFO_RX_REGISTER);
      if (Wire.endTransmission()) {
        return 0;
      }

      Wire.requestFrom((uint8_t) i2cAddr, (uint8_t) 1);
      if (Wire.available() != 1) {
      }

      return Wire.read();
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    uint8_t write(const char* value)
    {
      Wire.beginTransmission(i2cAddr);
      Wire.write(FIFO_TX_REGISTER);
      Wire.write(*value);
      while (*value++ != '\n')
      {
        Wire.write(*value);
      }
      if (Wire.endTransmission()) {
        return 0;
      }
      return 1;
    }

    uint8_t write(uint8_t value)
    {
      Wire.beginTransmission(i2cAddr);
      Wire.write(FIFO_TX_REGISTER);
      Wire.write(value);

      if (Wire.endTransmission()) {
        return 0;
      }
      return 1;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
  private:
    uint8_t i2cAddr;

};
