
/* 
A* -------------------------------------------------------------------
B* This file contains source code for the PyMOL computer program
C* copyright 1998-2000 by Warren Lyford Delano of DeLano Scientific. 
D* -------------------------------------------------------------------
E* It is unlawful to modify or remove this copyright notice.
F* -------------------------------------------------------------------
G* Please see the accompanying LICENSE file for further information. 
H* -------------------------------------------------------------------
I* Additional authors of this source file include:
-* 
-* 
-*
Z* -------------------------------------------------------------------
*/

#include"os_gl.h"

#include"Base.h"
#include"Character.h"
#include"Pixmap.h"
#include"Util.h"
#include"MemoryDebug.h"
#include"Vector.h"
#include"Text.h"
#include"Texture.h"
#include"CGO.h"
#include <glm/gtc/type_ptr.hpp>

#define HASH_MASK 0x2FFF

static unsigned int get_hash(CharFngrprnt * fprnt)
{
  unsigned int result = 0;
  unsigned short int *data = fprnt->u.d.data;
  result = (data[0] << 1) + data[1];
  result = ((result << 4) + data[2]);
  result = ((result << 7) + data[3]) + (result >> 16);
  result = ((result << 10) + data[4]) + (result >> 16);
  result = ((result << 13) + data[5]) + (result >> 16);
  result = ((result << 15) + data[6]) + (result >> 16);
  result = ((result << 15) + data[7]) + (result >> 16);
  result = ((result << 15) + data[8]) + (result >> 16);
  result = ((result << 1) + data[9]) + (result >> 16);
  return (HASH_MASK & result);
}

static int equal_fprnt(CharFngrprnt * f1, CharFngrprnt * f2)
{
  unsigned short int *data1 = f1->u.d.data;
  unsigned short int *data2 = f2->u.d.data;

  /* must compare all fields in fingerprint */

  if(*(data1++) != *(data2++))
    return 0;
  if(*(data1++) != *(data2++))
    return 0;
  if(*(data1++) != *(data2++))
    return 0;
  if(*(data1++) != *(data2++))
    return 0;
  if(*(data1++) != *(data2++))
    return 0;
  if(*(data1++) != *(data2++))
    return 0;
  if(*(data1++) != *(data2++))
    return 0;
  if(*(data1++) != *(data2++))
    return 0;
  if(*(data1++) != *(data2++))
    return 0;
  if(*(data1++) != *(data2++))
    return 0;
  return 1;
}

int CharacterFind(PyMOLGlobals * G, CharFngrprnt * fprnt)
{
  CCharacter *I = G->Character;
  unsigned int hash_code = get_hash(fprnt);
  int id = I->Hash[hash_code];
  /*
     printf("seeking %d %d %d %d %d %d %d\n",
     fprnt->u.i.text_id,
     fprnt->u.i.ch,
     fprnt->u.i.height,
     fprnt->u.i.color[0],
     fprnt->u.i.color[1],
     fprnt->u.i.color[2],
     fprnt->u.i.color[3]);
   */

  while(id) {
    if(equal_fprnt(fprnt, &I->Char[id].Fngrprnt)) {

      /* pop character to top of retention list 
         (is this worth the effort?) */
      CharRec *rec = I->Char + id;
      int prev = rec->Prev;
      int next = rec->Next;

      if(prev && next) {        /* only act if character is in middle of list */
        I->Char[prev].Next = next;
        I->Char[next].Prev = prev;

        prev = I->NewestUsed;
        I->NewestUsed = id;
        I->Char[prev].Next = id;
        rec->Prev = prev;
        rec->Next = 0;
      }
      return id;
    } else
      id = I->Char[id].HashNext;
  }

  return 0;
}

unsigned char *CharacterGetPixmapBuffer(PyMOLGlobals * G, int id)
{
  CCharacter *I = G->Character;
  if(id) {
    CharRec *rec = I->Char + id;
    return rec->Pixmap.buffer;
  }
  return NULL;
}

int CharacterNewFromBitmap(PyMOLGlobals * G, int width, int height,
                           unsigned char *bitmap,
                           float x_orig, float y_orig, float advance,
                           CharFngrprnt * fprnt, int sampling)
{
  CCharacter *I = G->Character;
  int id = CharacterGetNew(G);
  if((id > 0) && (id <= I->MaxAlloc)) {
    CharRec *rec = I->Char + id;
    PixmapInitFromBitmap(G, &rec->Pixmap, width, height, bitmap,
                         fprnt->u.i.color, sampling);
    rec->Width = width * sampling;
    rec->Height = height * sampling;
    rec->XOrig = x_orig * sampling;
    rec->YOrig = y_orig * sampling;
    rec->Advance = advance * sampling;
    {                           /* add this character to the hash */
      int hash_code = get_hash(fprnt);
      int cur_entry;
      rec->Fngrprnt = *(fprnt);
      rec->Fngrprnt.hash_code = hash_code;
      cur_entry = I->Hash[hash_code];
      if(cur_entry) {
        I->Char[cur_entry].HashPrev = id;
      }
      I->Char[id].HashNext = I->Hash[hash_code];
      I->Hash[hash_code] = id;
    }
  }
  return id;
}

