
/* 
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_predef.h"
#include"os_gl.h"

#include"Rep.h"
#include"Err.h"
#include"RepWireBond.h"
#include"SideChainHelper.h"
#include"Color.h"
#include"Scene.h"
#include"main.h"
#include"Setting.h"
#include"ShaderMgr.h"
#include"CGO.h"
#include "Feedback.h"
#include"CoordSet.h"

struct RepWireBond : Rep {
  using Rep::Rep;

  ~RepWireBond() override;

  cRep_t type() const override { return cRepLine; }
  void render(RenderInfo* info) override;

  CGO *shaderCGO = nullptr;
  CGO *primitiveCGO = nullptr;
  bool shaderCGO_has_cylinders = false;
};

#include"ObjectMolecule.h"

static int RepLine(CGO *cgo, bool s1, bool s2, bool isRamped,
    const float *v1,
    const float *v2,
    const float *v1color,
    unsigned int b1, unsigned int b2, int a,
    const float *v2color,
    bool b1masked, bool b2masked)
{
  int ok = true;
  if (s1 && s2){
    CGOColorv(cgo, v1color);
    CGOPickColor(cgo, b1, b1masked ? cPickableNoPick : a);
    {
      // if not ramped, then insert vertices so colors are not interpolated since lines interpolate by default
      bool eq = equal3f(v1color, v2color);
      bool split = !eq || b1 != b2;
      if (split){
        cgo->add<cgo::draw::splitline>(v1, v2, v2color, b2, b2masked ? cPickableNoPick : a, isRamped, b1==b2, eq);
        cgo->current_pick_color_index = b2;
        cgo->current_pick_color_bond = b2masked ? cPickableNoPick : a;
      } else {
        cgo->add<cgo::draw::line>(v1, v2);
      }
    }
  } else {
    // if half bond, then split for either s1 or s2
    float h[3];
    average3f(v1, v2, h);
    if (s1){
      CGOColorv(cgo, v1color);
      CGOPickColor(cgo, b1, b1masked ? cPickableNoPick : a);
      cgo->add<cgo::draw::line>(v1, h);
    } else {
      if (v2color)
        CGOColorv(cgo, v2color);
      if (b2)
        CGOPickColor(cgo, b2, b2masked ? cPickableNoPick : a);
      cgo->add<cgo::draw::line>(h, v2);
    }
  }
  return ok;
}

static void RepValence(CGO *cgo, bool s1, bool s2, bool isRamped,
    const float *v1,
    const float *v2,
    const int *other,
    int a1, int a2,
    const float *coord,
    const float *color1,
    const float *color2, int ord,
    float tube_size, int fancy, unsigned int b1, unsigned int b2, int a, bool b1masked, bool b2masked)
{

  float d[3], t[3], p0[3], p1[3], p2[3];
  int a3;
  const float indent = tube_size;
  /* direction vector */
  subtract3f(v2, v1, p0);
  copy3f(p0, d);
  normalize3f(p0);
  /* need a prioritized third atom to get planarity */
  a3 = ObjectMoleculeGetPrioritizedOther(other, a1, a2, nullptr);
  if(a3 < 0) {
    t[0] = p0[0];
    t[1] = p0[1];
    t[2] = -p0[2];
  } else {
    subtract3f(coord + 3 * a3, v1, t);
    normalize3f(t);
  }
  cross_product3f(d, t, p1);
  normalize3f(p1);
  if(length3f(p1) == 0.0) {
    p1[0] = p0[1];
    p1[1] = p0[2];
    p1[2] = p0[0];
    cross_product3f(p0, p1, p2);
    normalize3f(p2);
  } else {
    cross_product3f(d, p1, p2);
    normalize3f(p2);
  }
  /* now we have a coordinate system */
  mult3f(p2, tube_size, t);

  bool ord3 = (ord == 3);
  if (ord3)
    mult3f(t, 2.f, t);
  if (fancy || ord3){
    RepLine(cgo, s1, s2, isRamped, v1, v2, color1, b1, b2, a, color2, b1masked, b2masked);
  }
  if(fancy) {
    float f[] = { indent, 1.f - indent };
    float f_1[] = { 1.f - f[0], 1.f - f[1] };
    float vv1[] = { (f_1[0] * v1[0] + f[0] * v2[0]) - 2 * t[0],
                    (f_1[0] * v1[1] + f[0] * v2[1]) - 2 * t[1],
                    (f_1[0] * v1[2] + f[0] * v2[2]) - 2 * t[2] };
    float vv2[] = { (f_1[1] * v1[0] + f[1] * v2[0]) - 2 * t[0],
                    (f_1[1] * v1[1] + f[1] * v2[1]) - 2 * t[1],
                    (f_1[1] * v1[2] + f[1] * v2[2]) - 2 * t[2] };
    RepLine(cgo, s1, s2, isRamped, vv1, vv2, color1, b1, b2, a, color2, b1masked, b2masked);
  } else {
    float vv1[][3] = { { v1[0] - t[0], v1[1] - t[1], v1[2] - t[2] },
                       { v1[0] + t[0], v1[1] + t[1], v1[2] + t[2] } };
    float vv2[][3] = { { v2[0] - t[0], v2[1] - t[1], v2[2] - t[2] },
                       { v2[0] + t[0], v2[1] + t[1], v2[2] + t[2] } };
    RepLine(cgo, s1, s2, isRamped, vv1[0], vv2[0], color1, b1, b2, a, color2, b1masked, b2masked);
    RepLine(cgo, s1, s2, isRamped, vv1[1], vv2[1], color1, b1, b2, a, color2, b1masked, b2masked);
  }
}

