/*******************************************************************************
*                                                                              *
*                                   Viewmol                                    *
*                                                                              *
*                              M K U N I T C . C                               *
*                                                                              *
*                 Copyright (c) Joerg-R. Hill, October 2003                    *
*                                                                              *
********************************************************************************
*
* $Id: mkunitc.c,v 1.7 2004/08/29 14:56:58 jrh Exp $
* $Log: mkunitc.c,v $
* Revision 1.7  2004/08/29 14:56:58  jrh
* Release 2.4.1
*
* Revision 1.6  2003/11/07 11:07:10  jrh
* Release 2.4
*
* Revision 1.5  2000/12/10 15:11:57  jrh
* Release 2.3
*
* Revision 1.4  1999/05/24 01:26:34  jrh
* Release 2.2.1
*
* Revision 1.3  1999/02/07 21:52:54  jrh
* Release 2.2
*
* Revision 1.1  1996/12/10  18:42:14  jrh
* Initial revision
*
*/
#include<math.h>
#include<stdio.h>
#include<string.h>
#include "viewmol.h"

#define MIN(a, b) (a) < (b) ? (a) : (b)
#define THRESHOLD 1.0e-6

void makeUnitCell(double, double, double, double, double, double, double *, int);
void fractionalToCartesian(struct ATOM *, double, double, double, double,
                           double, double, struct MOLECULE *, int, int);
double *cartesianToFractional(double, double, double, double, double, double,
                              double *, double *, double *, int *, int);
void expandCell(int, int);
void addBoundaryAtoms(struct MOLECULE *);
void shiftUnitCell(struct MOLECULE *);

extern struct WINDOW windows[];
extern struct MOLECULE *molecules;
extern double forceScale;

extern int    makeConnectivity(struct MOLECULE *, int, int);
extern double bondLength(struct ATOM *, int, int);
extern double bondAngle(struct ATOM *, int, int, int);
extern double dist(double, double, double, double, double, double);
extern double angle(double, double, double, double, double, double,
                    double, double, double);
extern void millerPlane(void);
extern void *getmem(size_t, size_t);
extern void *expmem(void *, size_t, size_t);
extern void fremem(void **);

