////////////////////////////////////////////////////////
//
// GEM - Graphics Environment for Multimedia
//
// Implementation file
//
// SPDX-FileCopyrightText: © 1997, Mark Danks and the GEM contributors
// SPDX-License-Identifier: GPL-2.0-or-later
//
////////////////////////////////////////////////////////

#include "GemPixDualObj.h"

#include "Gem/Cache.h"
#include "Gem/State.h"
#include "Utils/Functions.h"

#include <string.h>
#include <stdio.h>

#ifdef _MSC_VER

#define snprintf c99_snprintf

inline int c99_vsnprintf(char* str, size_t size, const char* format,
                         va_list ap)
{
  int count = -1;

  if (size != 0) {
    count = _vsnprintf_s(str, size, _TRUNCATE, format, ap);
  }
  if (count == -1) {
    count = _vscprintf(format, ap);
  }

  return count;
}

inline int c99_snprintf(char* str, size_t size, const char* format, ...)
{
  int count;
  va_list ap;

  va_start(ap, format);
  count = c99_vsnprintf(str, size, format, ap);
  va_end(ap);

  return count;
}

#endif // _MSC_VER

/////////////////////////////////////////////////////////
//
// GemPixDualObj
//
/////////////////////////////////////////////////////////
// Constructor
//
/////////////////////////////////////////////////////////
GemPixDualObj :: GemPixDualObj()
  : m_cacheRight(NULL),
    m_pixRight(NULL),// was disabled by DH 8/5/02
    m_pixRightValid(-1),
    org_pixRightValid(-1),
    m_inlet(NULL)
{
  m_inlet = inlet_new(this->x_obj, &this->x_obj->ob_pd, gensym("gem_state"),
                      gensym("gem_right"));
  memset(&m_pixRight, 0, sizeof(m_pixRight));
}

/////////////////////////////////////////////////////////
// Destructor
//
/////////////////////////////////////////////////////////
GemPixDualObj :: ~GemPixDualObj()
{
  inlet_free(m_inlet);
}

/////////////////////////////////////////////////////////
// processImage
//
/////////////////////////////////////////////////////////
void GemPixDualObj :: render(GemState *state)
{
  GemPixObj::render(state);
}

/////////////////////////////////////////////////////////
// processImage
//
/////////////////////////////////////////////////////////
#define PROCESS_DUALIMAGE_SIMD(CS)                      \
  switch(m_simd){                                       \
  case (GEM_SIMD_MMX):                                  \
    process##CS ##_MMX(image, m_pixRight->image);       \
    break;                                              \
  case(GEM_SIMD_SSE2):                                  \
    process##CS ##_SSE2(image, m_pixRight->image);      \
    break;                                              \
  case(GEM_SIMD_ALTIVEC):                               \
    process##CS ##_Altivec(image, m_pixRight->image);   \
    break;                                              \
  default:                                              \
    process##CS ##_##CS(image, m_pixRight->image);      \
  }

#define PROCESS_DUALIMAGE(CS1, CS2)                     \
  process##CS1 ##_##CS2 (image, m_pixRight->image);

#define PROCESS_COLORSPACE(FUN_RGBA, FUN_YUV, FUN_GRAY) \
  switch (m_pixRight->image.format) {                   \
  case GL_RGBA: case GL_BGRA_EXT:                       \
    found=true; FUN_RGBA; break;                        \
  case GL_LUMINANCE:                                    \
    found=true; FUN_GRAY; break;                        \
  case GL_YUV422_GEM:                                \
    found=true; FUN_YUV ; break;                        \
  default:break;}

