/*******************************************************************************
*                                                                              *
*                                   Viewmol                                    *
*                                                                              *
*                                B U I L D . C                                 *
*                                                                              *
*                 Copyright (c) Joerg-R. Hill, October 2003                    *
*                                                                              *
********************************************************************************
*
* $Id: build.c,v 1.4 2003/11/07 10:58:28 jrh Exp $
* $Log: build.c,v $
* Revision 1.4  2003/11/07 10:58:28  jrh
* Release 2.4
*
* Revision 1.3  2000/12/10 15:02:33  jrh
* Release 2.3
*
* Revision 1.2  1999/05/24 01:24:52  jrh
* Release 2.2.1
*
* Revision 1.1  1999/02/07 21:45:03  jrh
* Initial revision
*
*
*/
#include<math.h>
#include<stdio.h>
#include<string.h>
#include<Xm/Xm.h>
#include "viewmol.h"

void buildMolecule(struct MOLECULE *, int, int, int);
void replaceAtom(struct MOLECULE *, int);
void addAtom(struct MOLECULE *, int, int);
void changeCoordination(struct MOLECULE *);

extern int makeConnectivity(struct MOLECULE *, int, int);
extern double centerMolecule(struct MOLECULE *);
extern void setWindowSize(double);
extern int findBondPartners(int *, int, int);
extern int findAttachedAtoms(int *, int, int);
extern void changeBondLength(int, int, int *, double);
extern int  changeAngle(int, int, int *, double);
extern void vectorProduct(double *, double *, double *);
extern void *getmem(size_t, size_t);
extern void fremem(void **);
extern void *expmem(void *, size_t, size_t);
extern double bondAngle(struct ATOM *, int, int, int);
extern void clearGeometry(Widget, caddr_t, XmAnyCallbackStruct *);
extern void addUndo(struct MOLECULE *);

extern struct WINDOW windows[];
extern struct MOLECULE *molecules;
extern struct ELEMENT *elements;
extern double torsionDefault;
extern int element, bondType, localGeometry;