static void RepAromatic(CGO *cgo, bool s1, bool s2, bool isRamped,
    const float *v1,
    const float *v2,
    const int *other,
    int a1, int a2,
    const float *coord,
    const float *color1,
    const float *color2,
                        float tube_size, int half_state, unsigned int b1, unsigned int b2, int a, bool b1masked, bool b2masked)
{
  float d[3], t[3], p0[3], p1[3], p2[3];
  int a3;
  int double_sided;

  /* direction vector */
  p0[0] = (v2[0] - v1[0]);
  p0[1] = (v2[1] - v1[1]);
  p0[2] = (v2[2] - v1[2]);
  copy3f(p0, d);
  normalize3f(p0);

  /* need a prioritized third atom to get planarity */
  a3 = ObjectMoleculeGetPrioritizedOther(other, a1, a2, &double_sided);
  if(a3 < 0) {
    t[0] = p0[0];
    t[1] = p0[1];
    t[2] = -p0[2];
  } else {
    const float* vv = coord + 3 * a3;
    t[0] = *(vv++) - v1[0];
    t[1] = *(vv++) - v1[1];
    t[2] = *(vv++) - v1[2];
    normalize3f(t);
  }
  cross_product3f(d, t, p1);
  normalize3f(p1);
  if(length3f(p1) == 0.0) {
    p1[0] = p0[1];
    p1[1] = p0[2];
    p1[2] = p0[0];
    cross_product3f(p0, p1, p2);
    normalize3f(p2);
  } else {
    cross_product3f(d, p1, p2);
    normalize3f(p2);
  }

    t[0] = p2[0] * tube_size * 2;
    t[1] = p2[1] * tube_size * 2;
    t[2] = p2[2] * tube_size * 2;
  RepLine(cgo, s1, s2, isRamped, v1, v2, color1, b1, b2, a, color2, b1masked, b2masked);
  if (s1){
    CGOColorv(cgo, color1);
    CGOPickColor(cgo, b1, b1masked ? cPickableNoPick : a);
    float f[] = { 0.14F, 0.4F } ;
    float f_1[] = { 1.0F - f[0], 1.0F - f[1] };
    float pt1[] = { (f_1[0] * v1[0] + f[0] * v2[0]),
                    (f_1[0] * v1[1] + f[0] * v2[1]),
                    (f_1[0] * v1[2] + f[0] * v2[2]) };
    float pt2[] = { (f_1[1] * v1[0] + f[1] * v2[0]),
                    (f_1[1] * v1[1] + f[1] * v2[1]),
                    (f_1[1] * v1[2] + f[1] * v2[2]) };
    float p1[3], p2[3];
    subtract3f(pt1, t, p1);
    subtract3f(pt2, t, p2);
    cgo->add<cgo::draw::line>(p1, p2);
    if(double_sided) {
      add3f(pt1, t, p1);
      add3f(pt2, t, p2);
      cgo->add<cgo::draw::line>(p1, p2);
    }
  }
  if (s2){
    CGOColorv(cgo, color2);
    CGOPickColor(cgo, b2, b2masked ? cPickableNoPick : a);
    float f[] = { 0.6F, 0.86F } ;
    float f_1[] = { 1.0F - f[0], 1.0F - f[1] };
    float pt1[] = { (f_1[0] * v1[0] + f[0] * v2[0]),
                    (f_1[0] * v1[1] + f[0] * v2[1]),
                    (f_1[0] * v1[2] + f[0] * v2[2]) };
    float pt2[] = { (f_1[1] * v1[0] + f[1] * v2[0]),
                    (f_1[1] * v1[1] + f[1] * v2[1]),
                    (f_1[1] * v1[2] + f[1] * v2[2]) };
    float p1[3], p2[3];
    subtract3f(pt1, t, p1);
    subtract3f(pt2, t, p2);
    cgo->add<cgo::draw::line>(p1, p2);
    if(double_sided) {
      add3f(pt1, t, p1);
      add3f(pt2, t, p2);
      cgo->add<cgo::draw::line>(p1, p2);
    }
  }
}

