
/*
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_python.h"

#include "os_gl.h"
#include "os_predef.h"
#include "os_std.h"

#include "Base.h"
#include "CarveHelper.h"
#include "Color.h"
#include "Err.h"
#include "Executive.h"
#include "Feedback.h"
#include "Isosurf.h"
#include "Matrix.h"
#include "MemoryDebug.h"
#include "ObjectCGO.h"
#include "ObjectMesh.h"
#include "P.h"
#include "PConv.h"
#include "Parse.h"
#include "Scene.h"
#include "Setting.h"
#include "ShaderMgr.h"
#include "Vector.h"
#include "main.h"

static void ObjectMeshRecomputeExtent(ObjectMesh* I);

static PyObject* ObjectMeshStateAsPyList(ObjectMeshState* I)
{
  PyObject* result = nullptr;

  result = PyList_New(17);

  PyList_SetItem(result, 0, PyInt_FromLong(I->Active));
  PyList_SetItem(result, 1, PyString_FromString(I->MapName));
  PyList_SetItem(result, 2, PyInt_FromLong(I->MapState));
  PyList_SetItem(result, 3, CrystalAsPyList(&I->Crystal));
  PyList_SetItem(result, 4, PyInt_FromLong(I->ExtentFlag));
  PyList_SetItem(result, 5, PConvFloatArrayToPyList(I->ExtentMin, 3));
  PyList_SetItem(result, 6, PConvFloatArrayToPyList(I->ExtentMax, 3));
  PyList_SetItem(result, 7, PConvIntArrayToPyList(I->Range, 6));
  PyList_SetItem(result, 8, PyFloat_FromDouble(I->Level));
  PyList_SetItem(result, 9, PyFloat_FromDouble(I->Radius));
  PyList_SetItem(result, 10, PyInt_FromLong(I->CarveFlag));
  PyList_SetItem(result, 11, PyFloat_FromDouble(I->CarveBuffer));
  if (I->CarveFlag && I->AtomVertex) {
    PyList_SetItem(result, 12, PConvFloatVLAToPyList(I->AtomVertex));
  } else {
    PyList_SetItem(result, 12, PConvAutoNone(nullptr));
  }
  PyList_SetItem(result, 13, PyInt_FromLong(static_cast<int>(I->MeshMode)));
  PyList_SetItem(result, 14, PyFloat_FromDouble(I->AltLevel));
  PyList_SetItem(result, 15, PyInt_FromLong(I->quiet));
  if (I->Field) {
    PyList_SetItem(result, 16, IsosurfAsPyList(I->G, I->Field.get()));
  } else {
    PyList_SetItem(result, 16, PConvAutoNone(nullptr));
  }
  return (PConvAutoNone(result));
}

static int ObjectMeshStateMapExists(ObjectMesh* I, ObjectMeshState* ms)
{
  return ExecutiveFindObjectMapByName(I->G, ms->MapName) ? 1 : 0;
}

int ObjectMeshAllMapsInStatesExist(ObjectMesh* I)
{
  int a;
  for (a = 0; a < I->NState; a++) {
    if (I->State[a].Active) {
      if (!ObjectMeshStateMapExists(I, &I->State[a])) {
        return 0;
      }
    }
  }
  return 1;
}

static PyObject* ObjectMeshAllStatesAsPyList(ObjectMesh* I)
{

  PyObject* result = nullptr;
  int a;
  result = PyList_New(I->NState);
  for (a = 0; a < I->NState; a++) {
    if (I->State[a].Active) {
      PyList_SetItem(result, a, ObjectMeshStateAsPyList(&I->State[a]));
    } else {
      PyList_SetItem(result, a, PConvAutoNone(nullptr));
    }
  }
  return (PConvAutoNone(result));
}

static int ObjectMeshStateFromPyList(
    PyMOLGlobals* G, ObjectMeshState* I, PyObject* list)
{
  int ok = true;
  int ll = 0;
  PyObject* tmp;
  if (ok)
    ok = (list != nullptr);
  if (ok) {
    if (!PyList_Check(list))
      I->Active = false;
    else {
      *I = ObjectMeshState(G);
      if (ok)
        ok = (list != nullptr);
      if (ok)
        ok = PyList_Check(list);
      if (ok)
        ll = PyList_Size(list);
      /* TO SUPPORT BACKWARDS COMPATIBILITY...
         Always check ll when adding new PyList_GetItem's */

      if (ok)
        ok = PConvPyIntToInt(PyList_GetItem(list, 0), &I->Active);
      if (ok)
        ok = PConvPyStrToStr(PyList_GetItem(list, 1), I->MapName, WordLength);
      if (ok)
        ok = PConvPyIntToInt(PyList_GetItem(list, 2), &I->MapState);
      if (ok)
        ok = CrystalFromPyList(&I->Crystal, PyList_GetItem(list, 3));
      if (ok)
        ok = PConvPyIntToInt(PyList_GetItem(list, 4), &I->ExtentFlag);
      if (ok)
        ok = PConvPyListToFloatArrayInPlace(
            PyList_GetItem(list, 5), I->ExtentMin, 3);
      if (ok)
        ok = PConvPyListToFloatArrayInPlace(
            PyList_GetItem(list, 6), I->ExtentMax, 3);
      if (ok)
        ok = PConvPyListToIntArrayInPlace(PyList_GetItem(list, 7), I->Range, 6);
      if (ok)
        ok = PConvPyFloatToFloat(PyList_GetItem(list, 8), &I->Level);
      if (ok)
        ok = PConvPyFloatToFloat(PyList_GetItem(list, 9), &I->Radius);
      if (ok)
        ok = PConvPyIntToInt(PyList_GetItem(list, 10), &I->CarveFlag);
      if (ok)
        ok = PConvPyFloatToFloat(PyList_GetItem(list, 11), &I->CarveBuffer);
      if (ok) {
        tmp = PyList_GetItem(list, 12);
        if (tmp == Py_None)
          I->AtomVertex = nullptr;
        else
          ok = PConvPyListToFloatVLA(tmp, &I->AtomVertex);
      }
      if (ok)
        ok = PConvFromPyListItem(G, list, 13, I->MeshMode);
      if (ok) {
        I->RefreshFlag = true;
        I->ResurfaceFlag = true;
      }
      if (ok && (ll > 14)) {
        ok = PConvPyFloatToFloat(PyList_GetItem(list, 14), &I->AltLevel);
      } else {
        I->AltLevel = I->Level;
      }
      if (ok && (ll > 15)) {
        ok = PConvPyIntToInt(PyList_GetItem(list, 15), &I->quiet);
      } else {
        I->quiet = true;
      }
      if (ok && (ll > 16)) {
        tmp = PyList_GetItem(list, 16);
        if (tmp == Py_None)
          I->Field = nullptr;
        else {
          I->Field.reset(IsosurfNewFromPyList(G, tmp));
          ok = I->Field != nullptr;
        }
        CPythonVal_Free(tmp);
      }
    }
  }
  return (ok);
}