void makeUnitCell(double a, double b, double c, double alpha, double beta,
                  double gamma, double *bravaisMatrix, int frac)
{
/* This function computes from the unit cell lengths
   and angles the cartesian coordinates of the eight
   corners and stores them in corners[], if frac == TRUE
   the input coordinates are fractional coordinates
   and this function will convert them to cartesian
   coordinates */

  struct MOLECULE *mol;
  static int icell=0;
  double torad;
  int n;

  if (windows[VIEWER].set >= 0)
    mol=&molecules[windows[VIEWER].set];
  else
    mol=&molecules[0];

  if (mol->unitcell)
  {
    /* icell is incremented at the end of this function */
    mol->unitcell=(struct UNITCELL *)expmem((void *)mol->unitcell, (size_t)(icell+1),
                                            sizeof(struct UNITCELL));
  }
  else
  {
    mol->unitcell=(struct UNITCELL *)getmem((size_t)2, sizeof(struct UNITCELL));
    icell=1;
  }
  if (a > 0.0)
  {
    mol->unitcell[icell].a=a;
    mol->unitcell[icell].b=b;
    mol->unitcell[icell].c=c;
    mol->unitcell[icell].alpha=alpha;
    mol->unitcell[icell].beta=beta;
    mol->unitcell[icell].gamma=gamma;
  }
  mol->unitcell[icell].nc=8;
  mol->unitcell[icell].showMiller=FALSE;
  mol->unitcell[icell].miller[0]=0;
  mol->unitcell[icell].miller[1]=mol->unitcell[icell].miller[2]=1;
  mol->unitcell[icell].nmiller=0;
  mol->unitcell[icell].corners=(struct ATOM *)getmem((size_t)mol->unitcell[icell].nc,
                                                  sizeof(struct ATOM));
  
  torad=atan(1.0)/45.0;
  n=0;
  mol->unitcell[icell].corners[n].x=0.0;
  mol->unitcell[icell].corners[n].y=0.0;
  mol->unitcell[icell].corners[n].z=0.0;
  mol->unitcell[icell].corners[n].ref=n;
  mol->unitcell[icell].corners[n++].flags=ORIGINAL;
  if (a < 0.0)
  {
    mol->unitcell[icell].corners[n].x=bravaisMatrix[0];
    mol->unitcell[icell].corners[n].y=bravaisMatrix[1];
    mol->unitcell[icell].corners[n].z=bravaisMatrix[2];
  }
  else
  {
    mol->unitcell[icell].corners[n].x=a;
    mol->unitcell[icell].corners[n].y=0.0;
    mol->unitcell[icell].corners[n].z=0.0;
  }
/*printf("%10.6f %10.6f %10.6f\n", mol->unitcell[icell].corners[n].x/0.52917706,
                                   mol->unitcell[icell].corners[n].y/0.52917706,
                                   mol->unitcell[icell].corners[n].z/0.52917706); */
  mol->unitcell[icell].corners[n].ref=n;
  mol->unitcell[icell].corners[n++].flags=ORIGINAL;
  if (a < 0.0)
  {
    mol->unitcell[icell].corners[n].x=bravaisMatrix[3];
    mol->unitcell[icell].corners[n].y=bravaisMatrix[4];
    mol->unitcell[icell].corners[n].z=bravaisMatrix[5];
  }
  else
  {
    mol->unitcell[icell].corners[n].x=b*cos(torad*gamma);
    mol->unitcell[icell].corners[n].y=sqrt(b*b-mol->unitcell[icell].corners[n].x*
                                           mol->unitcell[icell].corners[n].x);
    mol->unitcell[icell].corners[n].z=0.0;
  }
/*printf("%10.6f %10.6f %10.6f\n", mol->unitcell[icell].corners[n].x/0.52917706,
                                   mol->unitcell[icell].corners[n].y/0.52917706,
                                   mol->unitcell[icell].corners[n].z/0.52917706); */
  mol->unitcell[icell].corners[n].ref=n;
  mol->unitcell[icell].corners[n++].flags=ORIGINAL;
  if (a < 0.0)
  {
    mol->unitcell[icell].corners[n].x=bravaisMatrix[6];
    mol->unitcell[icell].corners[n].y=bravaisMatrix[7];
    mol->unitcell[icell].corners[n].z=bravaisMatrix[8];
  }
  else
  {
    mol->unitcell[icell].corners[n].x=c*cos(torad*beta);
    mol->unitcell[icell].corners[n].y=(b*c*cos(torad*alpha)-mol->unitcell[icell].corners[n-1].x*
                                      mol->unitcell[icell].corners[n].x)/
                                      mol->unitcell[icell].corners[n-1].y;
    mol->unitcell[icell].corners[n].z=sqrt(c*c-mol->unitcell[icell].corners[n].x*
                                           mol->unitcell[icell].corners[n].x
                                          -mol->unitcell[icell].corners[n].y*
                                           mol->unitcell[icell].corners[n].y);
  }
/*printf("%10.6f %10.6f %10.6f\n", mol->unitcell[icell].corners[n].x/0.52917706,
                                   mol->unitcell[icell].corners[n].y/0.52917706,
                                   mol->unitcell[icell].corners[n].z/0.52917706); */
  mol->unitcell[icell].corners[n].ref=n;
  mol->unitcell[icell].corners[n++].flags=ORIGINAL;
  mol->unitcell[icell].corners[n].x=mol->unitcell[icell].corners[n-1].x+
                                    mol->unitcell[icell].corners[n-3].x;
  mol->unitcell[icell].corners[n].y=mol->unitcell[icell].corners[n-1].y+
                                    mol->unitcell[icell].corners[n-3].y;
  mol->unitcell[icell].corners[n].z=mol->unitcell[icell].corners[n-1].z+
                                    mol->unitcell[icell].corners[n-3].z;
  mol->unitcell[icell].corners[n].ref=n;
  mol->unitcell[icell].corners[n++].flags=ORIGINAL;
  mol->unitcell[icell].corners[n].x=mol->unitcell[icell].corners[n-1].x+
                                    mol->unitcell[icell].corners[n-3].x;
  mol->unitcell[icell].corners[n].y=mol->unitcell[icell].corners[n-1].y+
                                    mol->unitcell[icell].corners[n-3].y;
  mol->unitcell[icell].corners[n].z=mol->unitcell[icell].corners[n-1].z+
                                    mol->unitcell[icell].corners[n-3].z;
  mol->unitcell[icell].corners[n].ref=n;
  mol->unitcell[icell].corners[n++].flags=ORIGINAL;
  mol->unitcell[icell].corners[n].x=mol->unitcell[icell].corners[n-4].x+
                                    mol->unitcell[icell].corners[n-3].x;
  mol->unitcell[icell].corners[n].y=mol->unitcell[icell].corners[n-4].y+
                                    mol->unitcell[icell].corners[n-3].y;
  mol->unitcell[icell].corners[n].z=mol->unitcell[icell].corners[n-4].z+
                                    mol->unitcell[icell].corners[n-3].z;
  mol->unitcell[icell].corners[n].ref=n;
  mol->unitcell[icell].corners[n++].flags=ORIGINAL;
  mol->unitcell[icell].corners[n].x=mol->unitcell[icell].corners[n-6].x+
                                    mol->unitcell[icell].corners[n-5].x;
  mol->unitcell[icell].corners[n].y=mol->unitcell[icell].corners[n-6].y+
                                    mol->unitcell[icell].corners[n-5].y;
  mol->unitcell[icell].corners[n].z=mol->unitcell[icell].corners[n-6].z+
                                    mol->unitcell[icell].corners[n-5].z;
  mol->unitcell[icell].corners[n].ref=n;
  mol->unitcell[icell].corners[n].flags=ORIGINAL;

  mol->unitcell[icell].factor[0]=mol->unitcell[icell].factor[1]=mol->unitcell[icell].factor[2]=1.0;
  (void)memcpy((void *)&(mol->unitcell[0]), (void *)&(mol->unitcell[icell]),
               sizeof(struct UNITCELL));

  if (a < 0.0)
  {
    mol->unitcell[icell].a=bondLength(mol->unitcell[icell].corners, 0, 1);
    mol->unitcell[icell].b=bondLength(mol->unitcell[icell].corners, 0, 2);
    mol->unitcell[icell].c=bondLength(mol->unitcell[icell].corners, 0, 3);
    mol->unitcell[icell].alpha=bondAngle(mol->unitcell[icell].corners, 2, 0, 3);
    mol->unitcell[icell].beta=bondAngle(mol->unitcell[icell].corners, 1, 0, 3);
    mol->unitcell[icell].gamma=bondAngle(mol->unitcell[icell].corners, 1, 0, 2);
  }
  icell++;
  if (frac) fractionalToCartesian(mol->atoms, a, b, c, alpha, beta, gamma, mol,
                                  0, mol->na);
}

