/*
Copyright (C) 1993 by David H. Gay and Andrew L. Rohl 

dgay@ricx.royal-institution.ac.uk
andrew@ricx.royal-institution.ac.uk

Modified 2003 by Sean David Fleming

sean@ivec.org

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

The GNU GPL can also be found at http://www.gnu.org
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>

#include "gdis.h"
#include "coords.h"
#include "model.h"
#include "matrix.h"
#include "numeric.h"
#include "vector.h"
#include "space.h"
#include "surface.h"
#include "zone.h"
#include "interface.h"

typedef gchar boolean;

/**********************/
/* debugging routines */
/**********************/
void surface_print_planes(GSList *planes)
{
GSList *list;
struct plane_pak *plane;

for (list=planes ; list ; list=g_slist_next(list))
  {
  plane = list->data;

printf("[%2d %2d %2d]", plane->index[0], plane->index[1], plane->index[2]);
printf(" : %f , %f", plane->esurf[0], plane->eatt[0]);
printf("\n");
  }
}

/*********************/
/* duplicate a shift */
/*********************/
gpointer shift_dup(struct shift_pak *src)
{
struct shift_pak *dest;

dest = g_malloc(sizeof(struct shift_pak));
memcpy(dest, src, sizeof(struct shift_pak));
return(dest);
}

/********************************************/
/* allocate for new shift & init for safety */
/********************************************/
gpointer shift_new(gdouble shift)
{
struct shift_pak *sdata;

/* alloc */
sdata = g_malloc(sizeof(struct shift_pak));

sdata->locked = FALSE;
sdata->shift = shift;
sdata->dipole_computed = FALSE;
sdata->dipole = 99.9;
sdata->region[0] = 1;
sdata->region[1] = 1;
sdata->esurf[0] = 0.0;
sdata->esurf[1] = 0.0;
sdata->eatt[0] = 0.0;
sdata->eatt[1] = 0.0;
sdata->bbpa = 0.0;
sdata->gnorm = -1.0;
sdata->procfile = NULL;

return(sdata);
}

/*********************/
/* duplicate a plane */
/*********************/
gpointer plane_dup(struct plane_pak *src)
{
GSList *list;
struct plane_pak *dest;
struct shift_pak *shift;

dest = g_malloc(sizeof(struct plane_pak));

memcpy(dest, src, sizeof(struct plane_pak));
dest->shifts = NULL;
for (list=src->shifts ; list ; list=g_slist_next(list))
  {
  shift = shift_dup(list->data);
  dest->shifts = g_slist_prepend(dest->shifts, shift);
  }
return(dest);
}