RepWireBond::~RepWireBond()
{
  CGOFree(shaderCGO);
  CGOFree(primitiveCGO);
}


/* lower memory use and higher performance for
   display of large trajectories, etc. */

void RepWireBondRenderImmediate(CoordSet * cs, RenderInfo * info)
{
  /* performance optimized, so it does not support the following:

     - anything other than opengl
     - display of bond valences
     - per-bond & per-atom properties, including color and line-width
     - half-bonds
     - helper settings such as cartoon_side_chain_helper
     - suppression of long bonds
     - color ramps
     - atom picking
     - display lists
     - transparency 

   */
  PyMOLGlobals *G = cs->G;
  if(info->ray || info->pick || (!(G->HaveGUI && G->ValidContext)))
    return;
  else {
    int active = false;
    ObjectMolecule *obj = cs->Obj;
    float line_width, line_width_setting =
      SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_line_width);
    line_width = SceneGetDynamicLineWidth(info, line_width_setting);

    if(info->width_scale_flag)
      glLineWidth(line_width * info->width_scale);
    else
      glLineWidth(line_width);

    SceneResetNormal(G, true);
    if(!info->line_lighting)
      glDisable(GL_LIGHTING);
    glBegin(GL_LINES);
    {
      int a;
      int nBond = obj->NBond;
      const BondType *bd = obj->Bond.data();
      const AtomInfoType *ai = obj->AtomInfo.data();
      int last_color = -9;
      const float *coord = cs->Coord.data();

      for(a = 0; a < nBond; a++) {
        int b1 = bd->index[0];
        int b2 = bd->index[1];
        const AtomInfoType *ai1, *ai2;
        bd++;
        if(GET_BIT((ai1 = ai + b1)->visRep,cRepLine) && GET_BIT((ai2 = ai + b2)->visRep,cRepLine)) {
          active = true;
          int a1 = cs->atmToIdx(b1);
          int a2 = cs->atmToIdx(b2);
          if((a1 >= 0) && (a2 >= 0)) {
            int c1 = ai1->color;
            int c2 = ai2->color;

            const float *v1 = coord + 3 * a1;
            const float *v2 = coord + 3 * a2;

            if(c1 == c2) {      /* same colors -> one line */
              if(c1 != last_color) {
                last_color = c1;
                glColor3fv(ColorGet(G, c1));
              }
              glVertex3fv(v1);
              glVertex3fv(v2);  /* we done */
            } else {            /* different colors -> two lines */
              float avg[3];
              average3f(v1, v2, avg);

              if(c1 != last_color) {
                last_color = c1;
                glColor3fv(ColorGet(G, c1));
              }
              glVertex3fv(v1);
              glVertex3fv(avg);

              if(c2 != last_color) {
                last_color = c2;
                glColor3fv(ColorGet(G, c2));
              }
              glVertex3fv(avg);
              glVertex3fv(v2);
            }
          }
        }
      }
    }
    glEnd();
    glEnable(GL_LIGHTING);
    if(!active)
      cs->Active[cRepLine] = false;
  }
}

