

#include"Base.h"
#include"Scene.h"
#include"ScenePicking.h"
#include"SceneRender.h"
#include"ShaderMgr.h"
#include"MemoryDebug.h"
#include"PyMOL.h"
#include"P.h"
#include "MacPyMOL.h"
#include "Err.h"

typedef unsigned char pix[4];
#define cRange 7

int SceneDoXYPick(PyMOLGlobals * G, int x, int y, int click_side)
{
  CScene *I = G->Scene;
  int defer_builds_mode = SettingGetGlobal_i(G, cSetting_defer_builds_mode);

  if(defer_builds_mode == 5)    /* force generation of a pickable version */
    SceneUpdate(G, true);
  if(OrthoGetOverlayStatus(G) || SettingGetGlobal_i(G, cSetting_text))
    SceneRender(G, NULL, 0, 0, NULL, 0, 0, 0, 0);       /* remove overlay if present */
  SceneDontCopyNext(G);

  I->LastPicked.context.object = NULL;
  SceneRender(G, &I->LastPicked, x, y, NULL, 0, 0, click_side, 0);
  I->invPick = false;
  return (I->LastPicked.context.object != NULL);
  /* did we pick something? */
}

static
unsigned int SceneFindTriplet(PyMOLGlobals * G, int x, int y, GLenum gl_buffer, bool bits32)
{
  unsigned int result = 0;
  /*int before_check[100];
     int *int_ptr;
   */
  pix *buffer = NULL;
  pix *extra_safe_buffer = NULL;

  /*int after_check[100]; */
  /* pix_array *array_ptr;
     char *safe_place;
   */
  int a, b, d, flag;
  float contentScaleFactor = DIP2PIXEL(1);
  int cRangeVal = contentScaleFactor < 1.5 ? 7 : 21;

  int h = (cRangeVal * 2 + 1), w = (cRangeVal * 2 + 1);

  int debug = false;
  unsigned char *c;
  int bits15 = false;
  GLint rb, gb, bb, ab;
  int bkrd_alpha = 0xFF;
  int check_alpha = false;

  if(G->HaveGUI && G->ValidContext) {   /*just in case */

    glGetIntegerv(GL_RED_BITS, &rb);
    glGetIntegerv(GL_GREEN_BITS, &gb);
    glGetIntegerv(GL_BLUE_BITS, &bb);
    glGetIntegerv(GL_ALPHA_BITS, &ab);

    bits15 = (rb == 5) && (gb == 5) && (bb == 5);
    if((rb < 4) && (gb < 4) && (bb < 4)){
      PRINTFB(G, FB_Scene, FB_Errors) "SceneFindTriplet: ERROR: not enough colors to pick: rb=%d gb=%d bb=%d\n", rb, gb, bb ENDFB(G);
      return 0;
    }
    if(Feedback(G, FB_Scene, FB_Debugging))
      debug = true;

#ifndef PURE_OPENGL_ES_2
    if (!hasFrameBufferBinding())
      glReadBuffer(gl_buffer);
#endif
    extra_safe_buffer = Alloc(pix, w * h * 21);
    buffer = extra_safe_buffer + (w * h * 10);

    PyMOLReadPixels(x - cRangeVal, y - cRangeVal, cRangeVal * 2 + 1, cRangeVal * 2 + 1, GL_RGBA,
                    GL_UNSIGNED_BYTE, &buffer[0][0]);

    if(debug) {
      for(a = 0; a <= (cRangeVal * 2); a++) {
        for(b = 0; b <= (cRangeVal * 2); b++)
          printf("%2x ",
                 (buffer[a + b * w][0] + buffer[a + b * w][1] +
                  buffer[a + b * w][2]) & 0xFF);
        printf("\n");
      }
      printf("\n");
      for(a = 0; a <= (cRangeVal * 2); a++) {
        for(b = 0; b <= (cRangeVal * 2); b++)
          printf("%02x ", (buffer[a + b * w][3]) & 0xFF);
        printf("\n");
      }
      printf("\n");
      for(a = 0; a <= (cRangeVal * 2); a++) {
        for(b = 0; b <= (cRangeVal * 2); b++)
          printf("%02x%02x%02x ", (buffer[a + b * w][0]) & 0xFF,
                 (buffer[a + b * w][1]) & 0xFF, (buffer[a + b * w][2]) & 0xFF);
        printf("\n");
      }
      printf("\n");
    }

    /* first, check to make sure bkrd_alpha is correct 
       (this is a bug for systems with broken alpha, such as Extreme 3D on Solaris 8 */

    if (bits32){
      check_alpha = false;
    } else {
      flag = true;
      for(d = 0; ab && flag && (d < cRangeVal); d++)
        for(a = -d; flag && (a <= d); a++)
          for(b = -d; flag && (b <= d); b++) {
            c = &buffer[(a + cRangeVal) + (b + cRangeVal) * w][0];
            if(c[3] == bkrd_alpha) {
              check_alpha = true;
              flag = false;
            }
          }
    }

    /* now find the correct pixel */
    flag = true;
    bool check_green_bit = !bits32;
    bool strict = true;
    if (bits32 || bits15)
      strict = false;
    for(d = 0; flag && (d < cRangeVal); d++)
      for(a = -d; flag && (a <= d); a++)
        for(b = -d; flag && (b <= d); b++) {
          c = &buffer[(a + cRangeVal) + (b + cRangeVal) * w][0];
          if(
             ((c[3] == bkrd_alpha) || (!check_alpha)) &&
             ((bits15 && c[1]) || (c[1] & 0x8) || !check_green_bit) &&
             ((!strict) || ((c[1] & 0xF) == 8 && (c[0] & 0xF) == 0 && (c[2] & 0xF) == 0))
             ) {           /* only consider intact, saturated pixels */
	    if (bits15){  /* workaround for 15 bit rendering, for some reason red/green need rounding */
	      c[0] += 0x8;
	      c[2] += 0x8;
	    }
            if (bits32){
              result = (c[0] & 0xFF) + ((c[1] & 0xFF) << 8) + ((c[2] & 0xFF) << 16) + ((c[3] & 0xFF) << 24);
	      if (result)
		flag = false;
	    }
            else {
              result = ((c[0] >> 4) & 0xF) + (c[1] & 0xF0) + ((c[2] << 4) & 0xF00);
	      flag = false;
	    }
          }
        }
    FreeP(extra_safe_buffer);
  }
  return (result);
}