int CharacterNewFromBytemap(PyMOLGlobals * G, int width, int height,
                            int pitch, unsigned char *bytemap,
                            float x_orig, float y_orig, float advance,
                            CharFngrprnt * fprnt)
{
  CCharacter *I = G->Character;
  int id = CharacterGetNew(G);
  if((id > 0) && (id <= I->MaxAlloc)) {
    CharRec *rec = I->Char + id;
    PixmapInitFromBytemap(G, &rec->Pixmap, width, height, pitch, bytemap,
                          fprnt->u.i.color, fprnt->u.i.outline_color, fprnt->u.i.flat);
    rec->Width = width;
    rec->Height = height;
    rec->XOrig = x_orig;
    rec->YOrig = y_orig;
    rec->Advance = advance;
    {                           /* add this character to the hash */
      int hash_code = get_hash(fprnt);
      int cur_entry;
      rec->Fngrprnt = *(fprnt);
      rec->Fngrprnt.hash_code = hash_code;
      cur_entry = I->Hash[hash_code];
      if(cur_entry) {
        I->Char[cur_entry].HashPrev = id;
      }
      I->Char[id].HashNext = I->Hash[hash_code];
      I->Hash[hash_code] = id;
    }
  }
  return id;
}

float CharacterGetAdvance(PyMOLGlobals * G, int sampling, int id)
{
  CCharacter *I = G->Character;
  CharRec *rec = I->Char + id;
  return rec->Advance / sampling;
}

void CharacterRenderOpenGLPrime(PyMOLGlobals * G, const RenderInfo * info)
{
  if(G->HaveGUI && G->ValidContext) {
    if ((info && !info->use_shaders) || (!info && !SettingGetGlobal_b(G, cSetting_use_shaders))){
      glEnable(GL_TEXTURE_2D);
      glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
    }
    /*    glEnable(GL_BLEND);
       glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); */
  }
}

void CharacterRenderOpenGLDone(PyMOLGlobals * G, const RenderInfo * info)
{
  if(G->HaveGUI && G->ValidContext) {
    if ((info && !info->use_shaders) || (!info && !SettingGetGlobal_b(G, cSetting_use_shaders))){
      glDisable(GL_TEXTURE_2D);
    }
    /*    glDisable(GL_BLEND); */
  }
}

short CharacterRenderOpenGL(PyMOLGlobals * G, const RenderInfo * info, int id, short isworldlabel, short relativeMode, CGO* shaderCGO)

/* need orientation matrix */
{
  CCharacter *I = G->Character;
  CharRec *rec = I->Char + id;
  short success = 1;
  auto isTextured = TextureIsCharTextured(G, id, rec->extent);
  float sampling = 1.0F;

  if (G->HaveGUI && G->ValidContext && isTextured) {
    if(info)
      sampling = (float) info->sampling;
    if (isTextured) {
    /*    if(glIsTexture(texture_id)) -- BAD -- impacts performance */
      float *v, v0[3];
      float v1[3];
      if (!shaderCGO){
        if (!TextGetIsPicking(G)) {
          TextureBindTexture(G);
        }
      }
      v = TextGetPos(G);
      copy3f(v, v0);
      v0[0] -= rec->XOrig / sampling;
      v0[1] -= rec->YOrig / sampling;
      copy3f(v0, v1);
      v1[0] += rec->Width / sampling;
      v1[1] += rec->Height / sampling;
      /*      glColor4f(0.5F,0.5F,0.5F,1.0F); */
      if (shaderCGO){
          float *worldPos = TextGetWorldPos(G);
	  if (isworldlabel){
	    float *targetPos = TextGetTargetPos(G);
	    float *screenWorldOffset = TextGetScreenWorldOffset(G);
            shaderCGO->add<cgo::draw::label>(glm::make_vec3(worldPos),
                                             glm::make_vec3(screenWorldOffset),
                                             glm::make_vec3(v0),
                                             glm::make_vec3(v1),
                                             glm::make_vec4(rec->extent),
                                             relativeMode,
                                             glm::make_vec3(targetPos));
	  } else {
	    CGODrawTexture(shaderCGO, worldPos, v0, v1, rec->extent);
	  }
      } else {
#ifndef PURE_OPENGL_ES_2
	  glBegin(GL_QUADS);
        if (TextGetIsPicking(G)){
          unsigned char *cptr = TextGetColorUChar4uv(G);
          glColor4ubv(cptr);
          glVertex3f(v0[0], v0[1], v0[2]);
          glVertex3f(v0[0], v1[1], v0[2]);
          glVertex3f(v1[0], v1[1], v0[2]);
          glVertex3f(v1[0], v0[1], v0[2]);
          glEnd();
        } else {
	  glTexCoord2f(rec->extent[0], rec->extent[1]);
	  glVertex3f(v0[0], v0[1], v0[2]);
	  glTexCoord2f(rec->extent[0], rec->extent[3]);
	  glVertex3f(v0[0], v1[1], v0[2]);
	  glTexCoord2f(rec->extent[2], rec->extent[3]);
	  glVertex3f(v1[0], v1[1], v0[2]);
	  glTexCoord2f(rec->extent[2], rec->extent[1]);
	  glVertex3f(v1[0], v0[1], v0[2]);
	  glEnd();
        }
#endif
      }
    }
     TextAdvance(G, rec->Advance / sampling);
  } else {
    if (!isTextured)
      success = 0;
  }
  return success;
}

