#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/poll.h>

#include <rfb/IrixDMIC_RawToJpeg.h>
#include <rfb/LogWriter.h>

using namespace rfb;

static LogWriter vlog("IrixDMIC");

//
// Constructor. Find a converter and create a context. Also, create
// DMparams structures for using with dmICSetSrcParams,
// dmICSetDstParams and dmICSetConvParams.
//

IrixDMIC_RawToJpeg::IrixDMIC_RawToJpeg(bool needRealtime)
  : m_valid_ic(false),
    m_srcParams(0),
    m_dstParams(0),
    m_convParams(0)
{
  if ( dmParamsCreate(&m_srcParams) != DM_SUCCESS ||
       dmParamsCreate(&m_dstParams) != DM_SUCCESS ||
       dmParamsCreate(&m_convParams) != DM_SUCCESS ) {
    reportError("dmParamsCreate");
    return;
  }

  DMparams *p;
  const int JPEG_ID = 0x6A706567; // same as 'jpeg'
  int id, direction, speed;

  int n = dmICGetNum();
  while (n--) {
    if (dmParamsCreate(&p) != DM_SUCCESS) {
      reportError("dmParamsCreate");
      return;
    }
    if (dmICGetDescription(n, p) != DM_SUCCESS)
      continue;
    id = dmParamsGetInt(p, DM_IC_ID);
    direction = dmParamsGetEnum(p, DM_IC_CODE_DIRECTION);
    speed = dmParamsGetEnum(p, DM_IC_SPEED);
    if ( id == JPEG_ID && direction == DM_IC_CODE_DIRECTION_ENCODE &&
	 (!needRealtime || speed == DM_IC_SPEED_REALTIME) ) {

      const char *engine = dmParamsGetString(p, DM_IC_ENGINE);
      vlog.info("Found JPEG encoder: \"%s\" (%d)", engine, n);

      dmParamsDestroy(p);
      break;
    }
    dmParamsDestroy(p);
  }
  if (n < 0) {
    vlog.error("Error: No matching JPEG encoder found");
    debugListConverters();
    return;
  }
  if (dmICCreate(n, &m_ic) != DM_SUCCESS) {
    reportError("dmICCreate");
    return;
  }

  m_valid_ic = true;
}

//
// Destructor.
//

IrixDMIC_RawToJpeg::~IrixDMIC_RawToJpeg()
{
  if (m_valid_ic)
    dmICDestroy(m_ic);

  if (m_convParams)
    dmParamsDestroy(m_convParams);
  if (m_dstParams)
    dmParamsDestroy(m_dstParams);
  if (m_srcParams)
    dmParamsDestroy(m_srcParams);
}

void
IrixDMIC_RawToJpeg::debugListConverters()
{
  int n = dmICGetNum();
  vlog.debug("IRIX DMIC converters available (%d):", n);
  for (int i = 0; i < n; i++) {
    DMparams *p;
    if (dmParamsCreate(&p) != DM_SUCCESS) {
      return;
    }
    if (dmICGetDescription(i, p) != DM_SUCCESS) {
      vlog.debug("  continue");
      continue;
    }
    union {
      int id;
      char label[5];
    } id;
    id.id = dmParamsGetInt(p, DM_IC_ID);
    id.label[4] = '\0';
    int direction = dmParamsGetEnum(p, DM_IC_CODE_DIRECTION);
    const char *directionLabel = "converter";
    if (direction == DM_IC_CODE_DIRECTION_ENCODE) {
      directionLabel = "encoder";
    } else if (direction == DM_IC_CODE_DIRECTION_DECODE) {
      directionLabel = "decoder";
    }
    int speed = dmParamsGetEnum(p, DM_IC_SPEED);
    const char *speedLabel = "";
    if (speed == DM_IC_SPEED_REALTIME) {
      speedLabel = "realtime ";
    }
    const char *engine = dmParamsGetString(p, DM_IC_ENGINE);
    vlog.debug("  converter %d: '%s' %s%s (%s)",
	       i, id.label, speedLabel, directionLabel, engine);
    dmParamsDestroy(p);
  }
}

//
// Configure the source and destination formats.
//
// FIXME: Remember image size that was previously set, do not set the
//        same size again.
//