bool SceneHas32BitColor(PyMOLGlobals * G) {
#ifndef PURE_OPENGL_ES_2
  GLint bits;
  GLint currentFrameBuffer;

  ok_assert(1, SettingGetGlobal_b(G, cSetting_use_shaders));
  ok_assert(1, SettingGetGlobal_b(G, cSetting_pick32bit));

  glGetIntegerv(GL_FRAMEBUFFER_BINDING, &currentFrameBuffer);

  if (currentFrameBuffer != G->ShaderMgr->default_framebuffer_id) {
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, G->ShaderMgr->default_framebuffer_id);
  }

  glGetIntegerv(GL_ALPHA_BITS,  &bits); ok_assert(2, bits >= 8);
  glGetIntegerv(GL_BLUE_BITS,   &bits); ok_assert(2, bits >= 8);
  glGetIntegerv(GL_GREEN_BITS,  &bits); ok_assert(2, bits >= 8);
  glGetIntegerv(GL_RED_BITS,    &bits);

ok_except2:
  if (currentFrameBuffer != G->ShaderMgr->default_framebuffer_id) {
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, currentFrameBuffer);
  }

  ok_assert(1, bits >= 8);

  PRINTFD(G, FB_Scene) "Scene-DEBUG: 32bit picking\n" ENDFD;
  return true;

ok_except1:
  PRINTFD(G, FB_Scene) "Scene-DEBUG: 16bit picking\n" ENDFD;
#endif
  return false;
}