void fractionalToCartesian(struct ATOM *atoms, double a, double b, double c,
                           double alpha, double beta, double gamma,
                           struct MOLECULE *mol, int from, int to)
{
  double torad, x2, x3, y2, y3, z3;
  register int i, j, k;

  torad=atan(1.0)/45.0;
  alpha*=torad;
  beta*=torad;
  gamma*=torad;
  x2=b*cos(gamma);
  y2=sqrt(b*b-x2*x2);
  x3=c*cos(beta);
  y3=(b*c*cos(alpha)-x2*x3)/y2;
  z3=sqrt(c*c-x3*x3-y3*y3);
  for (i=from; i<to; i++)
  {
    atoms[i].x = a*atoms[i].x+x2*atoms[i].y+x3*atoms[i].z;
    atoms[i].y =y2*atoms[i].y+y3*atoms[i].z;
    atoms[i].z*=z3;
  }
  if (mol)
  {
    for (i=0; i<mol->nhist; i++)
    {
      k=i*to;
      x2=mol->unitcell->b*cos(mol->unitcell->gamma);
      y2=sqrt(mol->unitcell->b*mol->unitcell->b-x2*x2);
      x3=mol->unitcell->c*cos(mol->unitcell->beta);
      y3=(mol->unitcell->b*mol->unitcell->c*cos(mol->unitcell->alpha)-x2*x3)/y2;
      z3=sqrt(c*c-x3*x3-y3*y3);
      for (j=from; j<to; j++)
      {
        mol->history[k+j].x =mol->unitcell->a*mol->history[k+j].x
                            +x2*mol->history[k+j].y
                            +x3*mol->history[k+j].z;
        mol->history[k+j].y =y2*mol->history[k+j].y
                            +y3*mol->history[k+j].z;
        mol->history[k+j].z*=z3;
      }
    }
  }
}

