#ifndef  _WIN32

#include <error.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>

#include "utils/Util.hpp"
#include "utils/UtilTime64.hpp"
#include "drivers/linux/pci17x4/pci17x4api.hpp"

#define pci17x4_reg16to32(rval)					((rval) | ((rval) << 16))
#define pci17x4_reg8to16(rval)					((rval) | ((rval) << 8))
#define pci17x4_reg8to32(rval)					(pci17x4_reg16to32(pci17x4_reg8to16(rval)))

pci17x4::~pci17x4()
{
  if(_dmaBuffer) {
    munmap(_dmaBuffer, bufferSize());
  }
  close(_fd);
}

pci17x4::pci17x4(int fd)
{
  memset(this, 0, sizeof(*this));
  _fd = fd;
  _getStatus();
  _rangeBits = _inw(PCI17X4_RANGE_REG16);
}

pci17x4 *
pci17x4::open(const char *devname)
{
  int fd = ::open(devname, O_RDWR);
  if(fd >= 0) {
    pci17x4_dmastatus_t status;
    if(ioctl(fd, IOCTL_PCI17X4_DMA_STATUS, &status) >= 0) {
      return (new pci17x4(fd));
    }
    close(fd);
  }
  return (NULL);
}

pci17x4 *
pci17x4::open(int number, bool ul, bool ex)
{
  char name[32];
  sprintf(name, "/dev/pci17%s%d", (ex ? "44e" : ul ? "14ul" : "14u"), number);
  return (open(name));
}

int
pci17x4::_ioctl(int cmd, void *arg)
{
  int result = ioctl(_fd, cmd, arg);
  _dmaStatus.errio |= (result < 0);
#ifdef DEBUG
  if(result < 0) {
    perror("pci17x4: ");
  }
#endif
  return (result);
}

void
pci17x4::_getStatus()
{
  uint16_t euser = _dmaStatus.erruser;
  _ioctl(IOCTL_PCI17X4_DMA_STATUS, &_dmaStatus);
  _dmaStatus.erruser = euser;
}

uint32_t
pci17x4::_inb(int port, bool immediate)
{
  pci17x4_register_t regval = { (uint16_t)port, 8, (uint8_t)(PCI17X4_IOCTL_REGISTER_READ | (immediate ? PCI17X4_IOCTL_REGISTER_IMMEDIATE : 0)), 0 };
  return ((_ioctl(IOCTL_PCI17X4_REGISTER_IO, &regval) < 0) ? (uint32_t)(-1) : regval.value);
}

uint32_t
pci17x4::_inw(int port, bool immediate)
{
  pci17x4_register_t regval = { (uint16_t)port, 16, (uint8_t)(PCI17X4_IOCTL_REGISTER_READ | (immediate ? PCI17X4_IOCTL_REGISTER_IMMEDIATE : 0)), 0 };
  return ((_ioctl(IOCTL_PCI17X4_REGISTER_IO, &regval) < 0) ? (uint32_t)(-1) : regval.value);
}

uint32_t
pci17x4::_inl(int port, bool immediate)
{
  pci17x4_register_t regval = { (uint16_t)port, 32, (uint8_t)(PCI17X4_IOCTL_REGISTER_READ | (immediate ? PCI17X4_IOCTL_REGISTER_IMMEDIATE : 0)), 0 };
  return ((_ioctl(IOCTL_PCI17X4_REGISTER_IO, &regval) < 0) ? (uint32_t)(-1) : regval.value);
}

bool
pci17x4::_outb(int port, uint32_t data, bool immediate)
{
  pci17x4_register_t regval = { (uint16_t)port, 8, (uint8_t)(PCI17X4_IOCTL_REGISTER_WRITE | (immediate ? PCI17X4_IOCTL_REGISTER_IMMEDIATE : 0)), data };
  return (_ioctl(IOCTL_PCI17X4_REGISTER_IO, &regval) >= 0);
}

bool
pci17x4::_outw(int port, uint32_t data, bool immediate)
{
  pci17x4_register_t regval = { (uint16_t)port, 16, (uint8_t)(PCI17X4_IOCTL_REGISTER_WRITE | (immediate ? PCI17X4_IOCTL_REGISTER_IMMEDIATE : 0)), data };
  return (_ioctl(IOCTL_PCI17X4_REGISTER_IO, &regval) >= 0);
}