void GemPixDualObj :: processImage(imageStruct &image)
{
  if (!m_cacheRight || m_cacheRight->m_magic!=GEMCACHE_MAGIC) {
    m_cacheRight=NULL;
    return;
  }

  //if (!m_cacheRight || !&image || !&m_pixRight || !&m_pixRight->image) return;
  if (!m_pixRightValid) {
    return;
  }

  if (image.xsize != m_pixRight->image.xsize ||
      image.ysize != m_pixRight->image.ysize)    {
    error("two images do not have equal dimensions (%dx%d != %dx%d)",
          image.xsize, image.ysize,
          m_pixRight->image.xsize, m_pixRight->image.ysize);
    m_pixRightValid = 0;
    return;
  }

  if(image.upsidedown != m_pixRight->image.upsidedown) {
    image.fixUpDown();
    m_pixRight->image.fixUpDown();
  }

  bool found = false;
  switch (image.format) {
  case GL_RGBA:
  case GL_BGRA_EXT:
    PROCESS_COLORSPACE(PROCESS_DUALIMAGE_SIMD(RGBA),
                       PROCESS_DUALIMAGE(RGBA, YUV),
                       PROCESS_DUALIMAGE(RGBA, Gray));
    break;
  case GL_LUMINANCE:
    PROCESS_COLORSPACE(PROCESS_DUALIMAGE(Gray, RGBA),
                       PROCESS_DUALIMAGE(Gray, YUV),
                       PROCESS_DUALIMAGE_SIMD(Gray));
    break;
  case GL_YUV422_GEM:
    PROCESS_COLORSPACE(PROCESS_DUALIMAGE(YUV, RGBA),
                       PROCESS_DUALIMAGE_SIMD(YUV),
                       PROCESS_DUALIMAGE(YUV, Gray));
    break;
  default:
    break;
  }
  if (!found) {
    processDualImage(image, m_pixRight->image);
  }
}

/////////////////////////////////////////////////////////
// process
//
/////////////////////////////////////////////////////////
namespace
{
std::string format2string(GLenum fmt)
{
  std::string result="unknown";
  switch (fmt) {
  case GL_RGBA:
  case GL_BGRA_EXT:
    result = "RGBA";
    break;
  case GL_LUMINANCE:
    result = "Gray";
    break;
  case GL_YUV422_GEM:
    result = "YUV";
    break;
  default:
    do {
      char fmtstring[7];
      snprintf(fmtstring, 6, "0x%04X", (unsigned int)fmt);
      fmtstring[6]='\0';
      result=fmtstring;
    } while(0);
  }
  return result;
}
}
void GemPixDualObj :: processDualImage(imageStruct &left,
                                       imageStruct &right)
{
  std::string lformat=format2string(left.format);
  std::string rformat=format2string(right.format);
  error("no method to combine (%s) and (%s)", lformat.c_str(),
        rformat.c_str());
}

/////////////////////////////////////////////////////////
// postrender
//
/////////////////////////////////////////////////////////
void GemPixDualObj :: postrender(GemState *state)
{
  if (org_pixRightValid != m_pixRightValid) {
    setPixModified();
  }

  org_pixRightValid = m_pixRightValid;

  m_pixRightValid = 0;
}

/////////////////////////////////////////////////////////
// stopRendering
//
/////////////////////////////////////////////////////////
void GemPixDualObj :: stopRendering()
{
  m_pixRightValid = 0;
}

/////////////////////////////////////////////////////////
// rightRender
//
/////////////////////////////////////////////////////////
void GemPixDualObj :: rightRender(GemState *statePtr)
{
  if (!statePtr || !statePtr->get(GemState::_PIX, m_pixRight)
      || !m_pixRight) {
    m_pixRightValid = 0;
    m_pixRight = 0;
    return;
  }

  m_pixRightValid = 1;
  if (m_pixRight->newimage) {
    setPixModified();  // force the left arm to create a new image
  }
}

/////////////////////////////////////////////////////////
// static member function
//
/////////////////////////////////////////////////////////
void GemPixDualObj :: obj_setupCallback(t_class *classPtr)
{
  class_addmethod(classPtr,
                  reinterpret_cast<t_method>(&GemPixDualObj::gem_rightMessCallback),
                  gensym("gem_right"), A_GIMME, A_NULL);
}
void GemPixDualObj :: gem_rightMessCallback(void *data, t_symbol* s,
    int argc, t_atom *argv)
{
  if (argc==1 && argv->a_type==A_FLOAT) {
  } else if (argc==2 && argv->a_type==A_POINTER
             && (argv+1)->a_type==A_POINTER) {
    GetMyClass(data)->m_cacheRight = (GemCache*)argv->a_w.w_gpointer;
    GetMyClass(data)->rightRender((GemState *)(argv+1)->a_w.w_gpointer);
  } else {
    GetMyClass(data)->error("wrong righthand arguments....");
  }
}
