
/* 
A* -------------------------------------------------------------------
B* This file contains source code for the PyMOL computer program
C* copyright 1998-2003 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 <algorithm>

#include"os_python.h"

#include "MemoryDebug.h"
#include "os_gl.h"
#include "FontType.h"
#include "Text.h"
#include "Ortho.h"
#include "Scene.h"
#include "Character.h"
#include "Util.h"
#include "FontType.h"

#define max2 std::max

static void CheckUnicode(unsigned int *c, int *unicnt, int *unicode){
  if(*unicnt) {
    if(!(*c & 0x80))   /* corrupt UTF8 */
      *unicnt = 0;
    else {
      *unicode = ((*unicode) << 6) | (0x3F & *c);
      (*unicnt)--;
      *c = *unicode;
    }
  } else if(*c & 0x80) {
    while(*c & 0x80) {
      *c = (*c << 1) & 0xFF;
      (*unicnt)++;
    }
    *unicode = (*c >> ((*unicnt)--));
  }
}

static void GenerateCharFngrprnt(PyMOLGlobals *G, CharFngrprnt *fprnt, unsigned int c, int TextID, float size, int sampling, short no_flat, int flat){
  unsigned char *rgba;
  UtilZeroMem(fprnt, sizeof(CharFngrprnt));
  fprnt->u.i.text_id = TextID;
  fprnt->u.i.size = (int) (size * 64 * sampling);
  rgba = fprnt->u.i.color;
  if (!TextGetIsPicking(G)){
    TextGetColorUChar(G, rgba, rgba + 1, rgba + 2, rgba + 3);
    rgba = fprnt->u.i.outline_color;
    if (no_flat || !flat){
      TextGetOutlineColor(G, rgba, rgba + 1, rgba + 2, rgba + 3);
    } else if(flat) {
      TextGetColorUChar(G, rgba, rgba + 1, rgba + 2, rgba + 3);
    }
  }
  fprnt->u.i.ch = c;
  fprnt->u.i.flat = flat;
}