static
void SceneRenderPickingSinglePick(PyMOLGlobals * G, SceneUnitContext *context, Picking * pick, int x, int y, GLenum render_buffer){
  /* atom picking HACK - obfuscative coding */
  CScene *I = G->Scene;
  int debug_pick = 0;
  unsigned int lowBits = 0, highBits = 0;
  unsigned int index;
  bool bits32 = SceneHas32BitColor(G);

  debug_pick = SettingGetGlobal_i(G, cSetting_debug_pick);
  SceneGLClearColor(0.0, 0.0, 0.0, 0.);

  if (!I->pickVLA){
    I->pickVLA = VLACalloc(Picking, 5000);
  }

  if(I->grid.active)
    GridGetGLViewport(G, &I->grid);

  // two passes in 16bit picking mode, if needed
  for (int pass = 0;; ++pass) {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    if (I->invPick || !SettingGetGlobal_b(G, cSetting_use_shaders)){
      I->pickVLA[0].src.index = 0;
      I->pickVLA[0].src.bond = 2 + pass;
    } else {
      I->pickVLA[0].src.bond = 0 + pass;
    }
    {
      int slot;
      for(slot = 0; slot <= I->grid.last_slot; slot++) {
        if(I->grid.active) {
          GridSetGLViewport(&I->grid, slot);
        }
        SceneRenderAll(G, context, NULL, &I->pickVLA, 0, true, 0.0F, &I->grid, 0, 0, bits32);
      }
    }

    if(debug_pick) {
      PyMOL_SwapBuffers(G->PyMOL);
      PSleep(G, 1000000 * debug_pick / 4);
      PyMOL_SwapBuffers(G->PyMOL);
    }

    if (pass == 1) {
      highBits = SceneFindTriplet(G, x, y, render_buffer, false);
      index += (highBits << 12);
      break;
    }

    index = lowBits = SceneFindTriplet(G, x, y, render_buffer, bits32);

    if (bits32 || I->pickVLA[0].src.index < (1 << 12)) {
      // no need for a second pass
      break;
    }
  }

  if(I->grid.active)
    GridSetGLViewport(&I->grid, -1);

  if(debug_pick) {
    if (bits32){
      PRINTFB(G, FB_Scene, FB_Details)
	" SceneClick-Detail: lowBits=%u index %u < %u?\n", lowBits, index, I->pickVLA[0].src.index ENDFB(G);
    } else {
      PRINTFB(G, FB_Scene, FB_Details)
	" SceneClick-Detail: lowBits=%u highBits=%u index %u < %u?\n", lowBits, highBits, index, I->pickVLA[0].src.index ENDFB(G);
    }
  }

  if(index && (index <= I->pickVLA[0].src.index)) {
    *pick = I->pickVLA[index];       /* return object info */
    if(debug_pick) {
      PRINTFB(G, FB_Scene, FB_Details)
	" SceneClick-Detail: obj %p index %d bond %d\n",
	pick->context.object, pick->src.index, pick->src.bond ENDFB(G);
    }
    // if cPickableNoPick then set object to NULL since nothing picked
    if (pick->src.bond == cPickableNoPick)
      pick->context.object = NULL;
  } else {
    pick->context.object = NULL;
  }

#ifndef PURE_OPENGL_ES_2
  /* Picking changes the Shading model to GL_FLAT,
     we need to change it back to GL_SMOOTH. This is because
     bg_grad() might be called in OrthoDoDraw() before GL 
     settings are set in SceneRender() */
  //      glEnable(GL_COLOR_MATERIAL);
  glShadeModel(SettingGetGlobal_b(G, cSetting_pick_shading) ? GL_FLAT : GL_SMOOTH);
#endif
}

