#ifndef _I2C_BITBANG_H
#define _I2C_BITBANG_H

#include <stdint.h>
#include <stdio.h>

#include "ch32fun.h"

#ifndef I2C_SDA_PIN
#define I2C_SDA_PIN PA1
#endif

#ifndef I2C_SCL_PIN
#define I2C_SCL_PIN PA0
#endif

// decrease to 1 or 0 for Fast Mode (400kHz)
#ifndef I2C_DELAY_US
#define I2C_DELAY_US 3
#endif

#define I2C_ACK 0
#define I2C_NACK 1

#define SCL_HIGH() funDigitalWrite(I2C_SCL_PIN, 1)
#define SCL_LOW() funDigitalWrite(I2C_SCL_PIN, 0)
#define SDA_HIGH() funDigitalWrite(I2C_SDA_PIN, 1)
#define SDA_LOW() funDigitalWrite(I2C_SDA_PIN, 0)
#define SDA_READ() funDigitalRead(I2C_SDA_PIN)

static inline void i2c_delay(void) { Delay_Us(I2C_DELAY_US); }

static inline void i2c_init(void) {
  funGpioInitAll();

  funPinMode(I2C_SDA_PIN, GPIO_CFGLR_OUT_10Mhz_OD);
  funPinMode(I2C_SCL_PIN, GPIO_CFGLR_OUT_10Mhz_OD);

  // idle bus state
  SDA_HIGH();
  SCL_HIGH();
}

static inline void i2c_start(void) {
  SDA_HIGH();
  SCL_HIGH();
  i2c_delay();

  SDA_LOW();
  i2c_delay();
  SCL_LOW();
}

static inline void i2c_stop(void) {
  SDA_LOW();
  i2c_delay();
  SCL_HIGH();
  i2c_delay();

  SDA_HIGH();
  i2c_delay();
}

// returns 0 (I2C_ACK) if acknowledged, 1 (I2C_NACK) if not
static inline uint8_t i2c_write_byte(uint8_t byte) {
  for (uint8_t i = 0; i < 8; i++) {
    if (byte & 0x80) {
      SDA_HIGH();
    } else {
      SDA_LOW();
    }

    byte <<= 1;
    i2c_delay();
    SCL_HIGH();
    i2c_delay();
    SCL_LOW();
    i2c_delay();
  }

  // ACK/NACK processing
  SDA_HIGH();  // release SDA line for slave
  i2c_delay();
  SCL_HIGH();
  i2c_delay();

  uint8_t ack = SDA_READ();

  SCL_LOW();
  i2c_delay();

  return ack;
}

// I2C_ACK (0) to request more data, I2C_NACK (1) to end tx
static inline uint8_t i2c_read_byte(uint8_t ack_control) {
  uint8_t byte = 0;

  SDA_HIGH();

  for (uint8_t i = 0; i < 8; i++) {
    byte <<= 1;
    i2c_delay();
    SCL_HIGH();  // slave writes data here
    i2c_delay();

    if (SDA_READ()) {
      byte |= 1;
    }

    SCL_LOW();
  }

  // send ACK/NACK to slave
  if (ack_control == I2C_ACK) {
    SDA_LOW();
  } else {
    SDA_HIGH();
  }

  i2c_delay();
  SCL_HIGH();
  i2c_delay();
  SCL_LOW();

  SDA_HIGH();  // release bus
  i2c_delay();

  return byte;
}

static inline void i2c_scan(void) {
  printf("--- I2C Scan Start ---\n");
  uint8_t found = 0;

  for (uint8_t addr = 0x08; addr < 0x78; addr++) {
    i2c_start();

    // try to write to the address
    uint8_t ack = i2c_write_byte((addr << 1) | 0);

    i2c_stop();

    if (ack == I2C_ACK) {
      printf("Device found at 0x%02X\n", addr);
      found++;
      Delay_Ms(1);
    }
  }

  if (found == 0) {
    printf("No I2C devices found.\n");
  }
  printf("--- I2C Scan End ---\n");
}

#endif  // _I2C_BITBANG_H