static int ObjectMeshAllStatesFromPyList(ObjectMesh* I, PyObject* list)
{

  int ok = true;
  int a;
  VecCheckEmplace(I->State, I->NState, I->G);
  if (ok)
    ok = PyList_Check(list);
  if (ok) {
    for (a = 0; a < I->NState; a++) {
      auto* el = PyList_GetItem(list, a);
      ok = ObjectMeshStateFromPyList(I->G, &I->State[a], el);
      if (!ok)
        break;
    }
  }
  return (ok);
}

int ObjectMeshNewFromPyList(
    PyMOLGlobals* G, PyObject* list, ObjectMesh** result)
{
  int ok = true;
  ObjectMesh* I = nullptr;
  (*result) = nullptr;

  if (ok)
    ok = (list != nullptr);
  if (ok)
    ok = PyList_Check(list);
  /* TO SUPPORT BACKWARDS COMPATIBILITY...
     Always check ll when adding new PyList_GetItem's */

  I = new ObjectMesh(G);
  CHECKOK(ok, I);

  if (ok)
    ok = ObjectFromPyList(G, PyList_GetItem(list, 0), I);
  if (ok)
    ok = PConvPyIntToInt(PyList_GetItem(list, 1), &I->NState);
  if (ok)
    ok = ObjectMeshAllStatesFromPyList(I, PyList_GetItem(list, 2));
  if (ok) {
    (*result) = I;
    ObjectMeshRecomputeExtent(I);
  } else {
    DeleteP(I);
    (*result) = nullptr;
  }
  return (ok);
}

static CGO* ObjectMeshRenderImpl(
    ObjectMesh* I, RenderInfo* info, bool returnCGO, int stateArg);

PyObject* ObjectMeshAsPyList(ObjectMesh* I)
{
  PyObject* result = nullptr;

  int allMapsExist = ObjectMeshAllMapsInStatesExist(I);

  if (allMapsExist) {
    result = PyList_New(3);
    PyList_SetItem(result, 0, ObjectAsPyList(I));
    PyList_SetItem(result, 1, PyInt_FromLong(I->NState));
    PyList_SetItem(result, 2, ObjectMeshAllStatesAsPyList(I));
  } else {
    /* save ObjectMesh as ObjectCGO */
    ObjectCGO* retObjectCGO = new ObjectCGO(I->G);
    ObjectCopyHeader(retObjectCGO, I);
    retObjectCGO->type = cObjectCGO;

    int a;
    PRINTFB(I->G, FB_ObjectMesh, FB_Errors)
    "ObjectMesh-Warning: map has been deleted, saving as CGO.\n" ENDFB(I->G);
    for (a = 0; a < I->NState; a++) {
      CGO* cgo = ObjectMeshRenderImpl(I, nullptr, true, a);
      retObjectCGO = ObjectCGOFromCGO(I->G, retObjectCGO, cgo, a);
    }
    ObjectSetRepVisMask(retObjectCGO, cRepCGOBit, cVis_AS);
    result = ObjectCGOAsPyList(retObjectCGO);
    DeleteP(retObjectCGO);
  }
  return (PConvAutoNone(result));
}

int ObjectMeshInvalidateMapName(
    ObjectMesh* I, const char* name, const char* new_name)
{
  int a;
  ObjectMeshState* ms;
  int result = false;
  for (a = 0; a < I->NState; a++) {
    ms = &I->State[a];
    if (ms->Active) {
      if (strcmp(ms->MapName, name) == 0) {
        if (new_name)
          strcpy(ms->MapName, new_name);
        I->invalidate(cRepAll, cRepInvAll, a);
        result = true;
      }
    }
  }
  return result;
}

void ObjectMeshDump(ObjectMesh* I, const char* fname, int state, int quiet)
{
  float* v;
  int* n;
  int c;
  FILE* f;
  f = fopen(fname, "wb");
  if (!f) {
    ErrMessage(I->G, "ObjectMeshDump", "can't open file for writing");
  } else {
    if (state < I->NState) {
      n = I->State[state].N.data();
      v = I->State[state].V.data();
      if (n && v)
        while (*n) {
          c = *(n++);
          if (I->State[state].MeshMode == cIsomeshMode::isomesh) {
            fprintf(f, "\n");
          }
          while (c--) {
            fprintf(f, "%10.4f%10.4f%10.4f\n", v[0], v[1], v[2]);
            v += 3;
          }
        }
    }
    fclose(f);
    if (!quiet) {
      PRINTFB(I->G, FB_ObjectMesh, FB_Actions)
      " ObjectMeshDump: %s written to %s\n", I->Name, fname ENDFB(I->G);
    }
  }
}