/****************************************/
/* determines surface and depth vectors */
/****************************************/
/* TODO - call this to eliminate redundancy in generate_surface() */
#define DEBUG_GENSURF 0
void plane_compute_vectors(struct plane_pak *plane, struct model_pak *src)
{
vector normal;
vector lattice_vector[3];    /* lattice vectors */
vector t_mat[3];             /* transformation matrix */
vector work_lat[3];          /* transformed lattice vectors */
vector rec_work_lat[3];      /* reciprocal transformed lattice vectors */
vector s[3];                 /* space we are trying to fill */
vector tempvec;
vector *sv;
vector a, *v_a=NULL, *v_b=NULL;
boolean s2_found = FALSE;
gint c, i, j, h, k, l, flag;
gint GCD(gint, gint);
gint vector_compare(vector *, vector *);
gdouble inv_denom;
gdouble depth, depth_min;
gdouble gcd, cd, x;
gdouble tmat[9], norm[3], va[3];
gdouble wlat[9];
gdouble sign, vec[3], vec2[3], dn[3];
GSList *list1, *list2, *sv_list1, *sv_list2;

g_assert(src != NULL);

/* find surface normal from the miller index and lattice vectors*/
V_ZERO(normal);

/* setup */
h = plane->m[0];
k = plane->m[1];
l = plane->m[2];

/* acquire lattice vectors */
V_X(lattice_vector[0]) = src->latmat[0];
V_Y(lattice_vector[0]) = src->latmat[3];
V_Z(lattice_vector[0]) = src->latmat[6];
V_X(lattice_vector[1]) = src->latmat[1];
V_Y(lattice_vector[1]) = src->latmat[4];
V_Z(lattice_vector[1]) = src->latmat[7];
V_X(lattice_vector[2]) = src->latmat[2];
V_Y(lattice_vector[2]) = src->latmat[5];
V_Z(lattice_vector[2]) = src->latmat[8];

V_CROSS(tempvec, lattice_vector[1], lattice_vector[2]);
V_QADD(normal, normal, +h*, tempvec);
V_CROSS(tempvec, lattice_vector[2], lattice_vector[0]);
V_QADD(normal, normal, +k*, tempvec);
V_CROSS(tempvec, lattice_vector[0], lattice_vector[1]);
V_QADD(normal, normal, +l*, tempvec);

sv_list1 = NULL;
 
c = (float) GCD(h, k);
cd = c ? c : 1.0;
V_2_ASSIGN(a, = k/cd * ,lattice_vector[0], - h/cd *, lattice_vector[1]);
if (V_MAGSQ(a) > EPSILON) 
  {
  sv = g_malloc(sizeof(vector));
  V_EQUATE(*sv, a);
  sv_list1 = g_slist_prepend(sv_list1, sv);
  }
  
c = (float) GCD(h, l);
cd = c ? c : 1.0;
V_2_ASSIGN(a, = l/cd * ,lattice_vector[0], - h/cd *, lattice_vector[2]);
if (V_MAGSQ(a) > EPSILON) 
  {
  sv = g_malloc(sizeof(vector));
  V_EQUATE(*sv, a);
  sv_list1 = g_slist_prepend(sv_list1, sv);
  }
  
c = (float) GCD(k, l);
cd = c ? c : 1.0;
V_2_ASSIGN(a, = l/cd * ,lattice_vector[1], - k/cd *, lattice_vector[2]);
if (V_MAGSQ(a) > EPSILON) 
  {
  sv = g_malloc(sizeof(vector));
  V_EQUATE(*sv, a);
  sv_list1 = g_slist_prepend(sv_list1, sv);
  }

sv_list2 = NULL;
list1 = sv_list1;
for (i=0 ; i<g_slist_length(sv_list1)-1 ; i++)
  {
  v_a = (vector *) list1->data;
  list1 = list2 = g_slist_next(list1);
  for (j=i+1 ; j<g_slist_length(sv_list1) ; j++)
    {
    v_b = (vector *) list2->data;
    list2 = g_slist_next(list2);


    V_2_ASSIGN(a, = , *v_a, + , *v_b);
    if (V_MAGSQ(a) > EPSILON)
      {
      sv = g_malloc(sizeof(vector));
      V_EQUATE(*sv, a);
      sv_list2 = g_slist_prepend(sv_list2, sv);
      }
    V_2_ASSIGN(a, = , *v_a, - , *v_b);
    if (V_MAGSQ(a) > EPSILON)
      {
      sv = g_malloc(sizeof(vector));
      V_EQUATE(*sv, a);
      sv_list2 = g_slist_prepend(sv_list2, sv);
      }
    }
  }
sv_list1 = g_slist_concat(sv_list1, sv_list2);
sv_list1 = g_slist_sort(sv_list1, (gpointer) vector_compare);

/* set v_a to first (shortest now sorted) vector */
list1 = sv_list1;
v_a = (vector *) list1->data;
list1 = g_slist_next(list1);
/* loop over remaining vectors - find shortest w/ orthogonal component */
while (list1)
  {
  v_b = (vector *) list1->data;
  
  V_CROSS(a, *v_a, *v_b);
  x = V_MAGSQ(a);
  if (x > EPSILON) 
    {
    s2_found = TRUE;
    break;
    }
  list1 = g_slist_next(list1);
  }

/* checks */
if (!s2_found) 
  {
  printf("Failed to find surface vectors.\n");
  return;
  }

g_assert(v_a != NULL);
g_assert(v_b != NULL);

/* calculate transformation matrix */
V_SCALER(t_mat[2], (1.0/V_MAG(normal)), normal);
V_SCALER(t_mat[0], (1.0/V_MAG(*v_a)), *v_a);
V_CROSS(t_mat[1], t_mat[2], t_mat[0]);

ARR3SET(norm, normal.element);
ARR3SET(va, v_a->element);
normalize(norm, 3);
normalize(va, 3);
ARR3SET(&tmat[6], norm);
ARR3SET(&tmat[0], va);
crossprod(&tmat[3], norm, va);

/* calculate transformed lattice vectors */
for (j = 0; j < 3; j++)
  for (i = 0; i < 3; i++)
    V_E(work_lat[j], i) = V_DOT(t_mat[i], lattice_vector[j]);

/* calculate reciprocal transformed lattice vectors */
V_CROSS(tempvec, work_lat[1], work_lat[2]);
inv_denom = 1.0 / V_DOT(tempvec, work_lat[0]);
V_CROSS(tempvec, work_lat[1], work_lat[2]);
V_SCALER(rec_work_lat[0], inv_denom, tempvec);
V_CROSS(tempvec, work_lat[2], work_lat[0]);
V_SCALER(rec_work_lat[1], inv_denom, tempvec);
V_CROSS(tempvec, work_lat[0], work_lat[1]);
V_SCALER(rec_work_lat[2], inv_denom, tempvec);

/* calculate transformed surface vectors */
for (i = 0; i < 3; i++)
  V_E(s[0], i) = V_DOT(t_mat[i], *v_a);
for (i = 0; i < 3; i++)
  V_E(s[1], i) = V_DOT(t_mat[i], *v_b);

/* return transformed surface vectors data in dest->latmat */
  /* NB: latmat[0..8] is a 3x3 matrix so, */
  /*
    | a b 0 |
    | c d 0 |
    | 0 0 0 |

    goes into latmat rows first ie a,b,0,c,d,0,0,0,0
  */
  
/* set s[2] = to depths */
/* depth = 1.0e10; */

/* surface specification */
gcd = GCD(GCD(h, k), GCD(k, l));
VEC3SET(vec, h, k, l);
vecmat(src->rlatmat, vec);
 
/* calculate the Dhkl */
/* NB: we want the correct dspacing wrt hkl, ie DON'T remove the GCD */
/*
dest->surface.dspacing = 1.0/VEC3MAG(vec);
*/

/* actual cut depth */
/* NB: done wrt the FULL depth (ie remove the gcd) */
depth = gcd/VEC3MAG(vec);

/*
dest->surface.depth = depth;
*/
/* round off the shift */
/*
shift = decimal_round(dest->surface.shift, 4);
*/

/* store the work lattice */
for (i=0 ; i<3 ; i++)
  {
  wlat[3*i+0] = V_E(work_lat[0], i);
  wlat[3*i+1] = V_E(work_lat[1], i);
  wlat[3*i+2] = V_E(work_lat[2], i);
  }

/* transfer surface vectors */
VEC3SET(&plane->lattice[0], V_X(s[0]), V_X(s[1]), 0.0);
VEC3SET(&plane->lattice[3], V_Y(s[0]), V_Y(s[1]), 0.0);
VEC3SET(&plane->lattice[6], 0.0, 0.0, 1.0);

/* construct the depth vector */
/* compute the surface normal */
VEC3SET(vec, V_X(s[0]), V_Y(s[0]), 0.0);
VEC3SET(vec2, V_X(s[1]), V_Y(s[1]), 0.0);
crossprod(dn, vec, vec2);

/* search for a vector with the smallest */
/* orthogonal component to the surface vectors */
flag=0;
depth_min=0;
for (i=0 ; i<3 ; i++)
  {
/* ignore zero indices, which yield 0 orthogonal component */
  if (plane->m[i])
    {
/* dotproduct of vector with normal */
    ARR3SET(vec, dn);
    vec[0] *= wlat[i];
    vec[1] *= wlat[i+3];
    vec[2] *= wlat[i+6];
    depth = fabs(vec[0] + vec[1] + vec[2]);

   if (flag)
     {
     if (depth > depth_min)
       continue;
     }
   else
     flag++;

    depth_min = depth;

/* ensure the depth vector is pointing in the +ve z direction */
    if (wlat[i+6] < 0.0)
      sign = -1.0;
    else
      sign = 1.0;

    plane->lattice[2] = sign*wlat[i];
    plane->lattice[5] = sign*wlat[i+3];
    plane->lattice[8] = sign*wlat[i+6];
    }
  }
}