static int RepWireBondCGOGenerate(RepWireBond * I, RenderInfo * info)
{
  PyMOLGlobals *G = I->G;
  CGO *convertcgo = nullptr;
  int ok = true;
  short line_as_cylinders = 0;
  line_as_cylinders = SettingGetGlobal_b(G, cSetting_use_shaders) && SettingGetGlobal_b(G, cSetting_render_as_cylinders) && SettingGetGlobal_b(G, cSetting_line_as_cylinders);

  {
    if (ok && I->primitiveCGO){
      if (line_as_cylinders){
        CGO *tmpCGO = CGONew(G);

        if (ok) ok &= CGOEnable(tmpCGO, GL_CYLINDER_SHADER);
        if (ok) ok &= CGOSpecial(tmpCGO, CYLINDER_WIDTH_FOR_REPWIRE);
        convertcgo = CGOConvertLinesToCylinderShader(I->primitiveCGO, tmpCGO);
        I->shaderCGO_has_cylinders = true;

        if (ok) ok &= CGOAppendNoStop(tmpCGO, convertcgo);

        if (ok) ok &= CGODisable(tmpCGO, GL_CYLINDER_SHADER);
        if (ok) ok &= CGOStop(tmpCGO);
        CGOFreeWithoutVBOs(convertcgo);
        convertcgo = tmpCGO;
      } else {
        bool trilines = SettingGetGlobal_b(G, cSetting_trilines);
        CGO *tmpCGO = CGONew(G), *tmp2CGO;
        int shader = trilines ? GL_TRILINES_SHADER : GL_LINE_SHADER;

        if (ok) ok &= CGOEnable(tmpCGO, shader);
        if (ok) ok &= CGODisable(tmpCGO, CGO_GL_LIGHTING);
        if (trilines) {
          if (ok) ok &= CGOSpecial(tmpCGO, LINEWIDTH_DYNAMIC_WITH_SCALE);
          tmp2CGO = CGOConvertToTrilinesShader(I->primitiveCGO, tmpCGO);
        } else {
          tmp2CGO = CGOConvertToLinesShader(I->primitiveCGO, tmpCGO);
        }
        if (ok) ok &= CGOAppendNoStop(tmpCGO, tmp2CGO);
        if (ok) ok &= CGODisable(tmpCGO, shader);
        if (ok) ok &= CGOStop(tmpCGO);
        CGOFreeWithoutVBOs(tmp2CGO);
        convertcgo = tmpCGO;
      }
      convertcgo->use_shader = true;
    }
    CGOFree(I->shaderCGO);
    I->shaderCGO = convertcgo;
    CHECKOK(ok, I->shaderCGO);
  }
  return ok;
}
	  
void RepWireBond::render(RenderInfo* info)
{
  auto I = this;
  CRay *ray = info->ray;
  auto pick = info->pick;
  int ok = true;

  if(ray) {
#ifndef _PYMOL_NO_RAY
    CGORenderRay(primitiveCGO, ray, info, nullptr, nullptr, cs->Setting.get(), cs->Obj->Setting.get());
    ray->transparentf(0.0);
#endif
  } else if(G->HaveGUI && G->ValidContext) {
    bool use_shader = SettingGetGlobal_b(G, cSetting_line_use_shader) &&
                      SettingGetGlobal_b(G, cSetting_use_shaders);
    if(pick) {
      CGORenderPicking(use_shader ? I->shaderCGO : I->primitiveCGO, info, &I->context, nullptr, nullptr, I);
    } else { /* else not pick i.e., when rendering */
      short line_as_cylinders ;
      line_as_cylinders = SettingGetGlobal_b(G, cSetting_render_as_cylinders) && SettingGetGlobal_b(G, cSetting_line_as_cylinders);
      if (I->shaderCGO && (!use_shader || (line_as_cylinders ^ I->shaderCGO_has_cylinders))){
      CGOFree(I->shaderCGO);
        I->shaderCGO_has_cylinders = 0;
	}
	if (ok){
        if (use_shader) {
          if (!I->shaderCGO)
            ok &= RepWireBondCGOGenerate(I, info);
          CGORender(I->shaderCGO, nullptr, nullptr, nullptr, info, I);
        } else {
          CGORender(I->primitiveCGO, nullptr, nullptr, nullptr, info, I);
        }
      }
    }
  }
  if (!ok){
    CGOFree(I->shaderCGO);
    I->invalidate(cRepInvPurge);
    I->cs->Active[cRepLine] = false;
  }
}