/*========================================================================*/
static
unsigned int *SceneReadTriplets(PyMOLGlobals * G, int x, int y, int w, int h,
                                GLenum gl_buffer, bool bits32)
{
  unsigned int *result = NULL;
  pix *buffer = NULL;
  pix *extra_safe_buffer = NULL;
  int a, b;
  unsigned char *c;
  int cc = 0;
  int bits15 = false;
  int bkrd_alpha = 0xFF;
  int check_alpha = false;

  GLint rb, gb, bb, ab;

  if(w < 1)
    w = 1;
  if(h < 1)
    h = 1;
  if(G->HaveGUI && G->ValidContext) {   /*just in case */
    glGetIntegerv(GL_RED_BITS, &rb);
    glGetIntegerv(GL_GREEN_BITS, &gb);
    glGetIntegerv(GL_BLUE_BITS, &bb);
    glGetIntegerv(GL_ALPHA_BITS, &ab);

    bits15 = (rb == 5) && (gb == 5) && (bb == 5);
    if((rb < 4) && (gb < 4) && (bb < 4)){
      PRINTFB(G, FB_Scene, FB_Errors) "SceneReadTriplet: ERROR: not enough colors to pick: rb=%d gb=%d bb=%d\n", rb, gb, bb ENDFB(G);
      return 0;
    }
    /* create some safe RAM on either side of the read buffer -- buggy
       ReadPixels implementations tend to trash RAM surrounding the
       target block */

    extra_safe_buffer = Alloc(pix, w * h * 11);
    buffer = extra_safe_buffer + (w * h * 5);

    result = VLAlloc(unsigned int, w * h);
#ifndef PURE_OPENGL_ES_2
    if (!hasFrameBufferBinding())
      glReadBuffer(gl_buffer);
#endif
    PyMOLReadPixels(x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, &buffer[0][0]);

    /* first, check to make sure bkrd_alpha is correct 
       (this is a bug for systems with broken alpha, such as Extreme 3D on Solaris 8 */

    if (bits32)
      check_alpha = false;
    else
      for(a = 0; ab && a < w; a++)
	for(b = 0; b < h; b++) {
	  c = &buffer[a + b * w][0];
	  if(c[3] == bkrd_alpha) {
	    check_alpha = true;
	  }
        }

    bool check_green_bit = !bits32;
    bool strict = true;
    if (bits32 || bits15)
      strict = false;
    /* now read pixels */
    for(a = 0; a < w; a++)
      for(b = 0; b < h; b++) {
        c = &buffer[a + b * w][0];
        if(
           ((c[3] == bkrd_alpha) || (!check_alpha)) &&
           ((bits15 && c[1]) || (c[1] & 0x8) || !check_green_bit) &&
           ((!strict) || ((c[1] & 0xF) == 8 && (c[0] & 0xF) == 0 && (c[2] & 0xF) == 0))
           ) {             /* only consider intact, saturated pixels */
          VLACheck(result, unsigned int, cc + 1);
	  if (bits15){  /* workaround for 15 bit rendering, for some reason red/green need rounding */
	    c[0] += 0x8;
	    c[2] += 0x8;
	  }
          if (bits32)
            result[cc] = (c[0] & 0xFF) + ((c[1] & 0xFF) << 8) + ((c[2] & 0xFF) << 16) + ((c[3] & 0xFF) << 24);
          else {
            result[cc] = ((c[0] >> 4) & 0xF) + (c[1] & 0xF0) + ((c[2] << 4) & 0xF00);
          }
          result[cc + 1] = b + a * h; // resulting pixel offset
          if (result[cc] || (!bits32))  // !bits32 because it should hit the high-order bits
            cc += 2;
        }
      }
    FreeP(extra_safe_buffer);
    VLASize(result, unsigned int, cc);
  }
  return (result);
}

static
void SceneRenderPickingMultiPick(PyMOLGlobals * G, SceneUnitContext *context, Multipick * smp, GLenum render_buffer){
  /* multiple atom picking HACK - even more obfuscative coding */
  CScene *I = G->Scene;
  Picking *pik;
  unsigned int *lowBitVLA = NULL, *highBitVLA = NULL;
  int high, low;
  unsigned int lastIndex = 0;
  unsigned int index;
  void *lastPtr = NULL;
  int nPick;
  int nHighBits, nLowBits;
  bool bits32 = SceneHas32BitColor(G);

  SceneGLClearColor(0.0, 0.0, 0.0, 0.0);

  if (!I->pickVLA){
    I->pickVLA = VLACalloc(Picking, 5000);
  }

  if(I->grid.active)
    GridGetGLViewport(G, &I->grid);

  // two passes in 16bit picking mode, if needed
  for (int pass = 0;; ++pass) {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    if (I->invPick || !SettingGetGlobal_b(G, cSetting_use_shaders)){
      I->pickVLA[0].src.index = 0;
      I->pickVLA[0].src.bond = 2 + pass;
    } else {
      I->pickVLA[0].src.bond = 0 + pass;
    }

    {
      int slot;
      for(slot = 0; slot <= I->grid.last_slot; slot++) {
        if(I->grid.active) {
          GridSetGLViewport(&I->grid, slot);
        }
        SceneRenderAll(G, context, NULL, &I->pickVLA, 0, true, 0.0F, &I->grid, 0, 0, bits32);
      }
    }

    if (pass == 1) {
      highBitVLA = SceneReadTriplets(G, smp->x, smp->y, smp->w, smp->h, render_buffer, false);
      nHighBits = VLAGetSize(highBitVLA);
      break;
    }

    lowBitVLA = SceneReadTriplets(G, smp->x, smp->y, smp->w, smp->h, render_buffer, bits32);
    nLowBits = VLAGetSize(lowBitVLA);

    if (bits32 || I->pickVLA[0].src.index < (1 << 12)) {
      // no need for a second pass
      bits32 = true; // continue like with 32bit picking
      break;
    }
  }

  if(I->grid.active)
    GridSetGLViewport(&I->grid, -1);

  /* need to scissor this */ 
  nPick = 0;
  if(nLowBits && (bits32 || nHighBits)) {
    low = 0;
    high = 0;
    while((low < nLowBits) && (bits32 || high < nHighBits)) {
      if(bits32 || lowBitVLA[low + 1] == highBitVLA[high + 1]) {
        if (bits32){
          // 32bit picking
          index = lowBitVLA[low];
        } else {
          index = lowBitVLA[low] + (highBitVLA[high] << 12);
        }
        if(index && (index <= I->pickVLA[0].src.index)) {
          pik = I->pickVLA + index;  /* just using as a tmp */
          if((pik->src.index != lastIndex) || (pik->context.object != lastPtr)) {
            if(((CObject *) pik->context.object)->type == cObjectMolecule) {
              nPick++;    /* start from 1 */
              VLACheck(smp->picked, Picking, nPick);
              smp->picked[nPick] = *pik;  /* return atom/object info -- will be redundant */
            }
            lastIndex = pik->src.index;
            lastPtr = pik->context.object;
          }
        }
        low += 2;
        high += 2;
      } else if(lowBitVLA[low + 1] < highBitVLA[high + 1])
        low += 2;
      else
        high += 2;
    }
  }
  smp->picked[0].src.index = nPick;
#ifndef PURE_OPENGL_ES_2
  /* Picking changes the Shading model to GL_FLAT,
     we need to change it back to GL_SMOOTH. This is because
     bg_grad() might be called in OrthoDoDraw() before GL 
     settings are set in SceneRender() */
  //      glEnable(GL_COLOR_MATERIAL);
  glShadeModel(SettingGetGlobal_b(G, cSetting_pick_shading) ? GL_FLAT : GL_SMOOTH);
#endif
  VLAFreeP(lowBitVLA);
  VLAFreeP(highBitVLA);
}