bool
pci17x4::_outl(int port, uint32_t data, bool immediate)
{
  pci17x4_register_t regval = { (uint16_t)port, 32, (uint8_t)(PCI17X4_IOCTL_REGISTER_WRITE | (immediate ? PCI17X4_IOCTL_REGISTER_IMMEDIATE : 0)), data };
  return (_ioctl(IOCTL_PCI17X4_REGISTER_IO, &regval) >= 0);
}

double
pci17x4::sampleRate()
{
  uint32_t divisor = (_inw(PCI17X4_CLOCK_REG16) & PCI17X4_CLOCK_DIVMASK) + PCI17X4_CLOCK_DIVBIAS;
  uint32_t baseclock = isUlBoard() ? PCI1714_CLOCKRATE_UL : PCI17X4_CLOCKRATE;
  return ((double)baseclock / (double)divisor);
}

bool
pci17x4::sampleRate(double rate)
{
  if(Util::inRange(rate, minSampleRate(), maxSampleRate())) {
    uint32_t baseclock = isUlBoard() ? PCI1714_CLOCKRATE_UL : PCI17X4_CLOCKRATE;
    uint32_t divisor = Util::clip((int)(((double)baseclock / rate) + 0.5), (int)PCI17X4_CLOCK_MINDIV, (int)PCI17X4_CLOCK_MAXDIV);
    uint32_t clockbits = _inw(PCI17X4_CLOCK_REG16) & (~PCI17X4_CLOCK_DIVMASK);
    return (_outw(PCI17X4_CLOCK_REG16, (clockbits | (divisor - PCI17X4_CLOCK_DIVBIAS))));
  }
  return (false);
}

bool
pci17x4::configure(unsigned bufferscans, unsigned blockscans, unsigned toutms, bool usetrigger)
{
  assert(blockscans && ((bufferscans % blockscans) == 0));
  if(blockscans && ((bufferscans % blockscans) == 0)) {
    unsigned log2blk = Util::log2(blockscans * sizeof(datascan_t));
    assert((1U << log2blk) == (blockscans * sizeof(datascan_t)));
    if((1U << log2blk) == (blockscans * sizeof(datascan_t))) {
      pci17x4_dmasetup setup = { (bufferscans / blockscans), (uint8_t)log2blk, usetrigger, (uint16_t)toutms };
      if(_dmaBuffer) {
        munmap(_dmaBuffer, bufferSize());
        _dmaBuffer = NULL;
      }
      if(_ioctl(IOCTL_PCI17X4_DMA_SETUP, &setup) >= 0) {
        _getStatus();
        _dmaBuffer = (datascan_t *)mmap(NULL, bufferSize(), (PROT_READ | PROT_WRITE), MAP_SHARED, _fd, 0);
        if(_dmaBuffer) {
          return (true);
        }
      }
    }
  }
  return (false);
}

pci17x4::range_t
pci17x4::range(unsigned ch)
{
  if(ch < NUMCHANNELS) {
    return ((range_t)((_rangeBits >> (2 * ch)) & 3));
  }
  return ((range_t)(-1));
}

bool
pci17x4::range(unsigned ch, pci17x4::range_t range)
{
  if((ch < NUMCHANNELS) && Util::inRange(range, RANGE_5_0V, RANGE_0_5V)) {
    _rangeBits &= (~(3 << (2 * ch)));
    _rangeBits |= (range << (2 * ch));
    return (_outw(PCI17X4_RANGE_REG16, _rangeBits));
  }
  return (false);
}