void buildMolecule(struct MOLECULE *mol, int picked, int operation, int setWindow)
{
  int which, *list;
  register int i, j, k;

/* Save coordinates before any change */
  if (mol->na > 0) addUndo(mol);

  if (mol->internals == NULL)
    mol->internals=(struct INTERNAL *)getmem((size_t)1, sizeof(struct INTERNAL));
  else
    mol->internals=(struct INTERNAL *)expmem((void *)mol->internals,
                    (size_t)mol->ninternal+1, sizeof(struct INTERNAL));
  switch (operation)
  {
    case DELETE_ATOM:  mol->na--;
                       for (i=picked; i<mol->na; i++)
                       {
                         mol->atoms[i].x=mol->atoms[i+1].x;
                         mol->atoms[i].y=mol->atoms[i+1].y;
                         mol->atoms[i].z=mol->atoms[i+1].z;
                         mol->atoms[i].rad=mol->atoms[i+1].rad;
                         mol->atoms[i].radScale=mol->atoms[i+1].radScale;
                         mol->atoms[i].mass=mol->atoms[i+1].mass;
                         mol->atoms[i].neutronScatterfac=mol->atoms[i+1].neutronScatterfac;
                         mol->atoms[i].nbonds=mol->atoms[i+1].nbonds;
                         mol->atoms[i].nelectrons=mol->atoms[i+1].nelectrons;
                         mol->atoms[i].ref=mol->atoms[i+1].ref;
                         mol->atoms[i].flags=mol->atoms[i+1].flags;
                         mol->atoms[i].element=mol->atoms[i+1].element;
                         mol->atoms[i].basis=mol->atoms[i+1].basis;
                         strcpy(mol->atoms[i].name, mol->atoms[i+1].name);
                         strcpy(mol->atoms[i].basisname, mol->atoms[i+1].basisname);
                       }
                       mol->atoms=(struct ATOM *)expmem((void *)mol->atoms,
                                   (size_t)mol->na, sizeof(struct ATOM));
                       /* Scan through list of internal coordinates and delete all
                          which contain deleted atom */
                       for (i=0; i<mol->ninternal; i++)
                       {
                         if (mol->internals[i].atoms[0] == picked ||
                             mol->internals[i].atoms[1] == picked ||
                             mol->internals[i].atoms[2] == picked ||
                             mol->internals[i].atoms[3] == picked)
                           clearGeometry((Widget)0, (caddr_t)i, (XmAnyCallbackStruct *)0);
                       }
                       /* Scan through lists of bonds added or deleted by user
                          and change numbering of bonded atoms */
                       for (i=0; i<mol->nbAdded; i++)
                       {
                         if (mol->addedBonds[3*i] > picked) mol->addedBonds[3*i]--;
                         if (mol->addedBonds[3*i+1] > picked) mol->addedBonds[3*i+1]--;
                       }
                       for (i=0; i<mol->nbDeleted; i++)
                       {
                         if (mol->deletedBonds[2*i] > picked) mol->deletedBonds[2*i]--;
                         if (mol->deletedBonds[2*i+1] > picked) mol->deletedBonds[2*i+1]--;
                       }
                       /* Change coordination at atoms bond to deleted atom if
                          user desires so */
                       if (localGeometry)
                       {
                         list=(int *)getmem((size_t)mol->nb, sizeof(int));
                         j=0;
                         for (i=0; i<mol->nb; i++)
                         {
                           if (mol->bonds[i].first == picked || mol->bonds[i].second == picked)
                           {
                             list[j++]=i;
                             mol->bonds[i].order=(-1);   /* mark bond as hydrogen bond to ex- */
                           }                             /* clude it from findBondPartners */
                           if (mol->bonds[i].first > picked) mol->bonds[i].first--;
                           if (mol->bonds[i].second > picked) mol->bonds[i].second--;
                         }
                         for (i=0; i<j; i++)
                         {
                           k=list[i];
                           if (mol->bonds[k].first == picked)
                             mol->internals[mol->ninternal].atoms[1]=mol->bonds[k].second;
                           else if (mol->bonds[k].second == picked)
                             mol->internals[mol->ninternal].atoms[1]=mol->bonds[k].first;
                           changeCoordination(mol);
                         }
                         fremem((void **)&list);
                       }
		       makeConnectivity(mol, FALSE, FALSE);
                       break;
    case REPLACE_ATOM: replaceAtom(mol, picked);
                       break;
    case ADD_ATOM:     which=mol->na++;
                       mol->atoms=(struct ATOM *)expmem((void *)mol->atoms,
                                   (size_t)mol->na, sizeof(struct ATOM));
                       replaceAtom(mol, which);
                       if (mol->unitcell) makeConnectivity(mol, FALSE, TRUE);
                       addAtom(mol, which, picked);
                       break;
  }
  if (mol->ninternal == 0)
    fremem((void **)&(mol->internals));
  else
    mol->internals=(struct INTERNAL *)expmem((void *)mol->internals,
                    (size_t)mol->ninternal, sizeof(struct INTERNAL));
}

void replaceAtom(struct MOLECULE *mol, int which)
{
#include "isotopes.h"
  int *move, nmove;
  register int i;

  mol->atoms[which].rad=elements[element].rad;
  mol->atoms[which].radScale=elements[element].radScale;
  mol->atoms[which].element=&elements[element];
  mol->atoms[which].neutronScatterfac=0.0;
  mol->atoms[which].basis=(struct BASISSET *)NULL;
  strcpy(mol->atoms[which].name, elements[element].symbol);
  strcpy(mol->atoms[which].basisname, "");
  for (i=0; i<LASTELEMENT; i++)
  {
    if (!strncmp(mol->atoms[which].name, pse[i], 2))
    {
      mol->atoms[which].mass=isotope[i][0];
      mol->atoms[which].nelectrons=electrons[i];
    }
  }
  /* Adjust bond lengths */
  move=(int *)getmem((size_t)mol->na, sizeof(int));
  for (i=0; i<mol->nb; i++)
  {
    if (mol->bonds[i].first == which || mol->bonds[i].second == which)
    {
      mol->internals[mol->ninternal].atoms[0]=mol->bonds[i].first;
      mol->internals[mol->ninternal].atoms[1]=mol->bonds[i].second;
      mol->internals[mol->ninternal].type=BONDLENGTH;
      move[0]=mol->bonds[i].first == which ? mol->bonds[i].second : mol->bonds[i].first;
      if ((nmove=findAttachedAtoms(move, 1, which)) > 0)
        changeBondLength(mol->ninternal, nmove, move,
                         0.9*(mol->atoms[mol->bonds[i].first].rad
                              +mol->atoms[mol->bonds[i].second].rad));
    }
  }
  fremem((void *)&move);
}