static
bool IsBondTerminal(ObjectMolecule *obj, int b1, int b2){
  auto const has_heavy_neighbors = [obj](int atm, int atm_other) {
    for (auto const& neighbor : AtomNeighbors(obj, atm)) {
      if (neighbor.atm != atm_other &&
          obj->AtomInfo[neighbor.atm].protons > 1) {
        return true;
      }
    }
    return false;
  };

  return !has_heavy_neighbors(b1, b2) || !has_heavy_neighbors(b2, b1);
}

static int RepWireZeroOrderBond(CGO *cgo, bool s1, bool s2,
    const float *v1,
    const float *v2,
    const float *rgb1,
    const float *rgb2,
                                unsigned int b1, unsigned int b2, int a, float dash_gap, float dash_length, bool b1masked, bool b2masked)
{
  int ok = true;
  float axis[3], naxis[3];
  subtract3f(v2, v1, axis);
  copy3f(axis, naxis);
  normalize3f(naxis);
  float blen = length3f(axis);
  float dash_tot = dash_gap + dash_length;
  int ndashes = blen / dash_tot;

  // only do even number of dashes
  if (ndashes < 2) {
    ndashes = 2;
  } else if (ndashes % 2) {
    --ndashes;
  }

  float remspace = blen - (ndashes * dash_length); // remaining space for first gaps
  float dgap = remspace / (ndashes - 1.f); // endpoints at each vertex, therefore only account for ndashes-1 spaces
  float placep[3], placep2[3], adddlen[3], adddtot[3];
  float dplace;
  int ndashes_drawn = 0;
  bool color2_set = false;
  mult3f(naxis, dash_length, adddlen); // adddlen - length of dash as x/y/z vector
  mult3f(naxis, dash_length + dgap, adddtot); // adddtot - length of dash plus gap as x/y/z vector

  copy3f(v1, placep);
  if (s1){
    ok &= CGOColorv(cgo, rgb1);
    ok &= CGOPickColor(cgo, b1, b1masked ? cPickableNoPick : a);
    for (dplace = 0.f; (dplace+dash_length) < blen / 2.f; ){
      add3f(placep, adddlen, placep2);
      cgo->add<cgo::draw::line>(placep, placep2);
      add3f(placep, adddtot, placep);
      dplace += dash_length + dgap;
      ++ndashes_drawn;
    }
    if (!s2){
      if (dplace < blen / 2.f){
        // if we are behind the mid-point, only s1, so draw a half-bond
        add3f(placep, adddlen, placep2);
        cgo->add<cgo::draw::line>(placep, placep2);
        add3f(placep, adddtot, placep);
        dplace += dash_length + dgap;
        ++ndashes_drawn;
      }
    }
  } else {
    float tmpp[3];
    dplace = (ndashes/2) * (dash_length + dgap);
    mult3f(naxis, dplace, tmpp);
    add3f(v1, tmpp, placep);
    ndashes_drawn = ndashes/2;
    // if !s1, then definitely s2, so draw half-bond
    if (dplace <= blen / 2.f){
      // if no s1, and we are behind the mid-point, draw half-bond with only s2
      add3f(placep, adddlen, placep2);
      ok &= CGOColorv(cgo, rgb2);
      ok &= CGOPickColor(cgo, b2, b2masked ? cPickableNoPick : a);
      color2_set = true;
      cgo->add<cgo::draw::line>(placep, placep2);
      add3f(placep, adddtot, placep);
      dplace += dash_length + dgap;
      ++ndashes_drawn;
    }
  }
  if (s2){
    if (dplace < blen / 2.f){
      // if we are behind the mid-point, draw a split cylinder with both colors
      float tmpp[3];
      mult3f(axis, .5f, tmpp);
      add3f(v1, tmpp, tmpp);
      cgo->add<cgo::draw::line>(placep, tmpp);
      add3f(placep, adddlen, placep2);
      if (!color2_set){
        ok &= CGOColorv(cgo, rgb2);
        ok &= CGOPickColor(cgo, b2, b2masked ? cPickableNoPick : a);
      }
      cgo->add<cgo::draw::line>(tmpp, placep2);
      add3f(placep, adddtot, placep);
      dplace += dash_length + dgap;
      ++ndashes_drawn;
    } else if (!color2_set){
      ok &= CGOColorv(cgo, rgb2);
      ok &= CGOPickColor(cgo, b2, b2masked ? cPickableNoPick : a);
    }
    while (ndashes_drawn < ndashes){
      add3f(placep, adddlen, placep2);
      cgo->add<cgo::draw::line>(placep, placep2);
      add3f(placep, adddtot, placep);
      dplace += dash_length + dgap;
      ++ndashes_drawn;
    }
  }

  return ok;
}