bool
pci17x4::start()
{
  if(_dmaBuffer && !_dmaStatus.error) {
    /* enable all channels */
    _outw(PCI17X4_ADCENABLE_REG16, PCI17X4_ADCENABLE_ALL);
    /* turn off the clock */
    _outw(PCI17X4_CLOCK_REG16, ((_inw(PCI17X4_CLOCK_REG16) & PCI17X4_CLOCK_DIVMASK) | PCI17X4_CLOCK_SRCEXT0));
    /* set pacer trigger mode and select digital trigger input */
    _outw(PCI17X4_TRIGGER_REG16, (PCI17X4_TRIGGER_MODEPACER | PCI17X4_TRIGGER_SRCEXT));
    /* initialize and reset DMA trigger counter */
    _outw(PCI17X4_DMACOUNTER_REG16, 0xffff);
    _outw(PCI17X4_DMACRESET_REG16);
    _outw(PCI17X4_IRQCLEAR_REG16);
    /* clear and reset all FIFOs */
    _outl(PCI17X4_FIFOCTR_REG32, pci17x4_reg8to32(PCI17X4_FIFOCTR_CLEAR | PCI17X4_FIFOCTR_RESET));
    /* reset the DMA counter register (we don't use it, just for completeness's sake) */
    _outw(PCI17X4_DMACRESET_REG16);
    /* enable interrupts for almost full FIFO levels */
    _outw(PCI17X4_IRQ_REG16, (PCI17X4_IRQ_CH0AF | PCI17X4_IRQ_CH1AF | PCI17X4_IRQ_CH2AF | PCI17X4_IRQ_CH3AF | PCI17X4_IRQ_ENABLE));
    /* clear any pending interrupts */
    _outw(PCI17X4_IRQCLEAR_REG16);
    /* reset alternating DMA channel selector */
    _outw(PCI17X4_DMACHRESET_REG16);
    /* select channel 0 fifo as the DMA requestor (does not matter with four channels, just for completeness's sake) */
    _outw(PCI17X4_DMAREQUEST_REG16, PCI17X4_DMAREQUEST_FIFO0);
    /* start DMA in driver */
    if(!_dmaStatus.error && (_ioctl(IOCTL_PCI17X4_DMA_START) >= 0)) {
      /* turn on the clock */
      _outw(PCI17X4_CLOCK_REG16, ((_inw(PCI17X4_CLOCK_REG16, true) & PCI17X4_CLOCK_DIVMASK) | PCI17X4_CLOCK_SRCINT), true);
      _outw(PCI17X4_TRIGGER_REG16, (PCI17X4_TRIGGER_MODEPACER | PCI17X4_TRIGGER_SRCEXT | PCI17X4_TRIGGER_FLAG), true);
      _readCount = 0;
      _freeCount = 0;
      _getStatus();
    }
  }
  return(false);
}

void
pci17x4::stop()
{
  /* turn off the clock */
  _outw(PCI17X4_CLOCK_REG16, ((_inw(PCI17X4_CLOCK_REG16, true) & PCI17X4_CLOCK_DIVMASK) | PCI17X4_CLOCK_SRCOFF), true);
  /* stop DMA in driver */
  _ioctl(IOCTL_PCI17X4_DMA_STOP);
}

pci17x4::datascan_t *
pci17x4::readScans(unsigned count)
{
  assert(count && ((count % blockScans()) == 0));
  if(_dmaStatus.running && !_dmaStatus.error && count && ((count % blockScans()) == 0)) {
    unsigned nblocks = count / blockScans();
    if((_readCount + nblocks) > _dmaStatus.readcount) {
      _getStatus();
      if(((_readCount + nblocks) > _dmaStatus.readcount) && (_dmaStatus.setup.timeout > 0)) {
        pci17x4_dmastatus_t waitstatus = _dmaStatus;
        waitstatus.readcount = _readCount + nblocks;
        _ioctl(IOCTL_PCI17X4_DMA_WAIT, &waitstatus);
        assert(((_readCount + nblocks) <= waitstatus.readcount) || waitstatus.error);
        // assert((_readCount + nblocks) >= (waitstatus.readcount + 16));
        _dmaStatus = waitstatus;
      }
    }
    if((_freeCount + bufferBlocks()) <= _dmaStatus.readcount) {
      _dmaStatus.errbuf = 1;
      return(NULL);
    }
    if((_readCount + nblocks) <= _dmaStatus.readcount) {
      datascan_t *result = _scan(_readCount);
      assert(((_readCount % bufferBlocks()) + nblocks) <= bufferBlocks());
      _readCount += nblocks;
      return(result);
    }
  }
  return(NULL);
}

bool
pci17x4::freeScans(const datascan_t *scans, unsigned count)
{
  assert(scans && count && ((count % blockScans()) == 0) && (_scan(_freeCount) == scans));
  if(scans && count && ((count % blockScans()) == 0) && (_scan(_freeCount) == scans)) {
    _freeCount += (count / blockScans());
    return(true);
  }
  return(false);
}

unsigned
pci17x4::bufferFull()
{
  if(_dmaStatus.running) {
    _getStatus();
    return ((_dmaStatus.readcount - _freeCount) * blockScans());
  }
  return(0);
}

#endif