static const char* FontTypeRenderOpenGLImpl(const RenderInfo* info, CFontType* I,
    const char* st, float size, int flat, const float* rpos, bool needSize,
    short relativeMode, bool shouldRender, CGO* shaderCGO)
{
  PyMOLGlobals *G = I->G;
  if(G->ValidContext) {
    unsigned int c;
    int pushed = OrthoGetPushed(G);
    int kern_flag = false;
    unsigned int last_c = 0;
    int sampling = 1;
    const float _0 = 0.0F, _1 = 1.0F, _m1 = -1.0F;
    float x_indent = 0.0F, y_indent = 0.0F, z_indent = 0.0F;
    float text_width = 0.f, line_width = 0.f, tot_text_width;
    float text_just = 1.f - TextGetJustification(G);
    float text_spacing = TextGetSpacing(G);
    float text_buffer[2];
    int unicode = 0;
    int unicnt = 0;
    int nlines = countchrs(st, '\n') + 1;
    int linenum = 0;
    short cont = 0;
    float descender = TypeFaceGetDescender(I->TypeFace) / 2.f;
    float v_scale = SceneGetScreenVertexScale(G, nullptr);
    copy2f(TextGetLabelBuffer(G), text_buffer);
    sampling = info->sampling;
    if(st && (*st)) {
      float *line_widths = nullptr;
      float screenWorldOffset[3] = { 0.0F, 0.0F, 0.0F };
      float tot_height;
      if (nlines>1){
	line_widths = pymol::calloc<float>(nlines);
      }
      if(size < _0) {
        size = (int) (0.5F - size / v_scale);
        if (size <= 0)
          size = 1;
      } else {
        size = DIP2PIXEL(size);
      }

      text_buffer[0] *= size;
      text_buffer[1] *= size;

      if(rpos) {
	TextSetIndentFactorX(G, rpos[0] < _m1 ? _m1 : rpos[0] > 1.f ? 1.f : rpos[0]);
	TextSetIndentFactorY(G, rpos[1] < _m1 ? _m1 : rpos[1] > 1.f ? 1.f : rpos[1]);

        if(needSize || rpos[0] < _1) {      /* we need to measure the string width before starting to draw */
          const char *sst = st;
          while((c = *(sst++))) {
	    if (c == '\n'){
	      line_widths[linenum] = line_width;
	      text_width = max2(text_width, line_width);
	      line_width = 0.f;
	      linenum++;
	      continue;
	    }
	    CheckUnicode(&c, &unicnt, &unicode);

            if(!unicnt) {
              CharFngrprnt fprnt;
	      GenerateCharFngrprnt(G, &fprnt, c, I->TextID, size, sampling, 0, flat);
              {
                int id = CharacterFind(G, &fprnt);
                if(!id) {
                  id = TypeFaceCharacterNew(I->TypeFace, &fprnt, size * sampling);
                }
                if(id) {
                  if(kern_flag) {
                    line_width += (TypeFaceGetKerning(I->TypeFace,
							       last_c, c, size) / sampling);
                  }
                  line_width += CharacterGetAdvance(G, sampling, id);
                }
              }
              kern_flag = true;
              last_c = c;
            }
          }
	  if (line_widths)
	    line_widths[linenum] = line_width;
	  text_width = max2(text_width, line_width) ;
	  tot_text_width = text_width + 2.f * text_buffer[0];
	  TextSetWidth(G, tot_text_width);
	  tot_height = pymol_roundf(size * (nlines + (nlines-1) * (text_spacing-1.f)) + 2.f * text_buffer[1]);
	  TextSetHeight(G, tot_height);
	  {
	    float factor = rpos[0] / 2.0F - 0.5F;
	    /* if -1. < rpos[0] < 1. , then we need to determine the label's width
	       so that we can justify it appropriately */
	    // if rpos[0] < -1., right justified
	    // if rpos[0] > 1., left justified, label width not needed
	    factor = (factor < _m1) ? _m1 : (factor > _0) ? _0 : factor;
	    x_indent -= factor * tot_text_width;
	  }
        }
	/* if label_position x is -1 to 1, the label is placed in x such that
	   0 - centered 
	   -1 - right justified on projected point
	    1 - left justified on projected point
	*/
	if(rpos[0] < _m1) {
	  screenWorldOffset[0] -= (rpos[0] + _1);
	} else if(rpos[0] > _1) {
	  screenWorldOffset[0] -= (rpos[0] - _1);
	}
        if(rpos[1] < _1) {
          float factor = -rpos[1] / 2.0F + 0.5F;
	  factor = (factor > _1) ? _1 : (factor < _0) ? _0 : factor;
	  y_indent = pymol_roundf((size * factor) * (1.f + text_spacing * max2(0.f, nlines-1.f)));
	}
	{
          float factor = rpos[1];
	  factor = (factor > _1) ? _1 : (factor < _m1) ? _m1 : factor;
	  y_indent -= pymol_roundf(factor * text_buffer[1]);
        }
        if(rpos[1] < _m1) {
          screenWorldOffset[1] -= (rpos[1] + _1);
        } else if(rpos[1] > _1) {
          screenWorldOffset[1] -= (rpos[1] - _1);
        }
	/* leave room for fonts of finite depth */
	z_indent = (rpos[2] < _m1) ? (rpos[2]+1.f) : (rpos[2] > 1.f) ? (rpos[2]-1.f) : 0.f;
	if (!shaderCGO){
	  x_indent += screenWorldOffset[0] / v_scale;
	  y_indent += pymol_roundf(screenWorldOffset[1] / v_scale);
	}
	screenWorldOffset[2] += z_indent; // need to take into account weird -1 to 1, and sub 1 from abs val 
      }
      if(!pushed) {
        float *v = TextGetPos(G);
        float loc[3];
        float zero[3] = { 0.0F, 0.0F, 0.0F };
	TextSetScreenWorldOffset(G, screenWorldOffset);
        TextSetWorldPos(G, v);

        if(rpos) {
          if(info->ortho) {
            float orig[3];
            SceneOriginGet(G, orig);
            SceneGetEyeNormal(G, orig, loc);
          } else {
            SceneGetEyeNormal(G, v, loc);
          }
          scale3f(loc, z_indent, loc);
          add3f(v, loc, loc);
          v = loc;
        }
	if (!shaderCGO){
	  unsigned char posIsSet = TextGetLabelPosIsSet(G);
	  switch (posIsSet){
	  case 1:
	    SceneAdjustZtoScreenZ(G, v, TextGetLabelPos(G)[0]);
	    break;
	  case 2:
	    SceneSetPointToWorldScreenRelative(G, v, TextGetLabelPos(G));
	    break;
	  }
	}
	TextSetLabelPushPos(G, v);
	if (!shouldRender)
	  return st;
        ScenePushRasterMatrix(G, v);
        TextSetPos(G, zero);
#ifndef PURE_OPENGL_ES_2
      } else {
#endif
	if (!shouldRender)
	  return st;
      }
      if(rpos) {
        TextIndent(G, x_indent, y_indent);
      }
      CharacterRenderOpenGLPrime(G, info);
      kern_flag = false;
      TextGetPos(G)[1] += (int)pymol_roundf(size * (text_spacing * (nlines-1) + descender));
      if (line_widths){
	TextGetPos(G)[0] += text_just * (text_width - line_widths[0])/2.f;
      }
      TextGetPos(G)[0] += text_buffer[0];
      linenum = 0;
      cont = 1;
      while(cont && (c = *(st++))) {
	if (c == '\n'){
	  float zero[3] = { 0.0F, 0.0F, 0.0F };
	  TextSetPos(G, zero);
	  if (rpos){
	    TextIndent(G, x_indent, y_indent);
	  }
	  linenum++;
	  TextGetPos(G)[1] += (int)pymol_roundf(size * (text_spacing * (nlines - 1 - linenum) + descender));
	  if (line_widths)
	    TextGetPos(G)[0] += text_just * (text_width - line_widths[linenum])/2.f + text_buffer[0];
	  kern_flag = false;
	  continue;
	}
	CheckUnicode(&c, &unicnt, &unicode);
        if(!unicnt) {
          CharFngrprnt fprnt;
	  GenerateCharFngrprnt(G, &fprnt, c, I->TextID, size, sampling, 1, flat);
          {
            int id = CharacterFind(G, &fprnt);
            if(!id) {
              id = TypeFaceCharacterNew(I->TypeFace, &fprnt, size * sampling);
            }
            if(id) {
              if(kern_flag) {
                TextAdvance(G, TypeFaceGetKerning(I->TypeFace,
                                                  last_c, c, size) / sampling);
              }
              cont &= CharacterRenderOpenGL(G, info, id, true, relativeMode, shaderCGO);       /* handles advance */
            }
          }
          kern_flag = true;
          last_c = c;
        }
      }
      CharacterRenderOpenGLDone(G, info);
      if(!pushed) {
        ScenePopRasterMatrix(G);
      }
      FreeP(line_widths);
    }
    if (!cont)
      return st;
  }
  return st;
}

