#include "common.h"
#include "device/port-io.h"

#define PORT_IO_SPACE_MAX 65536
#define NR_MAP 8

/* "+ 3" is for hacking, see pio_read() below */
static uint8_t pio_space[PORT_IO_SPACE_MAX + 3];

typedef struct {
  ioaddr_t low;
  ioaddr_t high;
  pio_callback_t callback;
} PIO_t;

static PIO_t maps[NR_MAP];
static int nr_map = 0;

static void pio_callback(ioaddr_t addr, int len, bool is_write) {
  int i;
  for (i = 0; i < nr_map; i ++) {
    if (addr >= maps[i].low && addr + len - 1 <= maps[i].high) {
      if (maps[i].callback != NULL) {
        maps[i].callback(addr, len, is_write);
      }
      return;
    }
  }
}

/* device interface */
void* add_pio_map(ioaddr_t addr, int len, pio_callback_t callback) {
  assert(nr_map < NR_MAP);
  assert(addr + len <= PORT_IO_SPACE_MAX);
  maps[nr_map].low = addr;
  maps[nr_map].high = addr + len - 1;
  maps[nr_map].callback = callback;
  nr_map ++;
  return pio_space + addr;
}

template <size_t Bytes>
uint32_t pio_read_common(ioaddr_t addr) {
  static_assert(Bytes == 4 || Bytes == 2 || Bytes == 1);
  using result_type = std::conditional_t<Bytes == 4, uint32_t, std::conditional_t<Bytes == 2, uint16_t, uint8_t>>;

  assert(addr + Bytes - 1 < PORT_IO_SPACE_MAX);
  pio_callback(addr, Bytes, false);		// prepare data to read
  return *(result_type *)(pio_space + addr);
}

template <size_t Bytes>
void pio_write_common(ioaddr_t addr, uint32_t data) {
  static_assert(Bytes == 4 || Bytes == 2 || Bytes == 1);
  using result_type = std::conditional_t<Bytes == 4, uint32_t, std::conditional_t<Bytes == 2, uint16_t, uint8_t>>;

  assert(addr + Bytes - 1 < PORT_IO_SPACE_MAX);
  *(result_type *)(pio_space + addr) = data;
  pio_callback(addr, Bytes, true);
}


template uint32_t pio_read_common<4>(ioaddr_t addr);
template uint32_t pio_read_common<2>(ioaddr_t addr);
template uint32_t pio_read_common<1>(ioaddr_t addr);
template void pio_write_common<4>(ioaddr_t addr, uint32_t data);
template void pio_write_common<2>(ioaddr_t addr, uint32_t data);
template void pio_write_common<1>(ioaddr_t addr, uint32_t data);
