/*******************************************************************************
*                                                                              *
*                                   Viewmol                                    *
*                                                                              *
*                               I N T E R N . C                                *
*                                                                              *
*                 Copyright (c) Joerg-R. Hill, October 2003                    *
*                                                                              *
********************************************************************************
*
* $Id: intern.c,v 1.6 2003/11/07 11:05:17 jrh Exp $
* $Log: intern.c,v $
* Revision 1.6  2003/11/07 11:05:17  jrh
* Release 2.4
*
* Revision 1.5  2000/12/10 15:09:37  jrh
* Release 2.3
*
* Revision 1.4  1999/05/24 01:26:04  jrh
* Release 2.2.1
*
* Revision 1.3  1999/02/07 21:51:39  jrh
* Release 2.2
*
* Revision 1.2  1998/01/26 00:48:12  jrh
* Release 2.1
*
* Revision 1.1  1996/12/10  18:41:35  jrh
* Initial revision
*
*/
#include<math.h>
#include<stdio.h>
#include "viewmol.h"

double bondAverage(struct MOLECULE *, int);
double bondLength(struct ATOM *, int, int);
double bondAngle(struct ATOM *, int, int, int);
double torsionAngle(struct ATOM *, int, int, int, int);
double dist(double, double, double, double, double, double);
double angle(double, double, double, double, double, double, double,
             double, double);
double torsion(double, double, double, double, double, double, double,
               double, double, double, double, double);

extern struct MOLECULE *molecules;
extern double bndfac;
extern int debug;

int calcInternal(struct MOLECULE *mol, int n)
{
  int uc;
  register int i, j, k, l;

  uc=0;
  for (i=0; i<4; i++)
    if (mol->internals[n].atoms[i] != -1 && (mol->internals[n].atoms[i] & 0x20000000)) uc++;
  switch (mol->internals[n].type)
  {
    case BONDAVERAGE: if (uc == 0)
                      {
                        mol->internals[n].value=bndfac*bondAverage(mol,
                                                mol->internals[n].atoms[0]);
                        i=mol->internals[n].atoms[0];
                        mol->internals[n].x=mol->atoms[i].x+0.05;
                        mol->internals[n].y=mol->atoms[i].y+0.05;
                        mol->internals[n].z=mol->atoms[i].z+0.05;
                      }
                      else
                        return(FALSE);
                      break;
    case BONDLENGTH:  i=mol->internals[n].atoms[0];
                      j=mol->internals[n].atoms[1];
                      switch (uc)
                      {
                        /* previous use: case 4 ??? */	 
                        case 2:  i&=0x1fffffff;
                                 j&=0x1fffffff;
                                 mol->internals[n].value=bndfac*bondLength(mol->unitcell->corners, i, j);
                                 mol->internals[n].x=(mol->unitcell->corners[i].x+mol->unitcell->corners[j].x)*0.5+0.05;
                                 mol->internals[n].y=(mol->unitcell->corners[i].y+mol->unitcell->corners[j].y)*0.5+0.05;
                                 mol->internals[n].z=(mol->unitcell->corners[i].z+mol->unitcell->corners[j].z)*0.5+0.05;
                                 break;
                        case 0:  mol->internals[n].value=bndfac*bondLength(mol->atoms, i, j);
                                 mol->internals[n].x=(mol->atoms[i].x+mol->atoms[j].x)*0.5+0.05;
                                 mol->internals[n].y=(mol->atoms[i].y+mol->atoms[j].y)*0.5+0.05;
                                 mol->internals[n].z=(mol->atoms[i].z+mol->atoms[j].z)*0.5+0.05;
                                 break;
                        default: return(FALSE);
                      }
                      break;
    case ANGLE:       i=mol->internals[n].atoms[0];
                      j=mol->internals[n].atoms[1];
                      k=mol->internals[n].atoms[2];
                      switch (uc)
                      {
                        /* previous use: case 4 ??? */	 
                        case 3:  i&=0x1fffffff;
                                 j&=0x1fffffff;
                                 k&=0x1fffffff;
                                 mol->internals[n].value=bondAngle(mol->unitcell->corners, i, j, k);
                                 mol->internals[n].x=mol->unitcell->corners[j].x+0.05;
                                 mol->internals[n].y=mol->unitcell->corners[j].y+0.05;
                                 mol->internals[n].z=mol->unitcell->corners[j].z+0.05;
                                 break;
                        case 0:
                                 mol->internals[n].value=bondAngle(mol->atoms, i, j, k);
                                 mol->internals[n].x=mol->atoms[j].x+0.05;
                                 mol->internals[n].y=mol->atoms[j].y+0.05;
                                 mol->internals[n].z=mol->atoms[j].z+0.05;
                                 break;
                        default: return(FALSE);
                      }
                      break;
    case TORSION:     i=mol->internals[n].atoms[0];
                      j=mol->internals[n].atoms[1];
                      k=mol->internals[n].atoms[2];
                      l=mol->internals[n].atoms[3];
                      switch (uc)
                      {
                        case 4:  i&=0x1fffffff;
                                 j&=0x1fffffff;
                                 k&=0x1fffffff;
                                 l&=0x1fffffff;
                                 mol->internals[n].value=torsionAngle(mol->unitcell->corners, i, j, k, l);
                                 mol->internals[n].x=(mol->unitcell->corners[j].x+mol->unitcell->corners[k].x)*0.5+0.05;
                                 mol->internals[n].y=(mol->unitcell->corners[j].y+mol->unitcell->corners[k].y)*0.5+0.05;
                                 mol->internals[n].z=(mol->unitcell->corners[j].z+mol->unitcell->corners[k].z)*0.5+0.05;
                                 break;
                        case 0:
                                 mol->internals[n].value=torsionAngle(mol->atoms, i, j, k, l);
                                 mol->internals[n].x=(mol->atoms[j].x+mol->atoms[k].x)*0.5+0.05;
                                 mol->internals[n].y=(mol->atoms[j].y+mol->atoms[k].y)*0.5+0.05;
                                 mol->internals[n].z=(mol->atoms[j].z+mol->atoms[k].z)*0.5+0.05;
                                 break;
                        default: return(FALSE);
                      }
                      if (mol->internals[n].value > 360.) return(FALSE);
                      break;
  }
  if (debug) printf("Internal coordinate %d: %d-%d-%d-%d, %f\n", n,
                    mol->internals[n].atoms[0], mol->internals[n].atoms[1],
                    mol->internals[n].atoms[2], mol->internals[n].atoms[3],
                    mol->internals[n].value);
  return(TRUE);
}