double *cartesianToFractional(double a, double b, double c, double alpha,
                              double beta, double gamma, double *xmin,
                              double *ymin, double *zmin, int *natoms, int shift)
{
  struct MOLECULE *mol;
  double torad, x2, x3, y2, y3, z3;
  double *frac;
  register int i, j, n=0;

/* Transform cartesian coordinates to fractional coordinates */

  if (windows[VIEWER].set >= 0)
    mol=&molecules[windows[VIEWER].set];
  else
    mol=&molecules[0];
  torad=atan(1.0)/45.0;
  alpha*=torad;
  beta*=torad;
  gamma*=torad;
  x2=b*cos(gamma);
  y2=sqrt(b*b-x2*x2);
  x3=c*cos(beta);
  y3=(b*c*cos(alpha)-x2*x3)/y2;
  z3=1.0/sqrt(c*c-x3*x3-y3*y3);
  y2=1.0/y2;
  frac=(double *)getmem((size_t)(3*mol->na), sizeof(double));
  j=0;
  *xmin=0.0;
  *ymin=0.0;
  *zmin=0.0;
  for (i=0; i<mol->na; i++)
  {
    if (mol->atoms[i].flags & ORIGINAL)
    {
      frac[j+2]=mol->atoms[i].z*z3;
      frac[j+1]=(mol->atoms[i].y-frac[j+2]*y3)*y2;
      frac[j]  =(mol->atoms[i].x-frac[j+1]*x2-frac[j+2]*x3)/a;
      *xmin=MIN(*xmin, frac[j]);
      *ymin=MIN(*ymin, frac[j+1]);
      *zmin=MIN(*zmin, frac[j+2]);
      j+=3;
      n++;
    }
  }
  if (shift)
  {
    j=0;
    for (i=0; i<n; i++)
    {
      if (*xmin < 0.0) frac[j]  -=(*xmin);
      if (*ymin < 0.0) frac[j+1]-=(*ymin);
      if (*zmin < 0.0) frac[j+2]-=(*zmin);
      j+=3;
    }
  }
  *natoms=n;
  return(frac);
}

void expandCell(int doConnectivity, int setWindow)
{
  struct NEW
  {
    double x;
    double y;
    double z;
    int    ref;
  } *new;
  struct MOLECULE *mol;
  double a;
  double xmin, ymin, zmin;
  double *frac;
  int n, nnew;
  register int i, j, k, l, m;

  if (windows[VIEWER].set >= 0)
    mol=&molecules[windows[VIEWER].set];
  else
    mol=&molecules[0];

  n=0;
  for (i=0; i<mol->na; i++)
    if (mol->atoms[i].flags & ORIGINAL) n++;
  frac=cartesianToFractional(mol->unitcell[mol->nhist].a, mol->unitcell[mol->nhist].b, mol->unitcell[mol->nhist].c,
                             mol->unitcell[mol->nhist].alpha, mol->unitcell[mol->nhist].beta,
                             mol->unitcell[mol->nhist].gamma, &xmin, &ymin, &zmin, &n, TRUE);
  new=(struct NEW *)getmem((size_t)(n*ceil(mol->unitcell->factor[0])*ceil(mol->unitcell->factor[1])
                           *ceil(mol->unitcell->factor[2])), sizeof(struct NEW));
  nnew=0;
  for (i=0; i<(int)ceil(mol->unitcell->factor[0]); i++)
  {
    for (j=0; j<(int)ceil(mol->unitcell->factor[1]); j++)
    {
      for (k=0; k<(int)ceil(mol->unitcell->factor[2]); k++)
      {
        if (i == 0 && j == 0 && k == 0) continue;
        l=0;
        for (m=0; m<n; m++)
        {
          new[nnew].x=frac[l++]+(double)i;
          new[nnew].y=frac[l++]+(double)j;
          new[nnew].z=frac[l++]+(double)k;
          if (new[nnew].x <= mol->unitcell->factor[0] &&
              new[nnew].y <= mol->unitcell->factor[1] &&
              new[nnew].z <= mol->unitcell->factor[2])
            new[nnew++].ref=m;
        }
      }
    }
  }
  fremem((void *)&frac);

  /* Extend/shrink atoms structure */
  mol->atoms=(struct ATOM *)expmem((void *)mol->atoms, (size_t)(n+nnew),
                                   sizeof(struct ATOM));
  j=0;
  for (i=n; i<n+nnew; i++)
  {
    mol->atoms[i].x=new[j].x+xmin;
    mol->atoms[i].y=new[j].y+ymin;
    mol->atoms[i].z=new[j].z+zmin;
    k=new[j++].ref;
    mol->atoms[i].rad=mol->atoms[k].rad;
    mol->atoms[i].radScale=mol->atoms[k].radScale;
    mol->atoms[i].mass=mol->atoms[k].mass;
    mol->atoms[i].neutronScatterfac=mol->atoms[k].neutronScatterfac;
    mol->atoms[i].nbonds=mol->atoms[k].nbonds;
    mol->atoms[i].nelectrons=mol->atoms[k].nelectrons;
    mol->atoms[i].ref=k;
    mol->atoms[i].flags=mol->atoms[k].flags & ~ORIGINAL;
    mol->atoms[i].element=mol->atoms[k].element;
    mol->atoms[i].basis=mol->atoms[k].basis;
    strncpy(mol->atoms[i].name, mol->atoms[k].name, 7);
    strncpy(mol->atoms[i].basisname, mol->atoms[k].basisname, MAXLENBASISNAME-1);
  }
  fremem((void *)&new);
  fractionalToCartesian(mol->atoms, mol->unitcell[mol->nhist].a, mol->unitcell[mol->nhist].b,
                        mol->unitcell[mol->nhist].c, mol->unitcell[mol->nhist].alpha, mol->unitcell[mol->nhist].beta,
                        mol->unitcell[mol->nhist].gamma, NULL, n, n+nnew);
  mol->na=n+nnew;
  if (doConnectivity)
  {
    addBoundaryAtoms(mol);
    a=forceScale;
    /* setWindow should not be TRUE if expandCell
       is called due to the user moving to a
       different frame in the optimization history */
    makeConnectivity(mol, setWindow, TRUE);
    forceScale=a;
  }
  if (mol->unitcell->nmiller != 0) millerPlane();
  if (mol->ngridobjects)
  {
    for (i=0; i<mol->ngridobjects; i++)
    {
      mol->gridObjects[i].npoints[0]=mol->gridObjects[i].ngridpoints[0]*mol->unitcell->factor[0];
      mol->gridObjects[i].npoints[1]=mol->gridObjects[i].ngridpoints[1]*mol->unitcell->factor[1];
      mol->gridObjects[i].npoints[2]=mol->gridObjects[i].ngridpoints[2]*mol->unitcell->factor[2];
    }
  }
}