/********************************************/
/* allocate for new plane & init for safety */
/********************************************/
#define DEBUG_CREATE_PLANE 0
gpointer plane_new(gdouble *m, struct model_pak *model)
{
gint h, k, l, n;
gdouble len, vec[3];
struct plane_pak *plane=NULL;

/* checks */
g_return_val_if_fail(model != NULL, NULL);

plane = g_malloc(sizeof(struct plane_pak));

#if DEBUG_CREATE_PLANE
printf("creating: %d %d %d -> ", (gint) m[0], (gint) m[1], (gint) m[2]);
#endif

/* save orig */
h = m[0];
k = m[1];
l = m[2];
n = 2;

/* absence testing function */
if (!model->surface.ignore_symmetry)
  {
  while (surf_sysabs(model, h, k, l) && n<100)
    {
    h = n*m[0];
    k = n*m[1];
    l = n*m[2];
    n++;
    }
  }

if (n == 100)
  {
  printf("WARNING: screwed up systematic absence check.\n");
  h /= 99;
  k /= 99;
  l /= 99;
  n = 2;
  }

#if DEBUG_CREATE_PLANE
printf("%d %d %d  (x %d)\n", h, k, l, n-1);
#endif

/* init */
VEC3SET(plane->m, (gdouble) h, (gdouble) k, (gdouble) l);
VEC3SET(plane->norm, (gdouble) h, (gdouble) k, (gdouble) l);
VEC3SET(plane->index, h, k, l);
plane->shifts = NULL;
plane->vertices = NULL;

/* calc Dhkl */
VEC3SET(vec, h, k, l);
vecmat(model->rlatmat, vec);
plane->dhkl = 1.0/VEC3MAG(vec);

/* morphology prediction */
len = VEC3MAG(vec);
if (len > FRACTION_TOLERANCE)
  {
  VEC3MUL(vec, 1.0/len);
  VEC3MUL(vec, -1.0);
  ARR3SET(plane->x, vec);
  }
else
  {
  gui_text_show(WARNING, "plane of zero length created.\n");
  }

plane->esurf_shift = 0.0;
plane->eatt_shift = 0.0;
plane->bbpa_shift = 0.0;
plane->esurf[0] = 0.0;
plane->esurf[1] = 0.0;
plane->eatt[0] = 0.0;
plane->eatt[1] = 0.0;
plane->bbpa = 0.0;
plane->area = 0.0;
plane->f[0] = 0.0;
plane->f[1] = 0.0;
/* calc? */
plane->multiplicity = 1;
plane->present = TRUE;
plane->visible = FALSE;
plane->primary = TRUE;
plane->parent = NULL;
VEC3SET(plane->rx, 0.0, 0.0, 0.0);

/* NEW */
plane_compute_vectors(plane, model);

return(plane);
}

/************************************************************/
/* determine if a particular plane has already been created */
/************************************************************/
gpointer plane_find(gdouble *hkl, struct model_pak *model)
{
GSList *list;
struct plane_pak *comp, *plane;

/* get corrected (sys absent!) miller index */
comp = plane_new(hkl, model);
g_return_val_if_fail(comp != NULL, NULL);

for (list=model->planes ; list ; list=g_slist_next(list))
  {
  plane = list->data;
  if (facet_equiv(model, comp->index, plane->index))
    return(plane);
  }

g_free(comp);
return(NULL);
}

/***********************************/
/* generate symmetry related faces */
/***********************************/
/* FIXME - handle duplication */
#define DEBUG_SURF_SYMMETRY_GENERATE 0
void surf_symmetry_generate(struct model_pak *model)
{
gint i;
gint *sym_hkl;
gdouble m[3];
GSList *list, *new_planes, *sym_equiv, *sym_list;
struct plane_pak *plane=NULL, *new_plane=NULL;

#if DEBUG_SURF_SYMMETRY_GENERATE
printf("======\n");
printf("BEFORE\n");
printf("======\n");
surface_print_planes(model->planes);
#endif

/* compute symmetry related faces */
new_planes = NULL;
for (list=model->planes ; list ; list=g_slist_next(list))
  {
  plane = list->data;
  sym_equiv = get_facet_equiv(model, plane->index);

#if DEBUG_SURF_SYMMETRY_GENERATE
printf(" *** {%d %d %d}\n", plane->index[0], plane->index[1], plane->index[2]);
#endif

/* check each non-primary face for extinctions */
  i=0;
  sym_list = g_slist_next(sym_equiv);
  while (sym_list)
    {
/* get the (normalized) hkl's */
    sym_hkl = sym_list->data;
    ARR3SET(m, sym_hkl);

    new_plane = plane_new(m, model);
    if (plane)
      {
      new_plane->primary = FALSE;
      new_plane->parent = plane;
      new_plane->esurf[0] = plane->esurf[0];
      new_plane->esurf[1] = plane->esurf[1];
      new_plane->eatt[0] = plane->eatt[0];
      new_plane->eatt[1] = plane->eatt[1];
      new_plane->bbpa = plane->bbpa;
      new_plane->area = plane->area;

#if DEBUG_SURF_SYMMETRY_GENERATE
printf("   > (%d %d %d)\n",
       new_plane->index[0], new_plane->index[1], new_plane->index[2]);
#endif

      new_planes = g_slist_prepend(new_planes, new_plane);
      }
    sym_list = g_slist_next(sym_list);
    i++;
    }
  free_slist(sym_equiv);
  }
/* use the new list with the symmetry adjusted planes */
model->planes = g_slist_concat(model->planes, new_planes);
model->num_planes = g_slist_length(model->planes);

#if DEBUG_SURF_SYMMETRY_GENERATE
printf("======\n");
printf("ADDING\n");
printf("======\n");
surface_print_planes(new_planes);
#endif

}