Rep *RepWireBondNew(CoordSet * cs, int state)
{
  PyMOLGlobals *G = cs->G;
  ObjectMolecule *obj = cs->Obj;
  int a1, a2;
  unsigned int b1, b2;
  int a, c1, c2, ord;
  bool s1, s2;
  const BondType *b;
  int half_bonds, *other = nullptr;
  float valence;
  const float *v1, *v2;
  int visFlag;
  float line_width;
  int valence_flag = false;
  const AtomInfoType *ai1;
  int cartoon_side_chain_helper = 0;
  int ribbon_side_chain_helper = 0;
  int line_stick_helper = 0;
  int na_mode;
  bool *marked = nullptr;
  int valence_found = false;
  int variable_width = false;
  float last_line_width = -1.f;
  int line_color;
  int hide_long = false;
  int fancy;
  const float _0p9 = 0.9F;
  int ok = true;
  unsigned int line_counter = 0;
  PRINTFD(G, FB_RepWireBond)
    " RepWireBondNew-Debug: entered.\n" ENDFD;

  visFlag = false;
  b = obj->Bond;
  ai1 = obj->AtomInfo;
  if(ok && GET_BIT(obj->RepVisCache,cRepLine)){
    for(a = 0; a < obj->NBond; a++) {
      b1 = b->index[0];
      b2 = b->index[1];
      if((cRepLineBit & ai1[b1].visRep & ai1[b2].visRep)) {
        visFlag = true;
        break;
      }
      b++;
    }
  }
  if(!visFlag) {
    return (nullptr);              /* skip if no dots are visible */
  }
  marked = pymol::calloc<bool>(obj->NAtom);
  CHECKOK(ok, marked);
  if (!ok){
    return (nullptr);
  }
  
  valence_flag = SettingGet_b(G, cs->Setting.get(), obj->Setting.get(), cSetting_valence);
  valence = SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_valence_size);
  cartoon_side_chain_helper = SettingGet_b(G, cs->Setting.get(), obj->Setting.get(),
					   cSetting_cartoon_side_chain_helper);
  ribbon_side_chain_helper = SettingGet_b(G, cs->Setting.get(), obj->Setting.get(),
					  cSetting_ribbon_side_chain_helper);
  line_stick_helper = SettingGet_b(G, cs->Setting.get(), obj->Setting.get(),
				   cSetting_line_stick_helper);
  line_color = SettingGet_color(G, cs->Setting.get(), obj->Setting.get(), cSetting_line_color);
  line_width = SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_line_width);
  
  if(line_stick_helper && (SettingGet_f(G, cs->Setting.get(), obj->Setting.get(),
					cSetting_stick_transparency) > R_SMALL4))
    line_stick_helper = false;
  half_bonds = SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_half_bonds);
  hide_long = SettingGet_b(G, cs->Setting.get(), obj->Setting.get(), cSetting_hide_long_bonds);
  na_mode =
    SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_nucleic_acid_mode);
  int na_mode_ribbon =
    SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_ribbon_nucleic_acid_mode);
  fancy = SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_valence_mode) == 1;
  auto valence_zero_mode =
    SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_valence_zero_mode);
  
  b = obj->Bond;
  
  for(a = 0; a < obj->NBond; a++) {
    b1 = b->index[0];
    b2 = b->index[1];

    a1 = cs->atmToIdx(b1);
    a2 = cs->atmToIdx(b2);
    if((a1 >= 0) && (a2 >= 0)) {
      if(!variable_width)
        if (AtomInfoCheckBondSetting(G, b, cSetting_line_width)){
          variable_width = true;
          if (valence_found) break;
        }
      auto bd_valence_flag = BondSettingGetWD(G, b, cSetting_valence, valence_flag);
      if(bd_valence_flag) {
        valence_found = true;
        if (variable_width) break;
      }
    }
    b++;
  }

  auto I = new RepWireBond(cs, state);

  I->primitiveCGO = CGONew(G);

  CGOSpecialWithArg(I->primitiveCGO, LINE_LIGHTING, 0.f);

  CGOSpecial(I->primitiveCGO, LINEWIDTH_FOR_LINES);
  CGOBegin(I->primitiveCGO, GL_LINES);

  if(obj->NBond) {

    if(valence_found)           /* build list of up to 2 connected atoms for each atom */
      other = ObjectMoleculeGetPrioritizedOtherIndexList(obj, cs);

    if(ok && (cartoon_side_chain_helper || ribbon_side_chain_helper)) {
      SideChainHelperMarkNonCartoonBonded(marked, obj, cs,
          cartoon_side_chain_helper,
          ribbon_side_chain_helper);
    }


    b = obj->Bond;

    for(a = 0; ok && a < obj->NBond; ++a, ++b) {
      b1 = b->index[0];
      b2 = b->index[1];
      ord = b->order;

      if (ord == 0 && valence_zero_mode == 0)
        continue;

      a1 = cs->atmToIdx(b1);
      a2 = cs->atmToIdx(b2);
      if((a1 >= 0) && (a2 >= 0)) {

        AtomInfoType *ati1 = obj->AtomInfo + b1;
        AtomInfoType *ati2 = obj->AtomInfo + b2;

        s1 = (ati1->visRep & cRepLineBit);
        s2 = (ati2->visRep & cRepLineBit);

        if(s1 ^ s2){
          if(!half_bonds) {
            if(line_stick_helper &&
               (((!s1) && (cRepCylBit & ati1->visRep) && !(cRepCylBit & ati2->visRep)) ||
                ((!s2) && (cRepCylBit & ati2->visRep) && !(cRepCylBit & ati1->visRep))))
              s1 = s2 = 1;      /* turn on line when both stick and line are alternately shown */
            else {
              s1 = 0;
              s2 = 0;
            }
          }
        }

        auto const s1_before_symop = s1;
        auto const s2_before_symop = s2;
        int symop_pass = 0;

        pymol::SymOp symop[2] = {pymol::SymOp(), b->symop_2};
        assert(!symop[0]);
        float vv_buf[2][3];

      inv_sym_bond:

        v1 = cs->coordPtrSym(a1, symop[0], vv_buf[0], symop_pass);
        v2 = cs->coordPtrSym(a2, symop[1], vv_buf[1], symop_pass);

        if (!v1 || !v2) {
          PRINTFB(G, FB_RepCylBond, FB_Warnings)
          " %s-Warning: Failed to get symmetry coordiantes\n",
              __func__ ENDFB(G);
          continue;
        }

        // show half-bond for atom which connects to a symmetry mate
        s1 = s1_before_symop && !symop[0];
        s2 = s2_before_symop && !symop[1];

        if(hide_long && (s1 || s2)) {
          float cutoff = (ati1->vdw + ati2->vdw) * _0p9;
          if(!within3f(v1, v2, cutoff)) /* atoms separated by more than 90% of the sum of their vdw radii */
            s1 = s2 = 0;
        }

        if(s1 || s2) {
          float rgb1[3], rgb2[3];
          int terminal = false;

          auto bd_valence_flag = BondSettingGetWD(G, b, cSetting_valence, valence_flag);
          auto bd_line_color = BondSettingGetWD(G, b, cSetting_line_color, line_color);

          if(fancy && bd_valence_flag && (b->order > 1)) {
            terminal = IsBondTerminal(obj, b1, b2);
          }

          if(variable_width) {
            auto bd_line_width = BondSettingGetWD(G, b, cSetting_line_width, line_width);
            if (last_line_width!=bd_line_width){
              CGOSpecialWithArg(I->primitiveCGO, LINEWIDTH_FOR_LINES, bd_line_width);
              last_line_width = bd_line_width;
            }
          }

          if(bd_line_color < 0) {
            if(bd_line_color == cColorObject) {
              c1 = (c2 = obj->Color);
            } else if(ColorCheckRamped(G, bd_line_color)) {
              c1 = (c2 = bd_line_color);
            } else {
              c1 = ati1->color;
              c2 = ati2->color;
            }
          } else {
            c1 = (c2 = bd_line_color);
          }

          if (line_stick_helper && (ati1->visRep & ati2->visRep & cRepCylBit)) {
            s1 = s2 = 0;
          } else if ((ati1->flags & ati2->flags & cAtomFlag_polymer)) {
            // side chain helpers
            if ((cRepCartoonBit & ati1->visRep & ati2->visRep)) {
              bool sc_helper = AtomSettingGetWD(G, ati1,
                  cSetting_cartoon_side_chain_helper, cartoon_side_chain_helper);

              if (!sc_helper)
                AtomSettingGetIfDefined(G, ati2, cSetting_cartoon_side_chain_helper, &sc_helper);

              if (sc_helper &&
                  SideChainHelperFilterBond(G, marked, ati1, ati2, b1, b2, na_mode, &c1, &c2))
                s1 = s2 = 0;
            }

            if ((s1 || s2) && (cRepRibbonBit & ati1->visRep & ati2->visRep)) {
              bool sc_helper = AtomSettingGetWD(G, ati1,
                  cSetting_ribbon_side_chain_helper, ribbon_side_chain_helper);

              if (!sc_helper)
                AtomSettingGetIfDefined(G, ati2, cSetting_ribbon_side_chain_helper, &sc_helper);

              if (sc_helper &&
                  SideChainHelperFilterBond(G, marked, ati1, ati2, b1, b2, na_mode_ribbon, &c1, &c2))
                s1 = s2 = 0;
            }
          }

          bool isRamped = false;
          isRamped = ColorGetCheckRamped(G, c1, v1, rgb1, state);
          isRamped = ColorGetCheckRamped(G, c2, v2, rgb2, state) | isRamped;
          if (s1 || s2){
            if (ord == 0 && valence_zero_mode == 2) {
              ord = 1;
            }

            if (!ord){
              RepWireZeroOrderBond(I->primitiveCGO, s1, s2, v1, v2, rgb1, rgb2, b1, b2, a, .15f, .15f, ati1->masked, ati2->masked);
            } else if (!bd_valence_flag || ord <= 1){
              RepLine(I->primitiveCGO, s1, s2, isRamped, v1, v2, rgb1, b1, b2, a, rgb2, ati1->masked, ati2->masked);
            } else {
              if (ord == 4){
                RepAromatic(I->primitiveCGO, s1, s2, isRamped, v1, v2, other, a1, a2, cs->Coord, rgb1, rgb2, valence, 0, b1, b2, a, ati1->masked, ati2->masked);
              } else {
                RepValence(I->primitiveCGO, s1, s2, isRamped, v1, v2, other, a1, a2, cs->Coord, rgb1, rgb2, ord, valence, fancy && !terminal, b1, b2, a, ati1->masked, ati2->masked);
              }
            }
            line_counter++;
          }
        }

        // If this was a half-bond to a symmetry mate, do another pass and
        // render the other half.
        if (symop_pass == 0 && ati1 != ati2 && symop[1]) {
          symop_pass = 1;
          std::swap(symop[0], symop[1]);
          goto inv_sym_bond;
        }
      }
      ok &= !G->Interrupt;
    }
  }
  CGOEnd(I->primitiveCGO);
  CGOSpecialWithArg(I->primitiveCGO, LINE_LIGHTING, 1.f);
  CGOStop(I->primitiveCGO);
  FreeP(marked);
  FreeP(other);
  if (!ok || !line_counter){
    delete I;
    I = nullptr;
  }
  return (Rep *) I;
}