void addBoundaryAtoms(struct MOLECULE *mol)
{
  double cellFactor[3];
  int i, nlast;

  nlast=mol->na;
  cellFactor[0]=mol->unitcell->factor[0];
  cellFactor[1]=mol->unitcell->factor[1];
  cellFactor[2]=mol->unitcell->factor[2];
  mol->unitcell->factor[0]+=THRESHOLD;
  mol->unitcell->factor[1]+=THRESHOLD;
  mol->unitcell->factor[2]+=THRESHOLD;
  expandCell(FALSE, FALSE);
  mol->unitcell->factor[0]=cellFactor[0];
  mol->unitcell->factor[1]=cellFactor[1];
  mol->unitcell->factor[2]=cellFactor[2];
  for (i=nlast; i<mol->na; i++)
  {
    mol->atoms[i].flags|=DISPLAY_ONLY;
/*  printf("%f %f %f %s\n", mol->atoms[i].x, mol->atoms[i].y, mol->atoms[i].z, mol->atoms[i].name);*/
  }
}

void shiftUnitCell(struct MOLECULE *mol)
{
  double *frac;
  double xmin, ymin, zmin;
  double torad, alpha, beta, gamma;
  double x2, y2, x3, y3;
  int n, outside;
  register int i, j;

  n=0;
  for (i=0; i<mol->na; i++)
    if (mol->atoms[i].flags & ORIGINAL) n++;
  frac=cartesianToFractional(mol->unitcell->a, mol->unitcell->b, mol->unitcell->c,
                             mol->unitcell->alpha, mol->unitcell->beta,
                             mol->unitcell->gamma, &xmin, &ymin, &zmin, &n, FALSE);
  outside=FALSE;
  for (i=0; i<3*n; i++)
  {
    if (frac[i] < 0.0 || frac[i] > 1.0)
    {
      outside=TRUE;
      break;
    }
  }
  if (outside)
  {
    torad=atan(1.0)/45.0;
    alpha=torad*mol->unitcell->alpha;
    beta=torad*mol->unitcell->beta;
    gamma=torad*mol->unitcell->gamma;
    x2=mol->unitcell->b*cos(gamma);
    y2=sqrt(mol->unitcell->b*mol->unitcell->b-x2*x2);
    x3=mol->unitcell->c*cos(beta);
    y3=(mol->unitcell->b*mol->unitcell->c*cos(alpha)-x2*x3)/y2;
    xmin=mol->unitcell->a*xmin+x2*ymin+x3*zmin;
    ymin=y2*ymin+y3*zmin;
    zmin*=sqrt(mol->unitcell->c*mol->unitcell->c-x3*x3-y3*y3);
    for (i=0; i<mol->nhist+1; i++)
    {
      for (j=0; j<mol->unitcell[i].nc; j++)
      {
  	mol->unitcell[i].corners[j].x+=xmin;
	mol->unitcell[i].corners[j].y+=ymin;
	mol->unitcell[i].corners[j].z+=zmin;
      }
    }
  }
}