void SceneRenderPicking(PyMOLGlobals * G, int stereo_mode, int *click_side, int stereo_double_pump_mono, 
			Picking * pick, int x, int y, Multipick * smp, SceneUnitContext *context,
			GLenum render_buffer){
  CScene *I = G->Scene;

  if (render_buffer == GL_BACK) {
    render_buffer = G->DRAW_BUFFER0;
  }

  SceneSetupGLPicking(G);

  if (!stereo_double_pump_mono){
    switch (stereo_mode) {
    case cStereo_crosseye:
    case cStereo_walleye:
    case cStereo_sidebyside:
      glViewport(I->Block->rect.left, I->Block->rect.bottom, I->Width / 2, I->Height);
      break;
    case cStereo_geowall:
      *click_side = OrthoGetWrapClickSide(G);
      break;
    }
  }
#ifndef PURE_OPENGL_ES_2
  glPushMatrix();           /* 1 */
#endif

  switch (stereo_mode) {
  case cStereo_crosseye:
    ScenePrepareMatrix(G, (*click_side > 0) ? 1 : 2);
    break;
  case cStereo_walleye:
  case cStereo_geowall:
  case cStereo_sidebyside:
    ScenePrepareMatrix(G, (*click_side < 0) ? 1 : 2);
    break;
  }
  G->ShaderMgr->SetIsPicking(true);
  if(pick) {
    SceneRenderPickingSinglePick(G, context, pick, x, y, render_buffer);
  } else if(smp) {
    SceneRenderPickingMultiPick(G, context, smp, render_buffer);
  }
  G->ShaderMgr->SetIsPicking(false);
#ifndef PURE_OPENGL_ES_2
  glPopMatrix();            /* 1 */
#endif
}

/*========================================================================*/
int SceneMultipick(PyMOLGlobals * G, Multipick * smp)
{
  CScene *I = G->Scene;
  int click_side = 0;
  int defer_builds_mode = SettingGetGlobal_i(G, cSetting_defer_builds_mode);

  if(defer_builds_mode == 5)    /* force generation of a pickable version */
    SceneUpdate(G, true);

  if(OrthoGetOverlayStatus(G) || SettingGetGlobal_i(G, cSetting_text))
    SceneRender(G, NULL, 0, 0, NULL, 0, 0, 0, 0);       /* remove overlay if present */
  SceneDontCopyNext(G);
  if(StereoIsAdjacent(G)) {
    if(smp->x > (I->Width / 2))
      click_side = 1;
    else
      click_side = -1;
    smp->x = smp->x % (I->Width / 2);
  }
  SceneRender(G, NULL, 0, 0, smp, 0, 0, click_side, 0);
  SceneDirty(G);
  return (1);
}