void ObjectMesh::invalidate(cRep_t rep, cRepInv_t level, int state)
{
  auto I = this;
  if (level >= cRepInvExtents) {
    I->ExtentFlag = false;
  }
  if ((rep == cRepMesh) || (rep == cRepAll) || (rep == cRepCell)) {

    for (StateIterator iter(I->G, nullptr, state, I->NState); iter.next();) {
      ObjectMeshState* ms = &I->State[iter.state];

      ms->shaderCGO.reset();
      ms->shaderUnitCellCGO.reset();

      ms->RefreshFlag = true;
      if (level >= cRepInvAll) {
        ms->ResurfaceFlag = true;
        SceneChanged(I->G);
      } else if (level >= cRepInvColor) {
        ms->RecolorFlag = true;
        SceneChanged(I->G);
      } else {
        SceneInvalidate(I->G);
      }
    }
  }
}

pymol::Result<float> ObjectMeshGetLevel(ObjectMesh* I, int state)
{
  if (state >= I->NState) {
    return pymol::make_error("Invalid Mesh state");
  } else {
    if (state < 0) {
      state = 0;
    }
    auto ms = &I->State[state];
    if (ms->Active) {
      return ms->Level;
    } else {
      return pymol::make_error("Invalid Mesh state");
    }
  }
}

int ObjectMeshSetLevel(ObjectMesh* I, float level, int state, int quiet)
{
  int ok = true;
  if (state >= I->NState) {
    ok = false;
  } else {
    for (StateIterator iter(I->G, nullptr, state, I->NState); iter.next();) {
      ObjectMeshState* ms = &I->State[iter.state];
      if (ms->Active) {
        ms->ResurfaceFlag = true;
        ms->RefreshFlag = true;
        ms->Level = level;
        ms->quiet = quiet;
      }
    }
  }
  return (ok);
}

static void ObjectMeshStateUpdateColors(ObjectMesh* I, ObjectMeshState* ms)
{
  int one_color_flag = true;
  int cur_color = -1;

  if (ms->MeshMode == cIsomeshMode::isomesh) {
    cur_color =
        SettingGet_color(I->G, I->Setting.get(), nullptr, cSetting_mesh_color);
  } else if (ms->MeshMode == cIsomeshMode::isodot) {
    cur_color =
        SettingGet_color(I->G, I->Setting.get(), nullptr, cSetting_dot_color);
  }

  if (cur_color == -1)
    cur_color = I->Color;

  if (ColorCheckRamped(I->G, cur_color))
    one_color_flag = false;

  ms->OneColor = cur_color;
  if (ms->V) {
    int ramped_flag = false;
    float* v = ms->V.data();
    float* vc;
    int* rc;
    int a;
    int state = ms - I->State.data();
    int n_vert = VLAGetSize(ms->V) / 3;
    int base_n_vert = ms->base_n_V / 3;

    if (!ms->VC.empty() && (ms->VCsize < n_vert)) {
      ms->VC.clear();
      ms->RC.clear();
    }

    if (ms->VC.empty()) {
      ms->VCsize = n_vert;
      ms->VC = std::vector<float>(n_vert * 3);
    }
    if (ms->RC.empty()) {
      ms->RC = std::vector<int>(n_vert);
    }
    rc = ms->RC.data();
    vc = ms->VC.data();
    if (vc) {
      for (a = 0; a < n_vert; a++) {
        if (a == base_n_vert) {
          int new_color = SettingGet_color(
              I->G, I->Setting.get(), nullptr, cSetting_mesh_negative_color);
          if (new_color == -1)
            new_color = cur_color;
          if (new_color != cur_color) {
            one_color_flag = false;
            cur_color = new_color;
          }
        }
        if (ColorCheckRamped(I->G, cur_color)) {
          ColorGetRamped(I->G, cur_color, v, vc, state);
          *rc = cur_color;
          ramped_flag = true;
        } else {
          const float* col = ColorGet(I->G, cur_color);
          copy3f(col, vc);
        }
        rc++;
        vc += 3;
        v += 3;
      }
    }

    if (one_color_flag && (!ramped_flag)) {
      ms->VC.clear();
      ms->RC.clear();
    } else if ((!ramped_flag) || (!SettingGet_b(I->G, nullptr, I->Setting.get(),
                                     cSetting_ray_color_ramps))) {
      ms->RC.clear();
    }
  }
}

