

/* 
A* -------------------------------------------------------------------
B* This file contains source code for the PyMOL computer program
C* Copyright (c) Schrodinger, LLC. 
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_std.h"
#include"Matrix.h"
#include"CGO.h"
#include"Err.h"
#include"Crystal.h"
#include"Feedback.h"
#include"PConv.h"
#include"Vector.h"

#include <algorithm>

PyObject *CrystalAsPyList(const CCrystal * I)
{
  PyObject *result = nullptr;

  if(I) {
    result = PyList_New(2);
    PyList_SetItem(result, 0, PConvFloatArrayToPyList(I->Dim, 3));
    PyList_SetItem(result, 1, PConvFloatArrayToPyList(I->Angle, 3));
  }
  return (PConvAutoNone(result));
}

int CrystalFromPyList(CCrystal * I, PyObject * list)
{
  int ok = true, rok = true;
  int ll = 0;
  if(ok)
    ok = (I != nullptr);
  if(ok)
    ok = PyList_Check(list);
  if(ok)
    ll = PyList_Size(list);
  rok = ok;
  if(ok && (ll > 0))
    ok = PConvPyListToFloatArrayInPlace(PyList_GetItem(list, 0), I->Dim, 3);
  if(ok && (ll > 1))
    ok = PConvPyListToFloatArrayInPlace(PyList_GetItem(list, 1), I->Angle, 3);

  /* TO SUPPORT BACKWARDS COMPATIBILITY...
     Always check ll when adding new PyList_GetItem's */

  return (rok);
}

CCrystal::CCrystal(PyMOLGlobals* GParam) : G(GParam)
{
}

void CrystalDump(const CCrystal * I)
{
  PyMOLGlobals *G = I->G;
  int i;

  PRINTF
    " Crystal: Unit Cell         %8.3f %8.3f %8.3f\n", I->Dim[0], I->Dim[1], I->Dim[2]
    ENDF(G);
  PRINTF
    " Crystal: Alpha Beta Gamma  %8.3f %8.3f %8.3f\n",
    I->Angle[0], I->Angle[1], I->Angle[2]
    ENDF(G);
  PRINTF " Crystal: RealToFrac Matrix\n" ENDF(G);
  for(i = 0; i < 3; i++) {
    PRINTF " Crystal: %9.4f %9.4f %9.4f\n",
      I->realToFrac()[i * 3], I->realToFrac()[i * 3 + 1], I->realToFrac()[i * 3 + 2]
      ENDF(G);
  }
  PRINTF " Crystal: FracToReal Matrix\n" ENDF(G);
  for(i = 0; i < 3; i++) {
    PRINTF
      " Crystal: %9.4f %9.4f %9.4f\n",
      I->fracToReal()[i * 3], I->fracToReal()[i * 3 + 1], I->fracToReal()[i * 3 + 2]
      ENDF(G);
  }
  PRINTF " Crystal: Unit Cell Volume %8.0f.\n", I->unitCellVolume() ENDF(G);

}

static float unitCellVertices[][3] = { {0,0,0}, {1,0,0}, {1,1,0}, {0,1,0}, // bottom 4 vertices
				       {0,0,1}, {1,0,1}, {1,1,1}, {0,1,1} }; // top 4 vertices
static float const unitCellVerticesCentered[][3] = {
    {-0.5, -0.5, -0.5},
    {0.5, -0.5, -0.5},
    {0.5, 0.5, -0.5},
    {-0.5, 0.5, -0.5},
    {-0.5, -0.5, 0.5},
    {0.5, -0.5, 0.5},
    {0.5, 0.5, 0.5},
    {-0.5, 0.5, 0.5},
};

static int unitCellLineIndices[] = { 0, 1, 1, 2, 2, 3, 3, 0,   // bottom 4 lines
				     4, 5, 5, 6, 6, 7, 7, 4,   // top 4 lines
				     0, 4, 1, 5, 2, 6, 3, 7 }; // 4 connector lines