int CharacterGetWidth(PyMOLGlobals * G, int id)
{
  CCharacter *I = G->Character;
  if((id > 0) && (id <= I->MaxAlloc)) {
    return I->Char[id].Width;
  }
  return 0;
}

const float _inv255 = 1.0F / 255.0F;

const unsigned char zerouc[4] = { 0, 0, 0, 0 };


/* CharacterInterpolate: This function implements bilinear interpolation
   on looking up the pixel value in the texture.
*/
float CharacterInterpolate(PyMOLGlobals * G, int id, float *v)
{
  CCharacter *I = G->Character;
  int x = (int) v[0];
  int y = (int) v[1];

  if((id > 0) && (id <= I->MaxAlloc)) {
    CPixmap *pm = &I->Char[id].Pixmap;
    if(pm) {
      unsigned char *srcx0, *srcx1, *srcy0, *srcy1;
      int x1 = x + 1, y1 = y + 1;
      float xdiff = v[0] - x, ydiff = v[1] - y;
      float xdiff1 = (1.f-xdiff), ydiff1 = (1.f-ydiff);
      float interp0[4], interp1[4]; // interpolated in x for y0 and y1

      if (x < 0 || x > (pm->width - 1))
	srcx0 = (unsigned char *)zerouc;
      else
	srcx0 = pm->buffer + ((pm->width << 2) * y) + (x << 2);
      if (x1 < 0 || x1 > (pm->width - 1))
	srcx1 = (unsigned char *)zerouc;
      else
	srcx1 = pm->buffer + ((pm->width << 2) * y) + (x1 << 2);

      if (y1 < 0 || y1 > (pm->height - 1))
	srcy0 = (unsigned char *)zerouc;
      else
	srcy0 = pm->buffer + ((pm->width << 2) * y1) + (x << 2);

      if (x1 < 0 || x1 > (pm->width - 1) || y1 < 0 || y1 > (pm->height - 1))
	srcy1 = (unsigned char *)zerouc;
      else
	srcy1 = pm->buffer + ((pm->width << 2) * y1) + (x1 << 2);

      interp0[0] = (xdiff1 * (*(srcx0++)) + (xdiff * (*(srcx1++))));
      interp0[1] = (xdiff1 * (*(srcx0++)) + (xdiff * (*(srcx1++))));
      interp0[2] = (xdiff1 * (*(srcx0++)) + (xdiff * (*(srcx1++))));
      interp0[3] = (xdiff1 * (*(srcx0++)) + (xdiff * (*(srcx1++))));

      interp1[0] = (xdiff1 * (*(srcy0++)) + (xdiff * (*(srcy1++))));
      interp1[1] = (xdiff1 * (*(srcy0++)) + (xdiff * (*(srcy1++))));
      interp1[2] = (xdiff1 * (*(srcy0++)) + (xdiff * (*(srcy1++))));
      interp1[3] = (xdiff1 * (*(srcy0++)) + (xdiff * (*(srcy1++))));

      v[0] = ((ydiff1 * interp0[0]) + (ydiff * interp1[0])) * _inv255;
      v[1] = ((ydiff1 * interp0[1]) + (ydiff * interp1[1])) * _inv255;
      v[2] = ((ydiff1 * interp0[2]) + (ydiff * interp1[2])) * _inv255;
      return (255 - ((ydiff1 * interp0[3]) + (ydiff * interp1[3]))) * _inv255;
    } else {
      zero3f(v);
      return 1.0F;
    }
  }
  zero3f(v);
  return 1.0F;
}