void ObjectMesh::update()
{
  auto I = this;
  int a;
  int c;
  ObjectMeshState* ms;
  ObjectMapState* oms = nullptr;
  ObjectMap* map = nullptr;

  int* n;
  float* v;
  int n_cur;
  int n_seg;
  int n_line;
  int flag;
  int last_flag = 0;
  int mesh_skip =
      SettingGet_i(G, I->Setting.get(), nullptr, cSetting_mesh_skip);

  for (a = 0; a < I->NState; a++) {
    ms = &I->State[a];
    if (ms->Active) {

      map = ExecutiveFindObjectMapByName(I->G, ms->MapName);
      if (!map) {
        PRINTFB(I->G, FB_ObjectMesh, FB_Errors)
        "ObjectMeshUpdate-Error: map '%s' has been deleted.\n",
            ms->MapName ENDFB(I->G);
        ms->ResurfaceFlag = false;
      }
      if (map) {
        oms = ObjectMapGetState(map, ms->MapState);
      }
      if (oms) {
        if (ms->RefreshFlag || ms->ResurfaceFlag) {
          if (!ms->Field) {
            ms->Crystal = oms->Symmetry->Crystal;
          }

          if ((I->visRep & cRepCellBit)) {
            ms->UnitCellCGO.reset(CrystalGetUnitCellCGO(&ms->Crystal));
          }

          if (!oms->Matrix.empty()) {
            ObjectStateSetMatrix(ms, oms->Matrix.data());
          } else if (!ms->Matrix.empty()) {
            ObjectStateResetMatrix(ms);
          }
          ms->RefreshFlag = false;
        }
      }

      if (map && oms && ms->N && ms->V && (I->visRep & cRepMeshBit)) {
        if (ms->ResurfaceFlag) {
          Isofield* field = nullptr;
          ms->RecolorFlag = true;
          ms->ResurfaceFlag = false;
          if (!ms->quiet) {
            PRINTFB(G, FB_ObjectMesh, FB_Details)
            " ObjectMesh: updating \"%s\".\n", I->Name ENDFB(G);
          }
          if (ms->Field) {
            field = ms->Field.get();
          } else if (oms->Field) {
            field = oms->Field.get();
          }

          if (field) {
            {
              float *min_ext, *max_ext;
              float tmp_min[3], tmp_max[3];
              if (MatrixInvTransformExtentsR44d3f(ms->Matrix.data(),
                      ms->ExtentMin, ms->ExtentMax, tmp_min, tmp_max)) {
                min_ext = tmp_min;
                max_ext = tmp_max;
              } else {
                min_ext = ms->ExtentMin;
                max_ext = ms->ExtentMax;
              }

              IsosurfGetRange(I->G, field, &oms->Symmetry->Crystal, min_ext,
                  max_ext, ms->Range, true);
            }
            /*                      printf("Mesh-DEBUG: %d %d %d %d %d %d\n",
               ms->Range[0],
               ms->Range[1],
               ms->Range[2],
               ms->Range[3],
               ms->Range[4],
               ms->Range[5]); */
            IsosurfVolume(I->G, I->Setting.get(), nullptr, field, ms->Level,
                ms->N, ms->V, ms->Range, ms->MeshMode, mesh_skip, ms->AltLevel);

            if (!SettingGet_b(I->G, I->Setting.get(), nullptr,
                    cSetting_mesh_negative_visible)) {
              ms->base_n_V = VLAGetSize(ms->V);
            } else if (ms->MeshMode != cIsomeshMode::gradient) {
              /* do we want the negative surface too? */

              pymol::vla<int> N2(10000);
              pymol::vla<float> V2(10000);

              IsosurfVolume(I->G, I->Setting.get(), nullptr, field, -ms->Level,
                  N2, V2, ms->Range, ms->MeshMode, mesh_skip, ms->AltLevel);

              if (N2 && V2) {

                int base_n_N = VLAGetSize(ms->N);
                int base_n_V = VLAGetSize(ms->V);
                int addl_n_N = VLAGetSize(N2);
                int addl_n_V = VLAGetSize(V2);

                ms->base_n_V = base_n_V;

                /* make room */

                VLASize(ms->N, int, base_n_N + addl_n_N);
                VLASize(ms->V, float, base_n_V + addl_n_V);

                /* copy vertex data */

                memcpy(((char*) ms->V.data()) + (sizeof(float) * base_n_V), V2,
                    sizeof(float) * addl_n_V);

                /* copy strip counts */

                memcpy(((char*) ms->N.data()) + (sizeof(int) * (base_n_N - 1)),
                    N2, sizeof(int) * addl_n_N);
                ms->N[base_n_N + addl_n_N - 1] = 0;

                VLAFreeP(N2);
                VLAFreeP(V2);
              }
            }

            if (!ms->Matrix.empty() && VLAGetSize(ms->N) && VLAGetSize(ms->V)) {
              int count;
              /* take map coordinates back to view coordinates if necessary */
              v = ms->V.data();
              count = VLAGetSize(ms->V) / 3;
              while (count--) {
                transform44d3f(ms->Matrix.data(), v, v);
                v += 3;
              }
            }
          }
          if (ms->CarveFlag && ms->AtomVertex && VLAGetSize(ms->N) &&
              VLAGetSize(ms->V)) {
            /* cull my friend, cull */
            auto carvehelper = CarveHelper(G, ms->CarveBuffer, ms->AtomVertex,
                VLAGetSize(ms->AtomVertex) / 3);
            {
              pymol::vla<int> old_n = std::move(ms->N);
              pymol::vla<float> old_v = std::move(ms->V);
              ms->N = pymol::vla<int>(old_n.size());
              ms->V = pymol::vla<float>(old_v.size());

              n = old_n.data();
              v = old_v.data();
              n_cur = 0;
              n_seg = 0;
              n_line = 0;
              while (*n) {
                last_flag = false;
                c = *(n++);
                while (c--) {
                  flag = !carvehelper.is_excluded(v);
                  if (flag && (!last_flag)) {
                    VLACheck(ms->V, float, 3 * (n_line + 1));
                    copy3f(v, ms->V + n_line * 3);
                    n_cur++;
                    n_line++;
                  }
                  if (flag && last_flag) { /* continue segment */
                    VLACheck(ms->V, float, 3 * (n_line + 1));
                    copy3f(v, ms->V + n_line * 3);
                    n_cur++;
                    n_line++;
                  }
                  if ((!flag) && last_flag) { /* terminate segment */
                    VLACheck(ms->N, int, n_seg);
                    ms->N[n_seg] = n_cur;
                    n_seg++;
                    n_cur = 0;
                  }
                  last_flag = flag;
                  v += 3;

                  if (v - old_v.data() == ms->base_n_V) {
                    ms->base_n_V = n_line * 3;
                  }
                }
                if (last_flag) { /* terminate segment */
                  VLACheck(ms->N, int, n_seg);
                  ms->N[n_seg] = n_cur;
                  n_seg++;
                  n_cur = 0;
                }
              }
              VLACheck(ms->N, int, n_seg);
              ms->N[n_seg] = 0;
            }
          }
        }
        if (ms->RecolorFlag) {
          ObjectMeshStateUpdateColors(I, ms);
          ms->RecolorFlag = false;
        }
      }

      ms->shaderCGO.reset();
      ms->shaderUnitCellCGO.reset();
    }
    SceneInvalidate(I->G);
  }
  if (!I->ExtentFlag) {
    ObjectMeshRecomputeExtent(I);
    if (I->ExtentFlag)
      SceneInvalidate(I->G);
  }
}