/***********************/
/* free a single shift */
/***********************/
void shift_free(gpointer data)
{
struct shift_pak *shift = data;

g_assert(shift != NULL);

/* FIXME - this may create a memory leak, but it'll be */
/* the users fault */
if (shift->locked)
  {
  printf("ERROR: shift is locked.\n");
  return;
  }
g_free(shift);
}

/*************************************/
/* free the data in a list of shifts */
/*************************************/
void shift_data_free(GSList *shifts)
{
GSList *list;
struct shift_pak *shift;

/* free a list of shift pak structures */
list = shifts;
while (list)
  {
  shift = list->data;
  list = g_slist_next(list);

  shift_free(shift);
  }
}

/***********************/
/* free a single plane */
/***********************/
void plane_free(gpointer data)
{
struct plane_pak *plane = data;

shift_data_free(plane->shifts);
g_slist_free(plane->shifts);

/* FIXME - is this good enough? */
g_slist_free(plane->vertices);
g_free(plane);
}

/*************************************/
/* free the data in a list of planes */
/*************************************/
void plane_data_free(GSList *planes)
{
GSList *list;
struct plane_pak *plane;

/* free a list of plane pak structures */
list = planes;
while (list)
  {
  plane = list->data;
  list = g_slist_next(list);

  plane_free(plane);
  }
}

/* DEBUG */

void print_shell_offset(gint flag, struct model_pak *model)
{
gdouble x[3];
GSList *list;
struct core_pak *core;
struct shel_pak *shell;

printf("-----------------------------------------------\n");
printf("model: %p\n", model);
P3MAT("latmat:", model->latmat);
printf("-----------------------------------------------\n");

for (list=model->cores ; list ; list=g_slist_next(list))
  {
  core = list->data;

  if (core->shell)
    {
    shell = core->shell;

    ARR3SET(x, shell->x);
    ARR3SUB(x, core->x);
    if (flag)
      vecmat(model->latmat, x);

printf("[r = %f] ", VEC3MAG(x));

    P3VEC(" + ", x);
    }
  }
}

/**********************************************/
/* refresh plane energy data for all surfaces */
/**********************************************/
void plane_refresh_all(struct model_pak *model)
{
/* refresh primary planes */
/* refresh symmetry related planes */
}