int CharacterGetHeight(PyMOLGlobals * G, int id)
{
  CCharacter *I = G->Character;
  if((id > 0) && (id <= I->MaxAlloc)) {
    return I->Char[id].Height;
  }
  return 0;
}

int CharacterGetGeometry(PyMOLGlobals * G, int id,
                         int *width, int *height,
                         float *xorig, float *yorig, float *advance)
{
  CCharacter *I = G->Character;
  if((id > 0) && (id <= I->MaxAlloc)) {
    CharRec *ch = I->Char + id;
    *width = ch->Width;
    *height = ch->Height;
    *xorig = ch->XOrig;
    *yorig = ch->YOrig;
    *advance = ch->Advance;
  }
  return 0;
}

int CharacterInit(PyMOLGlobals * G)
{
  CCharacter *I = NULL;
  if((I = (G->Character = pymol::calloc<CCharacter>(1)))) {
    I->MaxAlloc = 5;
    I->Char = VLACalloc(CharRec, I->MaxAlloc + 1);
    {
      int a;
      for(a = 2; a <= I->MaxAlloc; a++)
        I->Char[a].Prev = a - 1;
      I->LastFree = I->MaxAlloc;
    }
    I->Hash = pymol::calloc<int>((HASH_MASK + 1));
    I->TargetMaxUsage = 25000;
    return 1;
  } else
    return 0;
}

static void CharacterAllocMore(PyMOLGlobals * G)
{
  CCharacter *I = G->Character;
  int new_max = I->MaxAlloc * 2;
  VLACheck(I->Char, CharRec, new_max);
  {
    int a;
    I->Char[I->MaxAlloc + 1].Prev = I->LastFree;
    for(a = I->MaxAlloc + 2; a <= new_max; a++)
      I->Char[a].Prev = a - 1;
    I->LastFree = new_max;
    I->MaxAlloc = new_max;
  }
}

void CharacterSetRetention(PyMOLGlobals * G, int retain_all)
{
  CCharacter *I = G->Character;
  I->RetainAll = retain_all;
}

static void CharacterPurgeOldest(PyMOLGlobals * G)
{
  CCharacter *I = G->Character;
  int max_kill = 10;

  while(I->NUsed > I->TargetMaxUsage) {
    if(!(max_kill--))
      break;                    /* if over, only purge a few entries at a time */
    {
      int id = I->OldestUsed;

      if(id) {
        int next;

        /* trim from end of list */

        if((next = I->Char[id].Next)) {
          I->Char[next].Prev = 0;
          I->OldestUsed = next;
        }

        {                       /* excise character from hash table linked list */
          int hash_code = I->Char[id].Fngrprnt.hash_code;
          int hash_prev = I->Char[id].HashPrev;
          int hash_next = I->Char[id].HashNext;

          if(hash_prev) {
            I->Char[hash_prev].HashNext = hash_next;
          } else {
            I->Hash[hash_code] = hash_next;
          }
          if(hash_next) {
            I->Char[hash_next].HashPrev = hash_prev;
          }

        }

        /* free and reinitialize */

        PixmapPurge(&I->Char[id].Pixmap);
        UtilZeroMem(I->Char + id, sizeof(CharRec));

        /* add to free chain */

        I->Char[id].Prev = I->LastFree;
        I->LastFree = id;
        I->NUsed--;
      }
    }
  }
}

int CharacterGetNew(PyMOLGlobals * G)
{
  CCharacter *I = G->Character;
  int result = 0;
  if(!I->LastFree)
    CharacterAllocMore(G);
  if(I->LastFree) {

    /* remove from free chain */
    result = I->LastFree;
    I->LastFree = I->Char[result].Prev;

    /* backwards-link (for continuous GC) */

    if(I->NewestUsed) {
      I->Char[I->NewestUsed].Next = result;     /* double-link list */
    } else {
      I->OldestUsed = result;
    }

    /* forwards-link */

    I->Char[result].Prev = I->NewestUsed;
    I->NewestUsed = result;

    I->NUsed++;

    if(!I->RetainAll)
      CharacterPurgeOldest(G);
  }

  return result;
}

void CharacterFree(PyMOLGlobals * G)
{
  CCharacter *I = G->Character;
  {
    int a;
    a = I->NewestUsed;
    while(a) {
      PixmapPurge(&I->Char[a].Pixmap);
      a = I->Char[a].Prev;
    }
  }
  FreeP(I->Hash);
  VLAFreeP(I->Char);
  FreeP(G->Character);
}