void ObjectMesh::render(RenderInfo* info)
{
  ObjectMeshRenderImpl(this, info, false, 0);
}

static bool ObjectMeshStateRenderShader(
    ObjectMeshState* ms, ObjectMesh* I, RenderInfo* info);


static CGO* ObjectMeshCGOGenerate(ObjectMesh* I, ObjectMeshState* ms,
    RenderInfo* info, bool returnCGO, bool mesh_as_cylinders)
{
  int ok = true;
  PyMOLGlobals* G = I->G;

  auto transparency =
      SettingGet<float>(G, I->Setting.get(), nullptr, cSetting_transparency);

  auto shaderCGO = CGONew(G);
  if (!shaderCGO) {
    ok = false;
    return nullptr;
  }
  shaderCGO->use_shader = true;
  CGOAlpha(shaderCGO, 1.0f - transparency);

  // Unit Cell Rendering
  if (ms->UnitCellCGO && (I->visRep & cRepCellBit)) {
    const float* color = ColorGet(I->G, I->Color);
    if (!ms->shaderUnitCellCGO) {
      CGO* newUnitCellCGO = CGONewSized(G, 0);
      CGOColorv(newUnitCellCGO, color);
      CGOAppend(newUnitCellCGO, ms->UnitCellCGO.get());
      ms->shaderUnitCellCGO.reset(
          CGOOptimizeToVBONotIndexedNoShader(newUnitCellCGO));
      CGOFree(newUnitCellCGO);
      ms->shaderUnitCellCGO->use_shader = true;
    }
  }
  // Turn off lighting
  if (info && !info->line_lighting) {
    if (!mesh_as_cylinders) {
      ok &= CGODisable(shaderCGO, GL_LIGHTING);
    }
  }
  if (!ok)
    return 0;

  CGOResetNormal(shaderCGO, true);

  auto n = ms->N.data();
  auto v = ms->V.data();

  if (!(n && v && (I->visRep & cRepMeshBit))) {
    return 0;
  }
  auto vc = ms->VC.data();

  if (!vc) {
    ok &= CGOColorv(shaderCGO, ColorGet(I->G, ms->OneColor));
  }

  if (!mesh_as_cylinders) {
    if (ms->MeshMode == cIsomeshMode::isodot) {
      ok &= CGODotwidth(shaderCGO, SettingGet<float>(I->G, I->Setting.get(),
                                        nullptr, cSetting_dot_width));
    } else {
      ok &= CGOSpecial(shaderCGO, LINEWIDTH_DYNAMIC_MESH);
    }
  }

  if (!ok)
    return nullptr;

  if (mesh_as_cylinders) {
    if (returnCGO) {
      ok &= CGOSpecial(shaderCGO, CYLINDERWIDTH_DYNAMIC_MESH);
    }
    int c;
    for (; ok && (c = *(n++)); v += 3, vc && (vc += 3)) {
      for (; ok && (--c); v += 3) {
        float axis[] = {v[3] - v[0], v[4] - v[1], v[5] - v[2]};
        if (vc) {
          ok &= CGOColorv(shaderCGO, vc);
          vc += 3;
        }
        if (vc && memcmp(vc - 3, vc, sizeof(float) * 3)) {
          ok &= (bool) shaderCGO->add<cgo::draw::shadercylinder2ndcolor>(
              shaderCGO, v, axis, 1.f, 15, vc);
        } else {
          ok &= (bool) shaderCGO->add<cgo::draw::shadercylinder>(
              v, axis, 1.f, 15);
        }
      }
    }
  } else {
    while (ok && *n) {
      auto c = *(n++);
      if (ms->MeshMode == cIsomeshMode::isodot)
        ok &= CGOBegin(shaderCGO, GL_POINTS);
      else {
        if (c < 2) {
          while (c--) {
            if (vc) {
              vc += 3;
            }
            v += 3;
          }
          continue;
        }
        ok &= CGOBegin(shaderCGO, GL_LINE_STRIP);
      }
      while (ok && c--) {
        if (vc) {
          ok &= CGOColorv(shaderCGO, vc);
          vc += 3;
        }
        if (ok)
          ok &= CGOVertexv(shaderCGO, v);
        v += 3;
      }
      if (ok)
        ok &= CGOEnd(shaderCGO);
    }
  }

  if (info && !info->line_lighting) {
    if (ok && !mesh_as_cylinders) {
      ok &= CGOEnable(shaderCGO, GL_LIGHTING);
    }
  }

  // Optimize
  if (!ok) {
    return nullptr;
  }
  CGO* convertcgo = nullptr;
  if (ok)
    ok &= CGOStop(shaderCGO);
  if (ok)
    convertcgo = CGOCombineBeginEnd(shaderCGO, 0);
  CHECKOK(ok, convertcgo);
  CGOFree(shaderCGO);
  shaderCGO = convertcgo;
  if (returnCGO) {
    return (shaderCGO);
  }
  ms->shaderCGO.reset(shaderCGO);
  if (!ok) {
    return nullptr;
  }

  CGO* tmpCGO = CGONew(G);
  auto enableMode = mesh_as_cylinders ? GL_CYLINDER_SHADER : GL_DEFAULT_SHADER;
  CGOEnable(tmpCGO, enableMode);
  if (mesh_as_cylinders) {
    CGOSpecial(tmpCGO, MESH_WIDTH_FOR_SURFACES);
    convertcgo = CGOConvertShaderCylindersToCylinderShader(
        ms->shaderCGO.get(), tmpCGO);
    convertcgo->has_draw_cylinder_buffers = true;
  } else {
    CGODisable(tmpCGO, GL_LIGHTING);
    convertcgo = CGOOptimizeToVBONotIndexed(ms->shaderCGO.get(), 0, false);
  }
  CGOAppendNoStop(tmpCGO, convertcgo);
  CGODisable(tmpCGO, enableMode);
  CGOStop(tmpCGO);
  CGOFreeWithoutVBOs(convertcgo);
  convertcgo = tmpCGO;
  convertcgo->use_shader = true;

  CHECKOK(ok, convertcgo);
  if (convertcgo) {
    ms->shaderCGO.reset(convertcgo);
  }

  return ok ? ms->shaderCGO.get() : nullptr;
}