CGO *CrystalGetUnitCellCGO(const CCrystal * I)
{
  PyMOLGlobals *G = I->G;
  float v[3];
  CGO *cgo = nullptr;

  auto const ucv = SettingGet<bool>(G, cSetting_cell_centered)
                       ? unitCellVerticesCentered
                       : unitCellVertices;

  if(I) {
    cgo = CGONew(G);
    CGODisable(cgo, GL_LIGHTING);

    float *vertexVals = CGODrawArrays(cgo, GL_LINES, CGO_VERTEX_ARRAY, 24);
    for (int pl = 0; pl < 24 ; pl++){
      transform33f3f(I->fracToReal(), ucv[unitCellLineIndices[pl]], v);
      copy3f(v, &vertexVals[pl*3]);
    }

    CGOEnable(cgo, GL_LIGHTING);
    CGOStop(cgo);
  }
  return (cgo);
}

float CCrystal::unitCellVolume() const
{
  return determinant33f(fracToReal());
}

void CCrystal::setDims(const float* dims)
{
  setDims(dims[0], dims[1], dims[2]);
}

void CCrystal::setDims(float a, float b, float c)
{
  invalidateMatrices();

  Dim[0] = a;
  Dim[1] = b;
  Dim[2] = c;
}

void CCrystal::setAngles(const float* angles)
{
  setAngles(angles[0], angles[1], angles[2]);
}

void CCrystal::setAngles(float alpha, float beta, float gamma)
{
  invalidateMatrices();

  Angle[0] = alpha ? alpha : 90;
  Angle[1] = beta ? beta : 90;
  Angle[2] = gamma ? gamma : 90;
}

const float* CCrystal::realToFrac() const
{
  if (!m_RealToFracValid) {
    double f2r[9], r2f[9];
    std::copy_n(fracToReal(), 9, f2r);
    xx_matrix_invert(r2f, f2r, 3);

    auto this_mutable = const_cast<CCrystal*>(this);
    this_mutable->m_RealToFracValid = true;
    std::copy_n(r2f, 9, this_mutable->RealToFrac);
  }

  return RealToFrac;
}

const float* CCrystal::fracToReal() const
{
  if (!m_FracToRealValid) {
    auto this_mutable = const_cast<CCrystal*>(this);
    this_mutable->m_FracToRealValid = true;
    auto* f2r = this_mutable->FracToReal;

    identity33f(f2r);

    if (Dim[0] && Dim[1] && Dim[2] && Angle[0] && Angle[1] && Angle[2]) {
      float cabg[3]; // Cosine of axis angle
      float sabg[3]; // Singe of axis angle

      for (int i = 0; i < 3; ++i) {
        cabg[i] = cos(Angle[i] * PI / 180.0);
        sabg[i] = sin(Angle[i] * PI / 180.0);
      }

      float const cabgs[3] = {
          (cabg[1] * cabg[2] - cabg[0]) / (sabg[1] * sabg[2]),
          (cabg[2] * cabg[0] - cabg[1]) / (sabg[2] * sabg[0]),
          (cabg[0] * cabg[1] - cabg[2]) / (sabg[0] * sabg[1]),
      };

      auto const sabgs1 = sqrt1d(1.0 - cabgs[0] * cabgs[0]);

      f2r[0] = Dim[0];
      f2r[1] = cabg[2] * Dim[1];
      f2r[2] = cabg[1] * Dim[2];
      f2r[4] = sabg[2] * Dim[1];
      f2r[5] = -sabg[1] * cabgs[0] * Dim[2];
      f2r[8] = sabg[1] * sabgs1 * Dim[2];
    }
  }

  return FracToReal;
}

void CCrystal::setFracToReal(const float* f2c)
{
  m_FracToRealValid = true;
  m_RealToFracValid = false;

  std::copy_n(f2c, 9, FracToReal);

  float f2c_t[9];
  transpose33f33f(f2c, f2c_t);

  Dim[0] = length3f(f2c_t + 0);
  Dim[1] = length3f(f2c_t + 3);
  Dim[2] = length3f(f2c_t + 6);

  Angle[0] = rad_to_deg(get_angle3f(f2c_t + 3, f2c_t + 6));
  Angle[1] = rad_to_deg(get_angle3f(f2c_t + 0, f2c_t + 6));
  Angle[2] = rad_to_deg(get_angle3f(f2c_t + 0, f2c_t + 3));
}

bool CCrystal::isSuspicious() const
{
  return is_identityf(3, fracToReal(), R_SMALL4) || //
         unitCellVolume() < R_SMALL4;
}