/****************************************/
/* uses marvin code to create a surface */
/****************************************/
#define DEBUG_GENSURF 0
gint generate_surface(struct model_pak *src, struct model_pak *dest)
{
vector normal;
vector lattice_vector[3];    /* lattice vectors */
vector t_mat[3];             /* transformation matrix */
vector work_lat[3];          /* transformed lattice vectors */
vector rec_work_lat[3];      /* reciprocal transformed lattice vectors */
vector s[3];                 /* space we are trying to fill */
vector rec_s[2];             /* reciprocal of s[0] and s[1] */
vector tempvec;
vector *sv;
vector a, *v_a=NULL, *v_b=NULL;
boolean s2_found = FALSE;
gint c, i, j, h, k, l, flag;
gint ia, ib, ic;
gint amin, amax, bmin, bmax, cmin, cmax;
gint ob, sb;
gint GCD(gint, gint);
gint vector_compare(vector *, vector *);
gdouble inv_denom;
gdouble shift, depth, depth_1, depth_2, depth_min;
gdouble gcd, cd, x;
gdouble z1_max, z1_min, z2_max, z2_min;
gdouble tempfloat;
gdouble tmat[9], norm[3], va[3];
gdouble wlat[9], temp[9], lpm[9];
gdouble sign, vec[3], vec2[3], dn[3];
GSList *list1, *list2, *sv_list1, *sv_list2;
GSList *list, *clist, *slist, *mlist;
struct core_pak *core;
struct shel_pak *shel;
struct mol_pak *mol;

/* checks */
g_assert(src != NULL);
g_assert(dest != NULL);

/* find surface normal from the miller index and lattice vectors*/
V_ZERO(normal);

/* setup */
h = dest->surface.miller[0];
k = dest->surface.miller[1];
l = dest->surface.miller[2];

/* acquire lattice vectors */
V_X(lattice_vector[0]) = src->latmat[0];
V_Y(lattice_vector[0]) = src->latmat[3];
V_Z(lattice_vector[0]) = src->latmat[6];
V_X(lattice_vector[1]) = src->latmat[1];
V_Y(lattice_vector[1]) = src->latmat[4];
V_Z(lattice_vector[1]) = src->latmat[7];
V_X(lattice_vector[2]) = src->latmat[2];
V_Y(lattice_vector[2]) = src->latmat[5];
V_Z(lattice_vector[2]) = src->latmat[8];

V_CROSS(tempvec, lattice_vector[1], lattice_vector[2]);
V_QADD(normal, normal, +h*, tempvec);
V_CROSS(tempvec, lattice_vector[2], lattice_vector[0]);
V_QADD(normal, normal, +k*, tempvec);
V_CROSS(tempvec, lattice_vector[0], lattice_vector[1]);
V_QADD(normal, normal, +l*, tempvec);

sv_list1 = NULL;
 
c = (float) GCD(h, k);
cd = c ? c : 1.0;
V_2_ASSIGN(a, = k/cd * ,lattice_vector[0], - h/cd *, lattice_vector[1]);
if (V_MAGSQ(a) > EPSILON) 
  {
  sv = g_malloc(sizeof(vector));
  V_EQUATE(*sv, a);
  sv_list1 = g_slist_prepend(sv_list1, sv);
  }
  
c = (float) GCD(h, l);
cd = c ? c : 1.0;
V_2_ASSIGN(a, = l/cd * ,lattice_vector[0], - h/cd *, lattice_vector[2]);
if (V_MAGSQ(a) > EPSILON) 
  {
  sv = g_malloc(sizeof(vector));
  V_EQUATE(*sv, a);
  sv_list1 = g_slist_prepend(sv_list1, sv);
  }
  
c = (float) GCD(k, l);
cd = c ? c : 1.0;
V_2_ASSIGN(a, = l/cd * ,lattice_vector[1], - k/cd *, lattice_vector[2]);
if (V_MAGSQ(a) > EPSILON) 
  {
  sv = g_malloc(sizeof(vector));
  V_EQUATE(*sv, a);
  sv_list1 = g_slist_prepend(sv_list1, sv);
  }

sv_list2 = NULL;
list1 = sv_list1;
for (i=0 ; i<g_slist_length(sv_list1)-1 ; i++)
  {
  v_a = (vector *) list1->data;
  list1 = list2 = g_slist_next(list1);
  for (j=i+1 ; j<g_slist_length(sv_list1) ; j++)
    {
    v_b = (vector *) list2->data;
    list2 = g_slist_next(list2);


    V_2_ASSIGN(a, = , *v_a, + , *v_b);
    if (V_MAGSQ(a) > EPSILON)
      {
      sv = g_malloc(sizeof(vector));
      V_EQUATE(*sv, a);
      sv_list2 = g_slist_prepend(sv_list2, sv);
      }
    V_2_ASSIGN(a, = , *v_a, - , *v_b);
    if (V_MAGSQ(a) > EPSILON)
      {
      sv = g_malloc(sizeof(vector));
      V_EQUATE(*sv, a);
      sv_list2 = g_slist_prepend(sv_list2, sv);
      }
    }
  }
sv_list1 = g_slist_concat(sv_list1, sv_list2);
sv_list1 = g_slist_sort(sv_list1, (gpointer) vector_compare);

/* set v_a to first (shortest now sorted) vector */
list1 = sv_list1;
v_a = (vector *) list1->data;
list1 = g_slist_next(list1);
/* loop over remaining vectors - find shortest w/ orthogonal component */
while (list1)
  {
  v_b = (vector *) list1->data;
  
  V_CROSS(a, *v_a, *v_b);
  x = V_MAGSQ(a);
  if (x > EPSILON) 
    {
    s2_found = TRUE;
    break;
    }
  list1 = g_slist_next(list1);
  }

/* checks */
if (!s2_found) 
  {
  gui_text_show(ERROR, "Failed to find surface vectors.\n");
  return(1);
  }
g_assert(v_a != NULL);
g_assert(v_b != NULL);

/* calculate transformation matrix */
V_SCALER(t_mat[2], (1.0/V_MAG(normal)), normal);
V_SCALER(t_mat[0], (1.0/V_MAG(*v_a)), *v_a);
V_CROSS(t_mat[1], t_mat[2], t_mat[0]);

ARR3SET(norm, normal.element);
ARR3SET(va, v_a->element);
normalize(norm, 3);
normalize(va, 3);
ARR3SET(&tmat[6], norm);
ARR3SET(&tmat[0], va);
crossprod(&tmat[3], norm, va);

/* calculate transformed lattice vectors */
for (j = 0; j < 3; j++)
  for (i = 0; i < 3; i++)
    V_E(work_lat[j], i) = V_DOT(t_mat[i], lattice_vector[j]);

/* calculate reciprocal transformed lattice vectors */
V_CROSS(tempvec, work_lat[1], work_lat[2]);
inv_denom = 1.0 / V_DOT(tempvec, work_lat[0]);
V_CROSS(tempvec, work_lat[1], work_lat[2]);
V_SCALER(rec_work_lat[0], inv_denom, tempvec);
V_CROSS(tempvec, work_lat[2], work_lat[0]);
V_SCALER(rec_work_lat[1], inv_denom, tempvec);
V_CROSS(tempvec, work_lat[0], work_lat[1]);
V_SCALER(rec_work_lat[2], inv_denom, tempvec);

/* calculate transformed surface vectors */
for (i = 0; i < 3; i++)
  V_E(s[0], i) = V_DOT(t_mat[i], *v_a);
for (i = 0; i < 3; i++)
  V_E(s[1], i) = V_DOT(t_mat[i], *v_b);

/* return transformed surface vectors data in dest->latmat */
  /* NB: latmat[0..8] is a 3x3 matrix so, */
  /*
    | a b 0 |
    | c d 0 |
    | 0 0 0 |

    goes into latmat rows first ie a,b,0,c,d,0,0,0,0
  */
  
/* set s[2] = to depths */
/* depth = 1.0e10; */

/* surface specification */
gcd = GCD(GCD(h, k), GCD(k, l));
VEC3SET(vec, h, k, l);
vecmat(src->rlatmat, vec);
 
/* calculate the Dhkl */
/* NB: we want the correct dspacing wrt hkl, ie DON'T remove the GCD */
dest->surface.dspacing = 1.0/VEC3MAG(vec);

/* actual cut depth */
/* NB: done wrt the FULL depth (ie remove the gcd) */
depth = gcd/VEC3MAG(vec);
dest->surface.depth = depth;

/* round off the shift */
shift = decimal_round(dest->surface.shift, 4);

#if DEBUG_GENSURF
printf("\n----------------------------\n");
printf("    hkl: %d %d %d\n", h, k, l);
printf("    gcd: %f \n", gcd);
printf("  shift: %.20f\n", shift);
printf("regions: %d %d\n", (gint) dest->surface.region[0], (gint) dest->surface.region[1]);
printf("   Dhkl: %f\n", dest->surface.dspacing);
printf("  depth: %f\n", depth);
printf("----------------------------\n");
#endif

/* NB: depth_1 is region[0], but depth_2 is region[0]+region[1] */
depth_1 = dest->surface.region[0] * depth;
depth_2 = depth_1 + dest->surface.region[1] * depth;

V_ZERO(s[2]);
V_Z(s[2]) = -(depth_2);

/* transfer surface vectors */
VEC3SET(&dest->latmat[0], V_X(s[0]), V_X(s[1]), 0.0);
VEC3SET(&dest->latmat[3], V_Y(s[0]), V_Y(s[1]), 0.0);
VEC3SET(&dest->latmat[6], 0.0, 0.0, 1.0);

/* calculate reciprocal of two surface vectors */
inv_denom = 1.0 / (V_X(s[0])*V_Y(s[1]) - V_Y(s[0])*V_X(s[1]));
V_X(rec_s[0]) =  V_Y(s[1])*inv_denom;
V_Y(rec_s[0]) = -V_X(s[1])*inv_denom;
V_Z(rec_s[0]) =  0.0;
V_X(rec_s[1]) = -V_Y(s[0])*inv_denom;
V_Y(rec_s[1]) =  V_X(s[0])*inv_denom;
V_Z(rec_s[1]) =  0.0;

z2_min = z1_min = 0.00;
z2_max = V_Z(s[2]);
z1_max = depth_1 * (z2_max < 0 ? -1.0: +1.0);

if (z2_max < z2_min) 
  {
  tempfloat = z2_max;
  z2_max = z2_min;
  z2_min = tempfloat;
  }
if (z1_max < z1_min) 
  {
  tempfloat = z1_max;
  z1_max = z1_min;
  z1_min = tempfloat;
  }

/* display name */
g_free(dest->basename);
dest->basename = g_strdup_printf("%s_%-1d%-1d%-1d_%6.4f",
                 g_strstrip(src->basename), h, k, l, shift);

/* store the work lattice */
for (i=0 ; i<3 ; i++)
  {
  wlat[3*i+0] = V_E(work_lat[0], i);
  wlat[3*i+1] = V_E(work_lat[1], i);
  wlat[3*i+2] = V_E(work_lat[2], i);
  }

#if DEBUG_GENSURF
P3MAT(" src lat: ", src->latmat);
#endif

/* construct the depth vector */
/* compute the surface normal */
VEC3SET(vec, dest->latmat[0], dest->latmat[3], dest->latmat[6]);
VEC3SET(vec2, dest->latmat[1], dest->latmat[4], dest->latmat[7]);
crossprod(dn, vec, vec2);

/* search for a vector with the smallest */
/* orthogonal component to the surface vectors */
flag=0;
depth_min=0;
for (i=0 ; i<3 ; i++)
  {
/* ignore zero indices, which yield 0 orthogonal component */
  if (dest->surface.miller[i])
    {
/* dotproduct of vector with normal */
    ARR3SET(vec, dn);
    vec[0] *= wlat[i];
    vec[1] *= wlat[i+3];
    vec[2] *= wlat[i+6];
    depth = fabs(vec[0] + vec[1] + vec[2]);

#if DEBUG_GENSURF
printf("[i=%d, depth=%f]", i, depth);
#endif

   if (flag)
     {
     if (depth > depth_min)
       continue;
     }
   else
     flag++;

    depth_min = depth;

/* ensure the depth vector is pointing in the +ve z direction */
    if (wlat[i+6] < 0.0)
      sign = -1.0;
    else
      sign = 1.0;

    dest->surface.depth_vec[0] = sign*wlat[i];
    dest->surface.depth_vec[1] = sign*wlat[i+3];
    dest->surface.depth_vec[2] = sign*wlat[i+6];
    dest->latmat[2] = sign*wlat[i];
    dest->latmat[5] = sign*wlat[i+3];
    dest->latmat[8] = sign*wlat[i+6];
    }
  }
#if DEBUG_GENSURF
printf(" : Minimum = %f\n", depth_min);
P3VEC("depth vec:", dest->surface.depth_vec);
#endif

g_assert(flag != 0);

/* generate lattice matrix inverse */
memcpy(dest->ilatmat, dest->latmat, 9*sizeof(gdouble));
matrix_invert(dest->ilatmat);

/* locate the position of the new cell's repeat vectors */
/* in terms of the lattice space of the source model */
/* this gives us the lattice point matrix (lpm) which */
/* is used to determine how many source unit cells */
/* will be required to fill out the new surface cell */
memcpy(lpm, dest->latmat, 9*sizeof(gdouble));
memcpy(temp, tmat, 9*sizeof(gdouble));
matrix_invert(temp);
matmat(temp, lpm);
matmat(src->ilatmat, lpm);

#if DEBUG_GENSURF
P3MAT("tmat: ", tmat);
P3MAT("dest latmat: ", dest->latmat);
P3MAT("dest ilatmat: ", dest->ilatmat);
P3MAT("lpm: ", lpm);
#endif

/* setup repeats required to fill the new cell */
amin=bmin=cmin=0;
amax=bmax=cmax=0;

/* required number of a cells */
for (i=0 ; i<3 ; i++)
  {
  ia = rint(lpm[i]);
  if (ia < amin)
    amin = ia;
  if (ia > amax)
    amax = ia;
  }
/* required number of b cells */
for (i=3 ; i<6 ; i++)
  {
  ib = rint(lpm[i]);
  if (ib < bmin)
    bmin = ib;
  if (ib > bmax)
    bmax = ib;
  }
/* required number of c cells */
for (i=6 ; i<9 ; i++)
  {
  ic = rint(lpm[i]);

  if (ic < cmin)
    cmin = ic;
  if (ic > cmax)
    cmax = ic;
  }

/* one cell buffer */
amax+=2;
bmax+=2;
cmax+=2;
amin--;
bmin--;
cmin--;

#if DEBUG_GENSURF
printf("a: %d - %d\n", amin, amax);
printf("b: %d - %d\n", bmin, bmax);
printf("c: %d - %d\n", cmin, cmax);
#endif

/* compute transformation pipeline */
memcpy(temp, src->latmat, 9*sizeof(gdouble));
matmat(tmat, temp);
matmat(dest->ilatmat, temp);

/* create a single transformed unit cell */
/* full loop required to cover one cell in the transformed coordinates */
for (ic=cmin ; ic<cmax ; ic++)
  {
  for (ib=bmin ; ib<bmax ; ib++)
    {
    for (ia=amin ; ia<amax ; ia++)
      {
/* current source cell translation */
      VEC3SET(vec, ia, ib, ic);

/* loop over all molecules */
      for (mlist=src->moles ; mlist ; mlist=g_slist_next(mlist))
        {
        mol = mlist->data;

/* transform the centroid */
        ARR3SET(va, mol->centroid);
        ARR3ADD(va, vec);
        vecmat(temp, va);
        va[2] += shift;

/* ignore molecules outside the transformed cell */
/* NB: the range must be exactly the smallest numerical value below 1.0 [G_MINDOUBLE, 1.0] */
/* NB: the value of EPS adds a bias to promote molecules to the top of the surface */
#define EPS 0.001
        if (va[0] < G_MINDOUBLE+EPS || va[0] > 1.0+EPS)
          continue;
        if (va[1] < G_MINDOUBLE+EPS || va[1] > 1.0+EPS)
          continue;
        if (va[2] < G_MINDOUBLE+EPS || va[2] > 1.0+EPS)
          continue;

/* loop over all atoms in molecule */
        for (clist=mol->cores ; clist ; clist=g_slist_next(clist))
          {
/* dup_core() will duplicate an attached shell as well */
          core = dup_core(clist->data);
          dest->cores = g_slist_prepend(dest->cores, core);

/* transform */
          ARR3ADD(core->x, vec);
          vecmat(temp, core->x);
          core->x[2] += shift;

/* init flags */
          core->primary = TRUE;
          core->primary_core = NULL;
          core->orig = TRUE;

/* shell */
          if (core->shell)
            {
            shel = core->shell;
            dest->shels = g_slist_prepend(dest->shels, shel);

            ARR3ADD(shel->x, vec);
            vecmat(temp, shel->x);
            shel->x[2] += shift;

/* init flags */
            shel->primary = TRUE;
            shel->primary_shell = NULL;
            shel->orig = TRUE;
            }
          }
        }
      }
    }
  }

dest->periodic = 3;
dest->fractional = FALSE;
dest->axes_type = CARTESIAN;
dest->construct_pbc = TRUE;

/* NB: can't leave the (expensive) delete_duplicate_cores() to the */
/* final model_prep() as that treats the model as 2D periodic */
/* and won't clip the top & bottom of the surface*/
matrix_lattice_init(dest);
zone_init(dest);
delete_duplicate_cores(dest);
shell_make_links(dest);

/* terminate here for debugging */
#define DEBUG_SURFACE_CELL 0
#if DEBUG_SURFACE_CELL
dest->sginfo.spacenum = 1;
dest->fractional = TRUE;
dest->axes_type = CARTESIAN;
dest->construct_pbc = TRUE;
dest->gulp.method = CONV;
model_prep(dest);
return(0);
#endif

/* build surface core and shell lists from transformed cell */
clist = slist = NULL;

/* region 1 cores and shells */
amax = dest->surface.region[0] + 1;
for (i=1 ; i<amax ; i++)
  {
  for (list=dest->cores ; list ; list=g_slist_next(list))
    {
    core = dup_core(list->data);
    clist = g_slist_prepend(clist, core);

/*
printf("[%s] c ", core->label);
P3VEC(" : ", core->x);
*/

    core->x[2] -= (gdouble) i;
    vecmat(dest->latmat, core->x);

    core->region = REGION1A;

    if (core->shell)
      {
      shel = core->shell;
      slist = g_slist_prepend(slist, shel);

/* CURRENT - alternate shell coordinate init */
/* more tolerant of pbc offset core-shell links */
      shel->x[2] -= (gdouble) i;
      vecmat(dest->latmat, shel->x);

/*
printf("[%s] s ", shel->label);
P3VEC(" : ", shel->x);
*/

      shel->region = REGION1A;
      }
    }
  }
/* region 2 cores and shells */
bmax = amax + dest->surface.region[1];
for (i=amax ; i<bmax ; i++)
  {
  for (list=dest->cores ; list ; list=g_slist_next(list))
    {
    core = dup_core(list->data);
    clist = g_slist_prepend(clist, core);
    core->x[2] -= (gdouble) i;
    vecmat(dest->latmat, core->x);
    core->region = REGION2A;
    if (core->shell)
      {
      shel = core->shell;
      slist = g_slist_prepend(slist, shel);
      shel->x[2] -= (gdouble) i;
      vecmat(dest->latmat, shel->x);
      shel->region = REGION2A;
      }
    }
  }

/* replace old structure (transformed cell) with the new surface */
free_core_list(dest);
dest->cores = g_slist_reverse(clist);
dest->shels = g_slist_reverse(slist);

/* adjust depth translation vector to account for region sizes */
dest->latmat[2] *= dest->surface.region[0]+dest->surface.region[1];
dest->latmat[5] *= dest->surface.region[0]+dest->surface.region[1];
dest->latmat[8] *= dest->surface.region[0]+dest->surface.region[1];

/* surface cell init */
dest->periodic = 2;
dest->sginfo.spacenum = 1;
dest->fractional = FALSE;
dest->axes_type = CARTESIAN;
dest->construct_pbc = TRUE;
dest->surface.keep_atom_order = src->surface.keep_atom_order;
dest->gulp.method = CONV;

/* surface cell type */
if (src->surface.true_cell)
  dest->surface.true_cell = TRUE;
else
  {
  dest->surface.true_cell = FALSE;

/* want to force c to be // to z */
/* NB: vectors are in columns */
  dest->latmat[2] = 0.0;
  dest->latmat[5] = 0.0;
  }
free_slist(sv_list1);

/* CURRENT - does this screw up the true_cell crap? */
/*
for (list=dest->cores ; list ; list=g_slist_next(list))
  {
  core = list->data;

  ARR3SET(va, core->x);
  vecmat(dest->latmat, va);

  core->x[2] = va[2];
  }
dest->latmat[2] = 0.0;
dest->latmat[5] = 0.0;
dest->latmat[8] = 1.0;
*/

#if DEBUG_GENSURF
printf("gensurf cores: %d\n", g_slist_length(dest->cores));
printf("gensurf shels: %d\n", g_slist_length(dest->shels));
#endif

/* build mode follows source structure */
/*
dest->build_molecules = src->build_molecules;
*/

/* last init */
model_prep(dest);
calc_emp(dest);

/* FIXME - bugged for unbroken bond chain materials */
/* mark the growth slice */
depth_min = dest->surface.region[0]+dest->surface.region[1];
depth_min *= gcd;
depth_min = -1.0/depth_min;
for (list1=dest->moles ; list1 ; list1=g_slist_next(list1))
  {
  mol = list1->data;
  if (mol->centroid[2] > depth_min)
    {
    for (list2=mol->cores ; list2 ; list2=g_slist_next(list2))
      {
      core = list2->data;
      core->growth = TRUE;
      }
    }
  }

/* NEW - bonding analysis */
ob = src->surface.bonds_full;
sb = g_slist_length(dest->bonds);
c = dest->surface.region[0]+dest->surface.region[1];
dest->surface.bonds_cut = ob*c - sb;

/* printout of bonding analysis */
/*
printf("[%2d %2d %2d] (%f)", h, k, l, shift);
printf(" [src = %d][surf = %d][r1+r2 = %d] : cleaved = %d : ", ob, sb, c, dest->surface.bonds_cut);
printf(" : area = %f", dest->area);
printf(" : cleaved = %d", dest->surface.bonds_cut);
printf("\n");
*/

return(0);
}