void addAtom(struct MOLECULE *mol, int which, int picked)
{
  mol->atoms[which].flags=ORIGINAL;
  mol->atoms[which].nbonds=0;
  if (picked == -1) /* if atom is not attached to another atom */
  {
    mol->atoms[which].x=0.0;
    mol->atoms[which].y=0.0;
    mol->atoms[which].z=0.0;
    return;
  }
  else
  {
    mol->atoms[which].x=mol->atoms[picked].x+1.0;
    mol->atoms[which].y=mol->atoms[picked].y;
    mol->atoms[which].z=mol->atoms[picked].z;
  }
  mol->nb++;
  mol->bonds=(struct BOND *)expmem((void *)mol->bonds, (size_t)mol->nb,
                                   sizeof(struct BOND));
  mol->bonds[mol->nb-1].first=picked;
  mol->bonds[mol->nb-1].second=which;
  mol->bonds[mol->nb-1].order=1;
  mol->bonds[mol->nb-1].frac=mol->atoms[picked].rad/(mol->atoms[picked].rad+mol->atoms[which].rad);
  mol->internals[mol->ninternal].atoms[0]=which;
  mol->internals[mol->ninternal].atoms[1]=picked;
  mol->internals[mol->ninternal].type=BONDLENGTH;
  changeBondLength(mol->ninternal, 1, &which,
                   0.9*(mol->atoms[which].rad+mol->atoms[picked].rad));
  changeCoordination(mol);
  setWindowSize(centerMolecule(mol));
}