bool
IrixDMIC_RawToJpeg::setImageParams(int w, int h)
{
  if (!m_valid_ic) {
    reportErrorNotInited();
    return false;
  }

  // Set source image parameters.
  DMpacking packing = DM_IMAGE_PACKING_XBGR;
  int orient = DM_IMAGE_TOP_TO_BOTTOM;
  if (dmSetImageDefaults(m_srcParams, w, h, packing) != DM_SUCCESS) {
    reportError("dmSetImageDefaults");
    return false;
  }
  DMstatus err = dmParamsSetEnum(m_srcParams, DM_IMAGE_ORIENTATION, orient);
  if (err != DM_SUCCESS) {
    reportError("dmParamsSetEnum");
    return false;
  }
  if (dmICSetSrcParams(m_ic, m_srcParams) != DM_SUCCESS) {
    reportError("dmICSetSrcParams");
    return false;
  }

  // Set destination image parameters.
  packing = DM_IMAGE_PACKING_CbYCrY;
  const char *compression = DM_IMAGE_JPEG;
  if (dmSetImageDefaults(m_dstParams, w, h, packing) != DM_SUCCESS) {
    reportError("dmSetImageDefaults");
    return false;
  }
  err = dmParamsSetEnum(m_dstParams, DM_IMAGE_ORIENTATION, orient);
  if (err != DM_SUCCESS) {
    reportError("dmParamsSetEnum");
    return false;
  }
  err = dmParamsSetString(m_dstParams, DM_IMAGE_COMPRESSION, compression);
  if (err != DM_SUCCESS) {
    reportError("dmParamsSetString");
    return false;
  }
  if (dmICSetDstParams(m_ic, m_dstParams) != DM_SUCCESS) {
    reportError("dmICSetDstParams");
    return false;
  }

  return true;
}

//
// Set JPEG image quality level (an integer in the range 0..99).
//
// FIXME: Remember image quality that was previously set, do not set
//        the same quality again.
//

bool
IrixDMIC_RawToJpeg::setImageQuality(int quality)
{
  if (!m_valid_ic) {
    reportErrorNotInited();
    return false;
  }

  double qf = (double)quality / 100.0;

  DMstatus err = dmParamsSetFloat(m_convParams, DM_IMAGE_QUALITY_SPATIAL, qf);
  if (err != DM_SUCCESS) {
    reportError("dmParamsSetFloat");
    return false;
  }
  if (dmICSetConvParams(m_ic, m_convParams) != DM_SUCCESS) {
    reportError("dmICSetConvParams");
    return false;
  }

  return true;
}

//
// Set up source buffer pool.
//
// NOTE: Both setImageParams() and setImageQuality() functions should
//       be called prior to creating and registering buffer pools.
//

bool
IrixDMIC_RawToJpeg::createSrcBufferPool(DMbufferpool *pool,
                                        int bufCount, int bufSize)
{
  if (!m_valid_ic) {
    reportErrorNotInited();
    return false;
  }

  DMparams *p;
  if (dmParamsCreate(&p) != DM_SUCCESS) {
    reportError("dmParamsCreate");
    return false;
  }

  if (dmBufferSetPoolDefaults(p, bufCount, bufSize, DM_FALSE, DM_TRUE) !=
      DM_SUCCESS) {
    reportError("dmBufferSetPoolDefaults");
    dmParamsDestroy(p);
    return false;
  }
  if (dmICGetSrcPoolParams(m_ic, p) != DM_SUCCESS) {
    reportError("dmICGetSrcPoolParams");
    dmParamsDestroy(p);
    return false;
  }
  if (dmBufferCreatePool(p, pool) != DM_SUCCESS) {
    reportError("dmBufferCreatePool");
    dmParamsDestroy(p);
    return false;
  }

  dmParamsDestroy(p);
  return true;
}

//
// Set up and register destination buffer pool.
//
// NOTE: Both setImageParams() and setImageQuality() functions should
//       be called prior to creating and registering buffer pools.
//

bool
IrixDMIC_RawToJpeg::registerDstBufferPool(DMbufferpool *pool,
                                          int bufCount, int bufSize)
{
  if (!m_valid_ic) {
    reportErrorNotInited();
    return false;
  }

  DMparams *p;
  if (dmParamsCreate(&p) != DM_SUCCESS) {
    reportError("dmParamsCreate");
    return false;
  }

  if (dmBufferSetPoolDefaults(p, bufCount, bufSize, DM_FALSE, DM_TRUE) !=
      DM_SUCCESS) {
    reportError("dmBufferSetPoolDefaults");
    dmParamsDestroy(p);
    return false;
  }
  if (dmICGetDstPoolParams(m_ic, p) != DM_SUCCESS) {
    reportError("dmICGetDstPoolParams");
    dmParamsDestroy(p);
    return false;
  }
  if (dmBufferCreatePool(p, pool) != DM_SUCCESS) {
    reportError("dmBufferCreatePool");
    dmParamsDestroy(p);
    return false;
  }

  dmParamsDestroy(p);

  if (dmICSetDstPool(m_ic, *pool) != DM_SUCCESS) {
    reportError("dmICSetDstPool");
    destroyBufferPool(*pool);
    return false;
  }

  return true;
}