/*************************************/
/* molecule centroid z value sorting */
/*************************************/
gint surf_molecules_zsort(gpointer p1, gpointer p2)
{
struct mol_pak *m1 = p1, *m2 = p2;

if (m1->centroid[2] > m2->centroid[2])
  return(-1);

return(1);
}

/*******************************************************************/
/* peturb a planar surface in order to test alternate terminations */
/*******************************************************************/
void surf_shift_explore(struct model_pak *src, struct surface_pak *surface)
{
gint i, j, n;
GSList *list;
struct plane_pak *plane;
struct shift_pak *shift;
struct core_pak *core;
struct mol_pak *mol1, *mol2;
struct model_pak *model;

g_assert(src != NULL);
g_assert(src->periodic == 3);

/* setup plane and shift */
plane = plane_new(surface->miller, src);
shift = shift_new(surface->shift);
shift->region[0] = surface->region[0];
shift->region[1] = surface->region[1];

printf("Perturbing [%d %d %d] : %f : [%d][%d]\n",
        plane->index[0], plane->index[1], plane->index[2],
        shift->shift, shift->region[0], shift->region[1]);

/* CURRENT - create a new model for each alternate (zero dipole) surface found */

/* generate surface */
model = make_surface(src, plane, shift);  
coords_init(INIT_COORDS, model);

/* sort molecule centroids */
model->moles = g_slist_sort(model->moles, (gpointer) surf_molecules_zsort);

printf("initial dipole: %f\n", model->gulp.sdipole);

i = 0;

/* top */
mol1 = g_slist_nth_data(model->moles, i);
n = g_slist_length(model->moles);

mol2 = g_slist_nth_data(model->moles, n-1);

/* nth bottommost */
/*
bottom = g_slist_copy(model->moles);
bottom = g_slist_reverse(bottom);
*/

/* for each topmost item, exchange with a bottomost item and recalculate dipole */

/* move all atoms in selection */
for (list=mol1->cores ; list ; list=g_slist_next(list))
  {
  core = list->data;
  region_move_atom(core, DOWN, model); 
  }

mol2 = g_slist_nth_data(model->moles, n-1);

for (j=0 ; j<3 ; j++)
  {

mol2 = g_slist_nth_data(model->moles, n-j-1);

/* test */
for (list=mol2->cores ; list ; list=g_slist_next(list))
  {
  core = list->data;
  region_move_atom(core, UP, model); 

/*
P3VEC("c1: ", core->x);
*/
  }

/* update */
/* FIXME - something wierd going on here - dipole recalc doesnt seem */
/* to work unless connect_molecules() is called - why??? */
coords_compute(model);
/*
connect_bonds(model);
*/
connect_molecules(model);

/* TODO - don't recalc from scratch - can update based on moves */
calc_emp(model);

printf("pert [%d,%d] new dipole: %f\n", i, j, model->gulp.sdipole);

/* TODO - if zero -> leave this model (after init) and create a new */
/* one for exploring further perturbations */

/* restore */
for (list=mol2->cores ; list ; list=g_slist_next(list))
  {
  core = list->data;
  region_move_atom(core, DOWN, model); 

/*
P3VEC("c2: ", core->x);
*/
  }

coords_compute(model);

  }

gui_refresh(GUI_CANVAS);
}

/******************************************************************************
 * GCD
 *      Greatest common denominator
 ******************************************************************************/
gint GCD(gint p, gint q)
{
#if defined(__MWERKS__)  /* fix for compiler bug! */
  gint  i;  
  
  if (q == 0) {
    i = abs(p);
    return(i);
  }
#else
  if (q == 0)
    return(abs(p));
#endif
  else
    return(GCD(q, p%q));
}

/******************************************************************************
 * vector_compare
 *      compare two vectors returning their difference as an integer.  This
 *      routine is to be used in conjuction with SORT().
 ******************************************************************************/
gint vector_compare(vector *a, vector *b)
{
int i;
double  diff;
  
diff = V_MAGSQ(*a) - V_MAGSQ(*b);
if (diff < -EPSILON)
  return(-1);
else if (diff > EPSILON)
  return(1);
else
  {
/* magnitude of a & b is the same, so sort based on element magnitudes */ 
  for (i=0 ; i<3 ; i++)
    {
    diff = V_E(*a,i) - V_E(*b,i);
    if (diff < -EPSILON)
      return(1);
    else if (diff > EPSILON)
      return(-1);
    }
  }
/* a & b are identical */
return(0);
}



syntax highlighted by Code2HTML, v. 0.9.1