static bool ObjectMeshStateRenderShader(
    ObjectMeshState* ms, ObjectMesh* I, RenderInfo* info)
{
  CGORender(ms->shaderCGO.get(), nullptr, nullptr, nullptr, info, nullptr);
  return true;
}

static bool ObjectMeshRenderRay(ObjectMeshState* ms, ObjectMesh* I,
    RenderInfo* info, CRay* ray, float transparency)
{
  auto* G = I->G;
  bool ok = true;

  auto mesh_width =
      SettingGet<float>(I->G, I->Setting.get(), nullptr, cSetting_mesh_width);

  auto line_width = SceneGetDynamicLineWidth(info, mesh_width);

  if (ms->UnitCellCGO && (I->visRep & cRepCellBit)) {
    ok &= CGORenderRay(ms->UnitCellCGO.get(), ray, info,
        ColorGet(I->G, I->Color), nullptr, I->Setting.get(), nullptr);
    if (!ok) {
      ms->UnitCellCGO.reset();
      return ok;
    }
  }
  float radius{};
  if (ms->MeshMode != cIsomeshMode::isodot) {
    radius = SettingGet<float>(
        I->G, I->Setting.get(), nullptr, cSetting_mesh_radius);

    if (radius == 0.0f) {
      radius = ray->PixelRadius * line_width / 2.0F;
    }
  } else {
    radius =
        SettingGet<float>(I->G, I->Setting.get(), nullptr, cSetting_dot_radius);
    if (radius == 0.0F) {
      radius = ray->PixelRadius *
               SettingGet<float>(
                   I->G, I->Setting.get(), nullptr, cSetting_dot_width) /
               1.4142F;
    }
  }

  auto v = ms->V.data();
  auto n = ms->N.data();
  if (!(ok && n && v && (I->visRep & cRepMeshBit))) {
    return false;
  }
  float cc[3];
  float colA[3], colB[3];
  ColorGetEncoded(G, ms->OneColor, cc);
  auto vc = ms->VC.data();
  auto rc = ms->RC.data();

  ray->transparentf(transparency);

  if (ms->MeshMode == cIsomeshMode::isodot) {
    ray->color3fv(cc);
    while (ok && *n) {
      auto c = *(n++);
      while (ok && c--) {
        if (vc) {
          float* cA = vc;
          if (rc) {
            if (rc[0] < -1)
              ColorGetEncoded(G, rc[0], (cA = colA));
            rc++;
          }
          ray->color3fv(cA);
          ok &= ray->sphere3fv(v, radius);
          vc += 3;
        } else {
          ok &= ray->sphere3fv(v, radius);
        }
        v += 3;
      }
    }
  } else {
    // isomesh
    while (ok && *n) {
      auto c = *(n++);
      if (c--) {
        v += 3;
        if (vc) {
          vc += 3;
          if (rc)
            rc++;
        }
        while (ok && c--) {
          if (vc) {
            float *cA = vc - 3, *cB = vc;
            if (rc) {
              if (rc[-1] < -1)
                ColorGetEncoded(G, rc[-1], (cA = colA));
              if (rc[0] < -1)
                ColorGetEncoded(G, rc[0], (cB = colB));
              rc++;
            }
            ok &= ray->sausage3fv(v - 3, v, radius, cA, cB);
            vc += 3;
          } else {
            ok &= ray->sausage3fv(v - 3, v, radius, cc, cc);
          }
          v += 3;
        }
      }
    }
  }
  return ok;
}