//
// Destroy buffer pool created with either createSrcBufferPool() or
// registerDstBufferPool().
//

void
IrixDMIC_RawToJpeg::destroyBufferPool(DMbufferpool pool)
{
  if (dmBufferDestroyPool(pool) != DM_SUCCESS)
    reportError("dmBufferDestroyPool");
}

//
// Allocate a buffer from the specified pool.
//

bool
IrixDMIC_RawToJpeg::allocBuffer(DMbuffer *pbuf, DMbufferpool pool)
{
  if (dmBufferAllocate(pool, pbuf) != DM_SUCCESS) {
    reportError("dmBufferAllocate");
    return false;
  }

  return true;
}

//
// Fill in a DMbuffer with data.
//
// NOTE: The caller must make sure that the buffer size is no less
//       than dataSize.
//

bool
IrixDMIC_RawToJpeg::copyToBuffer(DMbuffer buf, const void *data, int dataSize)
{
  void *bufPtr = dmBufferMapData(buf);
  memcpy(bufPtr, data, dataSize);

  if (dmBufferSetSize(buf, dataSize) != DM_SUCCESS) {
    reportError("dmBufferSetSize");
    return false;
  }

  return true;
}

//
// Fill in a DMbuffer with data.
//
// NOTE: The caller must make sure that the buffer size is no less
//       than (nRows * rowSize).
//

bool
IrixDMIC_RawToJpeg::copyToBuffer(DMbuffer buf, const void *data,
                                 int rowSize, int nRows, int stride)
{
  char *dataBytes = (char *)data;
  char *bufPtr = (char *)dmBufferMapData(buf);
  for (int i = 0; i < nRows; i++) {
    memcpy(bufPtr, &dataBytes[i * stride], rowSize);
    bufPtr += rowSize;
  }

  if (dmBufferSetSize(buf, nRows * rowSize) != DM_SUCCESS) {
    reportError("dmBufferSetSize");
    return false;
  }

  return true;
}

//
// Map DMbuffer to physical memory.
//

void *
IrixDMIC_RawToJpeg::mapBufferData(DMbuffer buf)
{
  return dmBufferMapData(buf);
}

//
// Get the number of valid bytes in DMbuffer.
//

int
IrixDMIC_RawToJpeg::getBufferSize(DMbuffer buf)
{
  return dmBufferGetSize(buf);
}

//
// Free DMbuffer.
//

void
IrixDMIC_RawToJpeg::freeBuffer(DMbuffer buf)
{
  if (dmBufferFree(buf) != DM_SUCCESS)
    reportError("dmBufferFree");
}

//
// Send input data (raw pixels) to the converter.
//

bool
IrixDMIC_RawToJpeg::sendData(DMbuffer buf)
{
  if (dmICSend(m_ic, buf, 0, NULL) != DM_SUCCESS) {
    reportError("dmICSend");
    return false;
  }

  return true;
}

//
// Wait until compression is finished (infinite timeout!).
// This function should be called after sendData() and before receiveData().
//
// FIXME: Report errors.
//

bool
IrixDMIC_RawToJpeg::waitConversion()
{
  struct pollfd ps;
  ps.fd = dmICGetDstQueueFD(m_ic);
  ps.events = POLLIN;

  int result = poll(&ps, 1, -1);
  if (result != 1)
    return false;

  if ((ps.revents & POLLIN) != 0) {
    return true;
  } else {
    return false;
  }
}

//
// Receive output (JPEG data) from the converter.
// Call waitConversion() function first.
//

bool
IrixDMIC_RawToJpeg::receiveData(DMbuffer *pbuf)
{
  if (dmICReceive(m_ic, pbuf) != DM_SUCCESS) {
    reportError("dmICReceive");
    return false;
  }

  return true;
}

//
// Report an error when a function returns DM_FAILURE.
//

void
IrixDMIC_RawToJpeg::reportError(const char *funcName)
{
  char errorDetail[DM_MAX_ERROR_DETAIL];
  const char *errorCategory = dmGetError(NULL, errorDetail);
  vlog.error("%s() failed: %s: %s",
             funcName, errorCategory, errorDetail);
}

//
// Report an error when (m_valid_ic == false).
//

void
IrixDMIC_RawToJpeg::reportErrorNotInited()
{
  vlog.error("Internal error: Image converter not initialized");
}