void changeCoordination(struct MOLECULE *mol)
{
  double v1[3], v2[3], v3[3], t=torsionDefault, angle, sign;
  int *bondPartners, *nbonds, max, imax, picked, added=FALSE;
  int linear=FALSE, useDummy=FALSE;
  register int i, j, n;

  bondPartners=(int *)getmem((size_t)mol->na, sizeof(int));
  picked=mol->internals[mol->ninternal].atoms[1];
  bondPartners[0]=picked;
  n=findBondPartners(bondPartners, 1, -1);
  if (n < 3) return;

  bondPartners=(int *)expmem((void *)bondPartners, (size_t)((n+2)*mol->na),
                             sizeof(int));
  nbonds=(int *)getmem((size_t)n, sizeof(int));
  max=0;
  imax=0;
  for (i=1; i<n; i++)
  {
    bondPartners[i*mol->na]=bondPartners[i];
    if (abs(nbonds[i]=findAttachedAtoms(&bondPartners[i*mol->na], 1,
            picked)) > max)
    {
      max=abs(nbonds[i]);
      imax=i;
    }
  }
  mol->internals[mol->ninternal].atoms[3]=bondPartners[imax*mol->na+1];
  angle=180.;
  sign=1.0;
  for (i=1; i<n; i++)
  {
    if (nbonds[i] < 0) continue;
    if (i != imax && (nbonds[i] != mol->na-2 || mol->na < 4))
    {
      mol->internals[mol->ninternal].atoms[0]=bondPartners[i];
      mol->internals[mol->ninternal].atoms[2]=bondPartners[imax];
      if (n > 4 && max <= 1)
        mol->internals[mol->ninternal].atoms[3]=(-1);
      else
      {
        if (bondAngle(mol->atoms, bondPartners[i],
	              picked, bondPartners[imax]) < 179.9)
          linear=FALSE;
        else
        {
          if (nbonds[imax] > 1)
          {
            for (j=0; j<nbonds[imax]; j++)
            {
              if (bondAngle(mol->atoms, bondPartners[i], picked,
                            bondPartners[imax*mol->na+j]) < 179.9)
              {
                mol->internals[mol->ninternal].atoms[3]=bondPartners[imax*mol->na+j];
                break;
              }
            }
          }
          else
            mol->internals[mol->ninternal].atoms[3]=(-1);
          linear=TRUE;
        }
      }
      mol->internals[mol->ninternal].type=ANGLE;
      if (n == 3 && mol->atoms[picked].nelectrons > 4)
      {
        n+=mol->atoms[picked].nelectrons-4;
        added=TRUE;
      }
      switch (n)
      {
        case 3:  (void)changeAngle(mol->ninternal, nbonds[i],
                                   &bondPartners[i*mol->na], 180.);
                 break;
        case 4:  (void)changeAngle(mol->ninternal, nbonds[i],
                                   &bondPartners[i*mol->na], sign*120.0);
                 if (linear) sign=(-1.0);
                 break;
        case 5:  (void)changeAngle(mol->ninternal, nbonds[i],
                                   &bondPartners[i*mol->na], 109.47122);
                 break;
        default: (void)changeAngle(mol->ninternal, nbonds[i],
                                   &bondPartners[i*mol->na], angle);
                 angle=90.;
                 break;
      }
      if (added)
      {
        n-=mol->atoms[picked].nelectrons-4;
        added=FALSE;
      }
      if (n > 3)
      {
        mol->internals[mol->ninternal].type=TORSION;
        if (mol->internals[mol->ninternal].atoms[3] >= 0)
        {
          if (!changeAngle(mol->ninternal, nbonds[i], &bondPartners[i*mol->na], t))
          {
            useDummy=TRUE;
            v1[0]=mol->atoms[bondPartners[i]].x-mol->atoms[picked].x;
            v1[1]=mol->atoms[bondPartners[i]].y-mol->atoms[picked].y;
            v1[2]=mol->atoms[bondPartners[i]].z-mol->atoms[picked].z;
            v2[0]=mol->atoms[bondPartners[imax]].x-mol->atoms[picked].x;
            v2[1]=mol->atoms[bondPartners[imax]].y-mol->atoms[picked].y;
            v2[2]=mol->atoms[bondPartners[imax]].z-mol->atoms[picked].z;
            vectorProduct(v1, v2, v3);
            mol->atoms=(struct ATOM *)expmem((void *)mol->atoms, (size_t)(mol->na+1),
                                             sizeof(struct ATOM));
            mol->atoms[mol->na].x=mol->atoms[bondPartners[imax]].x+v3[0];
            mol->atoms[mol->na].y=mol->atoms[bondPartners[imax]].y+v3[1];
            mol->atoms[mol->na].z=mol->atoms[bondPartners[imax]].z+v3[2];
            mol->internals[mol->ninternal].atoms[3]=mol->na;
            (void)changeAngle(mol->ninternal, nbonds[i], &bondPartners[i*mol->na], t);
          }
          if (n > 5)
            t+=360.0/(double)(n-3);
          else
            t+=360.0/(double)(n-2);
        }
        else
        {
          if (n > 4)
          {
            if (!useDummy)
            {
              useDummy=TRUE;
              v1[0]=mol->atoms[bondPartners[i]].x-mol->atoms[picked].x;
              v1[1]=mol->atoms[bondPartners[i]].y-mol->atoms[picked].y;
              v1[2]=mol->atoms[bondPartners[i]].z-mol->atoms[picked].z;
              v2[0]=mol->atoms[bondPartners[imax]].x-mol->atoms[picked].x;
              v2[1]=mol->atoms[bondPartners[imax]].y-mol->atoms[picked].y;
              v2[2]=mol->atoms[bondPartners[imax]].z-mol->atoms[picked].z;
              vectorProduct(v1, v2, v3);
              mol->atoms=(struct ATOM *)expmem((void *)mol->atoms, (size_t)(mol->na+1),
                                               sizeof(struct ATOM));
              mol->atoms[mol->na].x=mol->atoms[bondPartners[imax]].x+v3[0];
              mol->atoms[mol->na].y=mol->atoms[bondPartners[imax]].y+v3[1];
              mol->atoms[mol->na].z=mol->atoms[bondPartners[imax]].z+v3[2];
            }
            mol->internals[mol->ninternal].atoms[3]=mol->na;
            (void)changeAngle(mol->ninternal, nbonds[i], &bondPartners[i*mol->na], t);
            if (n > 5)
              t+=360.0/(double)(n-3);
            else
              t+=360.0/(double)(n-2);
          }
        }
      }
    }
  }  /* end of for-loop */
  fremem((void **)&bondPartners);
  fremem((void **)&nbonds);
  if (useDummy) mol->atoms=(struct ATOM *)expmem((void *)mol->atoms, (size_t)(mol->na),
                                                 sizeof(struct ATOM));
}