double bondAverage(struct MOLECULE *mol, int i)
{
  register double value=0.0;
  register int j, k=0;

  for (j=0; j<mol->nb; j++)
  {
    if (mol->bonds[j].first == i ||
        mol->bonds[j].second == i)
    {
      value+=bondLength(mol->atoms, mol->bonds[j].first, mol->bonds[j].second);
      k++;
    }
  }
  if (k > 0)
    value/=(double)k;
  else
    value=0.0;
  return(value);
}

double bondLength(struct ATOM *atoms, int i, int j)
{
  return(dist(atoms[i].x, atoms[i].y, atoms[i].z,
              atoms[j].x, atoms[j].y, atoms[j].z));
}

double bondAngle(struct ATOM *atoms, int i, int j, int k)
{
  return(angle(atoms[i].x, atoms[i].y, atoms[i].z,
               atoms[j].x, atoms[j].y, atoms[j].z,
               atoms[k].x, atoms[k].y, atoms[k].z));
}

double torsionAngle(struct ATOM *atoms, int i, int j, int k, int l)
{
  return(torsion(atoms[i].x, atoms[i].y, atoms[i].z,
                 atoms[j].x, atoms[j].y, atoms[j].z,
                 atoms[k].x, atoms[k].y, atoms[k].z,
                 atoms[l].x, atoms[l].y, atoms[l].z));
}

double dist(double x1, double y1, double z1, double x2, double y2, double z2)
{
  return(sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)+(z1-z2)*(z1-z2)));
}

double angle(double x1, double y1, double z1, double x2, double y2, double z2,
             double x3, double y3, double z3)
{
  double todeg=45.0/atan(1.0);
  double xa, ya, za, xb, yb, zb, r;

  xa=x1-x2;
  ya=y1-y2;
  za=z1-z2;
  xb=x3-x2;
  yb=y3-y2;
  zb=z3-z2;
  r=dist(x1, y1, z1, x2, y2, z2)*dist(x3, y3, z3, x2, y2, z2);
  r=(xa*xb+ya*yb+za*zb)/r;
  r=r > 1.0 ? 1.0 : r < -1.0 ? -1.0 : r;
  return(todeg*acos(r));
}

double torsion(double x1, double y1, double z1, double x2, double y2, double z2,
               double x3, double y3, double z3, double x4, double y4, double z4)
{
  double todeg=45.0/atan(1.0);
  double xa, ya, za, xb, yb, zb, xc, yc, zc, xd, yd, zd, xe, ye, ze, xf, yf, zf;
  double sgn, r;

  xa=x1-x2;
  ya=y1-y2;
  za=z1-z2;
  xb=x3-x2;
  yb=y3-y2;
  zb=z3-z2;
  xc=x4-x3;
  yc=y4-y3;
  zc=z4-z3;
  xd=ya*zb-yb*za;
  yd=xb*za-xa*zb;
  zd=xa*yb-xb*ya;
  xe=yc*zb-yb*zc;
  ye=xb*zc-xc*zb;
  ze=xc*yb-xb*yc;
  xf=yd*ze-ye*zd;
  yf=xe*zd-xd*ze;
  zf=xd*ye-xe*yd;
  sgn=xf*xb+yf*yb+zf*zb;
  r=sqrt((xd*xd+yd*yd+zd*zd)*(xe*xe+ye*ye+ze*ze));

/* The torsion angle is undefined (linear arrangement of atoms),
   return 400.0 as torsion angle which is outside the expected
   range */

  if (fabs(r) < 1.0e-6) return((double)400.);
  r=(xd*xe+yd*ye+zd*ze)/r;
  if (fabs(r) > 1.0) r/=fabs(r);
  if (sgn < 0.0)
    return(-todeg*acos(r));
  else
    return(todeg*acos(r));
}