static void ObjectMeshRenderImmediate(
    ObjectMeshState* ms, ObjectMesh* I, RenderInfo* info)
{
  auto mesh_width =
      SettingGet<float>(I->G, I->Setting.get(), nullptr, cSetting_mesh_width);

  auto line_width = SceneGetDynamicLineWidth(info, mesh_width);

  SceneResetNormal(I->G, false);

#ifdef PURE_OPENGL_ES_2
  /* TODO */
#else
  if (info && !info->line_lighting) {
    glDisable(GL_LIGHTING);
  }
  auto vc = ms->VC.data();

  if (!vc)
    glColor3fv(ColorGet(I->G, ms->OneColor));
  if (ms->MeshMode == cIsomeshMode::isodot) {
    glPointSize(
        SettingGet_f(I->G, I->Setting.get(), nullptr, cSetting_dot_width));
  } else {
    glLineWidth(line_width);
  }

  auto v = ms->V.data();
  auto n = ms->N.data();

  while (*n) {
    auto c = *(n++);
    if (ms->MeshMode == cIsomeshMode::isodot)
      glBegin(GL_POINTS);
    else
      glBegin(GL_LINE_STRIP);
    while (c--) {
      if (vc) {
        glColor3fv(vc);
        vc += 3;
      }
      glVertex3fv(v);
      v += 3;
    }
    glEnd();
  }
  if (info && !info->line_lighting) {
    glEnable(GL_LIGHTING);
  }
#endif

  // Render Unit Cell Immediately
  if (ms->UnitCellCGO) {
    const float* color = ColorGet(I->G, I->Color);
    CGORender(
      ms->UnitCellCGO.get(), color, I->Setting.get(), nullptr, info, nullptr);
  }
}

static CGO* ObjectMeshRenderImpl(
    ObjectMesh* I, RenderInfo* info, bool returnCGO, int stateArg)
{
  PyMOLGlobals* G = I->G;
  int state = 0;
  CRay* ray = 0;
  bool pick = false;
  RenderPass pass = RenderPass::Antialias;

  int ok = true;

  if (info) {
    state = info->state;
    ray = info->ray;
    pick = info->pick;
    pass = info->pass;
  } else {
    state = stateArg;
  }

  ObjectPrepareContext(I, info);

  bool use_shader = (SettingGet<bool>(G, cSetting_mesh_use_shader) &&
                        SettingGet<bool>(G, cSetting_use_shaders)) ||
                    returnCGO;

  for (StateIterator iter(I->G, I->Setting.get(), state, I->NState);
       iter.next();) {
    auto ms = &I->State[iter.state];

    if (!ms->Active || !ms->V || !ms->N)
      continue;

    auto transparency =
        SettingGet<float>(G, I->Setting.get(), nullptr, cSetting_transparency);

    if (ray) {
      ok = ObjectMeshRenderRay(ms, I, info, ray, transparency);
      if (!ok) {
        I->invalidate(cRepMesh, cRepInvPurge, -1);
        I->invalidate(cRepCGO, cRepInvPurge, -1);
        ObjectSetRepVisMask(I, 0, cVis_AS);
        return nullptr;
      }
    }
    if (!((G->HaveGUI && G->ValidContext) || returnCGO)) {
      return nullptr;
    }
    if (pick || pass != RenderPass::Antialias) {
      return nullptr;
    }

    if (use_shader) {
      bool mesh_as_cylinders = SettingGet<bool>(G, cSetting_render_as_cylinders) &&
                              SettingGet<bool>(G, cSetting_mesh_as_cylinders) &&
                              ms->MeshMode != cIsomeshMode::isodot;
      if (ms->shaderCGO &&
          (!use_shader ||
              (mesh_as_cylinders ^ ms->shaderCGO->has_draw_cylinder_buffers))) {
        ms->shaderCGO.reset();
        ms->shaderUnitCellCGO.reset();
      }

      if (!ms->shaderCGO) {
        auto cgo = ObjectMeshCGOGenerate(I, ms, info, returnCGO, mesh_as_cylinders);
        if (returnCGO) {
          return cgo;
        }
      }
      ok &= ObjectMeshStateRenderShader(ms, I, info);

      if (!ok) {
        break;
      }
      return ms->shaderCGO.get();
    }
    ObjectMeshRenderImmediate(ms, I, info);
  }
  if (!ok) {
    I->invalidate(cRepMesh, cRepInvPurge, -1);
    I->invalidate(cRepCGO, cRepInvPurge, -1);
    ObjectSetRepVisMask(I, 0, cVis_AS);
  }

  return nullptr;
}

/*========================================================================*/

int ObjectMesh::getNFrame() const
{
  return NState;
}

/*========================================================================*/
ObjectMesh::ObjectMesh(PyMOLGlobals* G)
    : pymol::CObject(G)
{
  auto I = this;
  I->type = cObjectMesh;
}

/*========================================================================*/
ObjectMeshState::ObjectMeshState(PyMOLGlobals* G)
    : CObjectState(G)
    , Crystal(G)
{
  V = pymol::vla<float>(10000);
  N = pymol::vla<int>(10000);
}