const char* CFontType::RenderOpenGL(const RenderInfo* info, const char* st,
    float size, const float* rpos, bool needSize, short relativeMode,
    bool shouldRender, CGO* shaderCGO)
{
  return FontTypeRenderOpenGLImpl(info, this, st, size, false, rpos, needSize,
      relativeMode, shouldRender, shaderCGO);
}

const char* CFontType::RenderOpenGLFlat(const RenderInfo* info, const char* st,
    float size, const float* rpos, bool needSize, short relativeMode,
    bool shouldRender, CGO* shaderCGO)
{
  return FontTypeRenderOpenGLImpl(info, this, st, size, true, rpos, needSize,
      relativeMode, shouldRender, shaderCGO);
}

const char* CFontType::RenderRay(CRay* ray, const char* st, float size,
    const float* rpos, bool needSize, short relativeMode)
{
  auto I = this;
  PyMOLGlobals *G = I->G;
  unsigned int c;
  int kern_flag = false;
  unsigned int last_c = 0;
  int sampling = ray->Sampling;
  const float _0 = 0.0F, _1 = 1.0F, _m1 = -1.0F;
  float x_indent = 0.0F, y_indent = 0.0F, z_indent = 0.0F;
  float text_width = 0.f, line_width = 0.f, tot_text_width;
  float text_just = 1.f - TextGetJustification(G);
  float text_spacing = TextGetSpacing(G);
  float text_buffer[2];
  float xn[3], yn[3], x_adj[3], y_adj[3], pos[3], *v;
  int unicode = 0;
  int unicnt = 0;
  int nlines = countchrs(st, '\n') + 1;
  int linenum = 0;
  float descender = TypeFaceGetDescender(I->TypeFace);
  float v_scale = SceneGetScreenVertexScale(G, nullptr);
  copy2f(TextGetLabelBuffer(G), text_buffer);
  if(st && (*st)) {
    float origpos[3];
    float *line_widths = nullptr;
    float tot_height;
    if (nlines>1){
      line_widths = pymol::calloc<float>(nlines);
    }
    if(size < _0) {
      size = (int) (0.5F - size / v_scale);
    } else {
      size = DIP2PIXEL(size);
    }

    text_buffer[0] *= size;
    text_buffer[1] *= size;

    if(rpos) {
      float loc[3];
      /* leave room for fonts of finite depth */
      z_indent = (rpos[2] < _m1) ? (rpos[2]+1.f) : (rpos[2] > 1.f) ? (rpos[2]-1.f) : 0.f;
      v = TextGetPos(I->G);
      if(ray->Ortho) {
        float orig[3];
        SceneOriginGet(G, orig);
        SceneGetEyeNormal(G, orig, loc);
      } else {
        SceneGetEyeNormal(G, v, loc);
      }
      scale3f(loc, z_indent, loc);
      add3f(v, loc, loc);

      {
	unsigned char posIsSet = TextGetLabelPosIsSet(G);
	switch (posIsSet){
	case 1:
	  RayAdjustZtoScreenZ(ray, loc, TextGetLabelPos(G)[0]);
	  break;
	case 2:
	  RaySetPointToWorldScreenRelative(ray, loc, TextGetLabelPos(G));
	  break;
	}
      }
      TextSetLabelPushPos(G, loc);
      TextSetPos(I->G, loc);
    }

    RayGetScaledAxes(ray, xn, yn);

    if(rpos) {
      if(needSize || rpos[0] < _1) {        /* we need to measure the string width before starting to draw */
        float factor = rpos[0] / 2.0F - 0.5F;
        const char *sst = st;
	float max_x_indent = 0.f;
	// factor -1 to 0 based on justification (i.e., rpos[0])
	factor = (factor < _m1) ? _m1 : (factor > _0) ? _0 : factor;
        while((c = *(sst++))) {
	  if (c == '\n'){
	    text_width = max2(text_width, line_width);
	    line_widths[linenum] = line_width;
	    line_width = 0.f;
	    kern_flag = false;
	    linenum++;
	    max_x_indent = max2(x_indent, max_x_indent);
	    x_indent = 0.f;
	    continue;
	  }
	  CheckUnicode(&c, &unicnt, &unicode);
          if(!unicnt) {
            CharFngrprnt fprnt;
	    GenerateCharFngrprnt(G, &fprnt, c, I->TextID, size, sampling, 1, 0 /* flat is not set */);
            {
              int id = CharacterFind(G, &fprnt);
              if(!id) {
                id = TypeFaceCharacterNew(I->TypeFace, &fprnt, size * sampling);
              }
              if(id) {
		float adv;
                if(kern_flag) {
		  float kern = TypeFaceGetKerning(I->TypeFace,
						  last_c,
						  c, size * sampling) ;
		  line_width += kern;
                  x_indent -= factor * kern;
                }
		adv = CharacterGetAdvance(G, 1, id);
		line_width += adv;
                x_indent -= factor * adv;
                kern_flag = true;
                last_c = c;
              }
            }
          }
        }
	max_x_indent = max2(x_indent, max_x_indent);
	x_indent = max_x_indent;
      }
      text_width = max2(text_width, line_width);
      tot_text_width = text_width + 2.f * text_buffer[0] * sampling;
      if (line_widths)
	line_widths[linenum] = line_width;
      TextSetWidth(G, tot_text_width/(float)sampling);
      tot_height = size * (nlines + (nlines-1) * (text_spacing-1.f)) + 2.f * text_buffer[1];
      TextSetHeight(G, tot_height);
      if(rpos[0] < _m1) {
        x_indent -= 2 * (rpos[0] + _1) / v_scale;
      } else if(rpos[0] > _1) {
        x_indent -= 2 * (rpos[0] - _1) / v_scale;
      }
      if(rpos[1] < _1) {
        float factor = -rpos[1] / 2.0F + 0.5F;
        factor = (factor > _1) ? _1 : (factor < _0) ? _0 : factor;
	y_indent = (sampling * size * factor) * (1.f + text_spacing * max2(0.f, nlines-1.f));
      }
      {
	float factor = rpos[1];
	factor = (factor > _1) ? _1 : (factor < _m1) ? _m1 : factor;
	y_indent -= factor * sampling * text_buffer[1];
      }
      if(rpos[1] < _m1) {
        y_indent -= 2 * (rpos[1] + _1) / v_scale;
      } else if(rpos[1] > _1) {
        y_indent -= 2 * (rpos[1] - _1) / v_scale;
      }
      v = TextGetPos(I->G);
      if (line_widths){
	x_indent -= text_just * (text_width - line_widths[0])/2.f;
      }
      {
	float factor = rpos[0];
	factor = (factor > _1) ? _1 : (factor < _m1) ? _m1 : factor;
	x_indent -= factor * sampling * text_buffer[0];
      }
      scale3f(xn, x_indent, x_adj);
      scale3f(yn, y_indent - size * (sampling * text_spacing * (nlines-1) + descender), y_adj);
      subtract3f(v, x_adj, pos);
      subtract3f(pos, y_adj, pos);
      TextSetPos(I->G, pos);
    }
    kern_flag = false;
    copy3f(TextGetPos(I->G), origpos);
    linenum = 0;
    while((c = *(st++))) {
      if (c == '\n'){
	copy3f(origpos, TextGetPos(I->G));
	kern_flag = false;
	linenum++;
	scale3f(yn, pymol_roundf(text_spacing * size * linenum * sampling), y_adj); // need to round to pixel in y
	subtract3f(TextGetPos(G), y_adj, TextGetPos(G));
	if (line_widths){
	  scale3f(xn, text_just * (line_widths[0] - line_widths[linenum])/2.f, x_adj);
	  add3f(TextGetPos(G), x_adj, TextGetPos(G));
	}
	kern_flag = false;
	continue;
      }
      CheckUnicode(&c, &unicnt, &unicode);
      if(!unicnt) {
        CharFngrprnt fprnt;
	GenerateCharFngrprnt(G, &fprnt, c, I->TextID, size, sampling, 0, 0);
        {
          int id = CharacterFind(G, &fprnt);
          if(!id) {
            id = TypeFaceCharacterNew(I->TypeFace, &fprnt, size * sampling);
          }
          if(id) {
            if(kern_flag) {
              float kern = TypeFaceGetKerning(I->TypeFace,
                                              last_c,
                                              c,
                                              size * sampling);
              v = TextGetPos(I->G);
              scale3f(xn, kern, x_adj);
              add3f(v, x_adj, pos);
              TextSetPos(I->G, pos);
            }
            ray->character(id);   /* handles advance */

            kern_flag = true;
            last_c = c;
          }
        }
      }
    }
    FreeP(line_widths);
  }
  return st;
}

CFontType::~CFontType()
{
  if (TypeFace) {
    TypeFaceFree(TypeFace);
  }
}

CFontType::CFontType(PyMOLGlobals* G, unsigned char* dat, unsigned int len)
    : CFont(G)
{
  TypeFace = TypeFaceLoad(G, dat, len);
}

CFont* FontTypeNew(PyMOLGlobals* G, unsigned char* dat, unsigned int len)
{
  auto fontType = new CFontType(G, dat, len);
  if (!fontType->TypeFace) {
    DeleteP(fontType);
  }
  return fontType;
}