/*========================================================================*/
ObjectMesh* ObjectMeshFromXtalSym(PyMOLGlobals* G, ObjectMesh* obj,
    ObjectMap* map, CSymmetry* sym, int map_state, int state, float* mn,
    float* mx, float level, cIsomeshMode meshMode, float carve, float* vert_vla,
    float alt_level, int quiet)
{
  int ok = true;
  ObjectMesh* I = nullptr;
  ObjectMeshState* ms = nullptr;
  ObjectMapState* oms = nullptr;
  int created = !obj;

  if (created) {
    I = new ObjectMesh(G);
  } else {
    I = obj;
  }
  CHECKOK(ok, I);

  if (ok) {
    if (state < 0)
      state = I->NState;
    if (I->NState <= state) {
      VecCheckEmplace(I->State, state, G);
      if (ok)
        I->NState = state + 1;
    }
  }

  if (ok) {
    ms = &I->State[state];
    *ms = ObjectMeshState(G);
  }

  if (ok) {
    strcpy(ms->MapName, map->Name);
    ms->MapState = map_state;
    oms = ObjectMapGetState(map, map_state);

    ms->Level = level;
    ms->AltLevel = alt_level;
    ms->MeshMode = meshMode;
    ms->quiet = quiet;
  }
  if (ok && oms) {
    if ((meshMode == cIsomeshMode::gradient) && (ms->AltLevel < ms->Level)) {
      /* gradient object -- need to auto-set range */
      if (!ObjectMapStateGetDataRange(G, oms, &ms->Level, &ms->AltLevel)) {
        ms->Level = -1.0F;
        ms->AltLevel = 1.0F;
      }
    }

    copy3f(mn, ms->ExtentMin); /* this is not exactly correct...should actually
                                  take vertex points from range */
    copy3f(mx, ms->ExtentMax);

    if (!oms->Matrix.empty()) {
      ok &= ObjectStateSetMatrix(ms, oms->Matrix.data());
    } else if (!ms->Matrix.empty()) {
      ObjectStateResetMatrix(ms);
    }

    if (ok) {
      float *min_ext, *max_ext;
      float tmp_min[3], tmp_max[3];
      if (MatrixInvTransformExtentsR44d3f(ms->Matrix.data(), ms->ExtentMin,
              ms->ExtentMax, tmp_min, tmp_max)) {
        min_ext = tmp_min;
        max_ext = tmp_max;
      } else {
        min_ext = ms->ExtentMin;
        max_ext = ms->ExtentMax;
      }

      if (sym) {
        int eff_range[6];

        if (IsosurfGetRange(G, oms->Field.get(), &oms->Symmetry->Crystal,
                min_ext, max_ext, eff_range, false)) {
          int fdim[3];
          int expand_result;
          /* need to generate symmetry-expanded temporary map */

          ms->Crystal = (oms->Symmetry->Crystal);
          fdim[0] = eff_range[3] - eff_range[0];
          fdim[1] = eff_range[4] - eff_range[1];
          fdim[2] = eff_range[5] - eff_range[2];
          ms->Field = pymol::make_copyable<Isofield>(I->G, fdim);

          expand_result = IsosurfExpand(oms->Field.get(), ms->Field.get(),
              &oms->Symmetry->Crystal, sym, eff_range);

          if (expand_result == 0) {
            ok = false;
            if (!quiet) {
              PRINTFB(G, FB_ObjectMesh, FB_Warnings)
              " ObjectMesh-Warning: no symmetry expanded map points "
              "found.\n" ENDFB(G);
            }
          } else {
            if (!quiet) {
              PRINTFB(G, FB_ObjectMesh, FB_Warnings)
              " ObjectMesh-Warning: not all symmetry expanded points covered "
              "by map.\n" ENDFB(G);
            }
          }

          ms->Range[0] = 0;
          ms->Range[1] = 0;
          ms->Range[2] = 0;
          ms->Range[3] = fdim[0];
          ms->Range[4] = fdim[1];
          ms->Range[5] = fdim[2];

        } else {
          /* mesh entirely contained within bounds of current map */
          int a;
          for (a = 0; a < 6; a++) {
            ms->Range[a] = eff_range[a];
          }
        }
      } else {
        IsosurfGetRange(G, oms->Field.get(), &oms->Symmetry->Crystal, min_ext,
            max_ext, ms->Range, true);
      }
    }
    ms->ExtentFlag = true;
  }
  if (ok) {
    if (carve != 0.0) {
      ms->CarveFlag = true;
      ms->CarveBuffer = carve;
      ms->AtomVertex = pymol::vla_take_ownership(vert_vla);
    }
    if (I) {
      ObjectMeshRecomputeExtent(I);
    }
    I->ExtentFlag = true;
    /*  printf("Brick %d %d %d %d %d
     * %d\n",I->Range[0],I->Range[1],I->Range[2],I->Range[3],I->Range[4],I->Range[5]);
     */
  }
  if (!ok && created) {
    DeleteP(I);
  }
  SceneChanged(G);
  SceneCountFrames(G);
  return (I);
}

/*========================================================================*/
ObjectMesh* ObjectMeshFromBox(PyMOLGlobals* G, ObjectMesh* obj, ObjectMap* map,
    int map_state, int state, float* mn, float* mx, float level,
    cIsomeshMode meshMode, float carve, float* vert_vla, float alt_level,
    int quiet)
{
  return ObjectMeshFromXtalSym(G, obj, map, nullptr, map_state, state, mn, mx,
      level, meshMode, carve, vert_vla, alt_level, quiet);
}

/*========================================================================*/

void ObjectMeshRecomputeExtent(ObjectMesh* I)
{
  int extent_flag = false;
  int a;
  ObjectMeshState* ms;

  for (a = 0; a < I->NState; a++) {
    ms = &I->State[a];
    if (ms->Active) {
      if (ms->ExtentFlag) {
        if (!extent_flag) {
          extent_flag = true;
          copy3f(ms->ExtentMax, I->ExtentMax);
          copy3f(ms->ExtentMin, I->ExtentMin);
        } else {
          max3f(ms->ExtentMax, I->ExtentMax, I->ExtentMax);
          min3f(ms->ExtentMin, I->ExtentMin, I->ExtentMin);
        }
      }
    }
  }

  I->ExtentFlag = extent_flag;

  if (I->TTTFlag && I->ExtentFlag) {
    const float* ttt;
    double tttd[16];
    if (ObjectGetTTT(I, &ttt, -1)) {
      convertTTTfR44d(ttt, tttd);
      MatrixTransformExtentsR44d3f(
          tttd, I->ExtentMin, I->ExtentMax, I->ExtentMin, I->ExtentMax);
    }
  }
}

pymol::CObject* ObjectMesh::clone() const
{
  return new ObjectMesh(*this);
}
