/* KIM compliant program to compute the energy of and forces on an      */
/* isolated cluster of Ar atoms                                         */
/*                                                                      */
/* Authors: Valeriu Smirichinski, Ryan S. Elliott, Ellad B. Tadmor      */
/*                                                                      */
/* Copyright 2011 Ellad B. Tadmor, Ryan S. Elliott, and James P. Sethna */
/* All rights reserved.                                                 */
/*                                                                      */

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include "KIMserviceC.h"

#define FCCSPACING    5.260
#define NCELLSPERSIDE 2
#define DIM           3
#define ATYPES        1
#define NCLUSTERATOMS (4*(NCELLSPERSIDE*NCELLSPERSIDE*NCELLSPERSIDE) + 6*(NCELLSPERSIDE*NCELLSPERSIDE) + 3*(NCELLSPERSIDE) + 1)

/* Define prototypes */
static void report_error(int line, char* str, int status);
static void create_FCC_configuration(double FCCspacing, int nCellsPerSide, int periodic,
                                     double *coords, int *MiddleAtomId);


/* Main program */
int main(int argc, char* argv[])
{
   /* Local variable declarations */
   int i;


   /* KIM variable declarations */
   char testname[] = "test_Ar_free_cluster_CLUSTER_C";
   char modelname[80];
   void* pkim;
   int status;
   int atypecode;
   
   /* model inputs */
   long long* numberOfAtoms;
   int* numberAtomTypes;
   int* atomTypes;
   double* coords;
   /* model outputs */
   double* cutoff;
   double* energy;
   double* forces;
   int middleDum;

   /* Get KIM Model name to use */
   printf("Please enter a valid KIM model name: \n");
   scanf("%s",modelname);
   
   /* Initialize the KIM Model */
   status = KIM_API_init(&pkim, testname, modelname);
   if (0 > status)
   {
      report_error(__LINE__, "KIM_API_init", status);
      exit(1);
   }

   /* Allocate memory via the KIM system */
   KIM_API_allocate(pkim, NCLUSTERATOMS, ATYPES, &status);
   if (0 > status)
   {
      report_error(__LINE__, "KIM_API_allocate", status);
      exit(1);
   }

   /* call Model's init routine */
   status = KIM_API_model_init(pkim);
   if (0 > status)
   {
      report_error(__LINE__, "KIM_API_model_init", status);
      exit(1);
   }

   /* Unpack data from KIM object */
   numberOfAtoms = KIM_API_get_data(pkim, "numberOfAtoms", &status);
   if (0 > status)
   {
      report_error(__LINE__, "KIM_API_get_data", status);
      exit(1);
   }
   
   numberAtomTypes = KIM_API_get_data(pkim, "numberAtomTypes", &status);
   if (0 > status)
   {
      report_error(__LINE__, "KIM_API_get_data", status);
      exit(1);
   }
   
   atomTypes = KIM_API_get_data(pkim, "atomTypes", &status);
   if (0 > status)
   {
      report_error(__LINE__, "KIM_API_get_data", status);
      exit(1);
   }
   
   coords = KIM_API_get_data(pkim, "coordinates", &status);
   if (0 > status)
   {
      report_error(__LINE__, "KIM_API_get_data", status);
      exit(1);
   }
   
   cutoff = KIM_API_get_data(pkim, "cutoff", &status);
   if (0 > status)
   {
      report_error(__LINE__, "KIM_API_get_data", status);
      exit(1);
   }
   
   energy = KIM_API_get_data(pkim, "energy", &status);
   if (0 > status)
   {
      report_error(__LINE__, "KIM_API_get_data", status);
      exit(1);
   }
   
   forces = KIM_API_get_data(pkim, "forces", &status);
   if (0 > status)
   {
      report_error(__LINE__, "KIM_API_get_data", status);
      exit(1);
   }
   
   /* Set values */
   *numberOfAtoms   = NCLUSTERATOMS;
   *numberAtomTypes = ATYPES;
   atypecode = KIM_API_get_aTypeCode(pkim, "Ar", &status);
   if (0 > status)
   {
      report_error(__LINE__, "KIM_API_get_aTypeCode", status);
      exit(1);
   }
   for (i = 0; i < *numberOfAtoms; ++i)
   {
      atomTypes[i] = atypecode;
   }

   /* set up the cluster atom positions */
   create_FCC_configuration(FCCSPACING, NCELLSPERSIDE, 0, coords, &middleDum);

   /* Call model compute */
   KIM_API_model_compute(pkim, &status);
   if (0 > status)
   {
      report_error(__LINE__, "KIM_API_model_compute", status);
      exit(1);
   }

   /* print results to screen */
   printf("--------------------------------------------------------------------------------\n");
   printf("This is Test          : %s\n",testname);
   printf("Results for KIM Model : %s\n",modelname);
   printf("Forces:\n");
   printf("Atom     X                        Y                        Z\n");
   for (i = 0; i < *numberOfAtoms; ++i)
   {
      printf("%2i   %25.15e%25.15e%25.15e\n", i,
             forces[i*DIM + 0], forces[i*DIM + 1], forces[i*DIM + 2]);
   }
   printf("\n");
   printf("Energy = %20.15e\n", *energy);

   
   /* don't forget to destroy and deallocate */
   KIM_API_model_destroy(pkim, &status);
   if (0 > status)
   {
      report_error(__LINE__, "KIM_API_model_destory", status);
      exit(1);
   }
   KIM_API_free(&pkim, &status);
   if (0 > status)
   {
      report_error(__LINE__, "KIM_API_free", status);
      exit(1);
   }
   
   /* everything is great */
   return 0;
}

/******************************************************************************
 *
 * create_FCC_configuration function
 *
 *  creates a cubic configuration of FCC atoms with lattice spacing
 *  `FCCspacing' and `nCellsPerSide' cells along each direction.
 *
 *  With periodic==0. this will result in a total number of atoms equal to
 *  4*(nCellsPerSide)**3 + 6*(nCellsPerSide)**2 + 3*(nCellsPerSide) + 1
 *
 *  With periodic==1 this will result in a total number of atoms equal to
 *  4*(nCellsPerSide)**3
 *
 *  Returns the Id of the atom situated in the middle of the configuration
 *  (this atom will have the most neighbors.)
 *
 ******************************************************************************/
static void create_FCC_configuration(double FCCspacing, int nCellsPerSide, int periodic,
                                     double *coords, int* MiddleAtomId)
{
   /* local variables */
   double FCCshifts[4][3];
   double latVec[3];
   int a;
   int i;
   int j;
   int k;
   int m;
   int n;

   /* create a cubic FCC cluster */
   FCCshifts[0][0] = 0.0;            FCCshifts[0][1] = 0.0;            FCCshifts[0][2] = 0.0;
   FCCshifts[1][0] = 0.5*FCCspacing; FCCshifts[1][1] = 0.5*FCCspacing; FCCshifts[1][2] = 0.0;
   FCCshifts[2][0] = 0.5*FCCspacing; FCCshifts[2][1] = 0.0;            FCCshifts[2][2] = 0.5*FCCspacing;
   FCCshifts[3][0] = 0.0;            FCCshifts[3][1] = 0.5*FCCspacing; FCCshifts[3][2] = 0.5*FCCspacing;

   a = 0;
   for (i = 0; i < nCellsPerSide; ++i)
   {
      latVec[0] = ((double) i)*FCCspacing;
      for (j = 0; j < nCellsPerSide; ++j)
      {
         latVec[1] = ((double) j)*FCCspacing;
         for (k = 0; k < nCellsPerSide; ++k)
         {
            latVec[2] = ((double) k)*FCCspacing;
            for (m = 0; m < 4; ++m)
            {
               for (n = 0; n < DIM; ++n)
               {
                  coords[a*DIM + n] = latVec[n] + FCCshifts[m][n];
               }
               if ((nCellsPerSide/2 == i) && (nCellsPerSide/2 == j) &&
                   (nCellsPerSide/2 == k) && (1 == m))
               {
                  *MiddleAtomId = a;
               }
               a++;
            }
         }
         if (!periodic)
         {
            /* add in the remaining three faces */
            /* pos-x face */
            latVec[0] = NCELLSPERSIDE*FCCspacing;
            latVec[1] = ((double) i)*FCCspacing;
            latVec[2] = ((double) j)*FCCspacing;
            for (n = 0; n < DIM; ++n)
            {
               coords[a*DIM + n] = latVec[n];
            }
            a++;
            for (n = 0; n < DIM; ++n)
            {
               coords[a*DIM + n] = latVec[n] + FCCshifts[3][n];
            }
            a++;
            /* pos-y face */
            latVec[0] = ((double) i)*FCCspacing;
            latVec[1] = NCELLSPERSIDE*FCCspacing;
            latVec[2] = ((double) j)*FCCspacing;
            for (n = 0; n < DIM; ++n)
            {
               coords[a*DIM + n] = latVec[n];
            }
            a++;
            for (n = 0; n < DIM; ++n)
            {
               coords[a*DIM + n] = latVec[n] + FCCshifts[2][n];
            }
            a++;
            /* pos-z face */
            latVec[0] = ((double) i)*FCCspacing;
            latVec[1] = ((double) j)*FCCspacing;
            latVec[2] = NCELLSPERSIDE*FCCspacing;
            for (n = 0; n < DIM; ++n)
            {
               coords[a*DIM + n] = latVec[n];
            }
            a++;
            for (n = 0; n < DIM; ++n)
            {
               coords[a*DIM + n] = latVec[n] + FCCshifts[1][n];
            }
            a++;
         }
      }
      if (!periodic)
      {
         /* add in the remaining three edges */
         latVec[0] = ((double) i)*FCCspacing;
         latVec[1] = NCELLSPERSIDE*FCCspacing;
         latVec[2] = NCELLSPERSIDE*FCCspacing;
         for (n = 0; n < DIM; ++n)
         {
            coords[a*DIM + n] = latVec[n];
         }
         a++;
         latVec[0] = NCELLSPERSIDE*FCCspacing;
         latVec[1] = ((double) i)*FCCspacing;
         latVec[2] = NCELLSPERSIDE*FCCspacing;
         for (n = 0; n < DIM; ++n)
         {
            coords[a*DIM + n] = latVec[n];
         }
         a++;
         latVec[0] = NCELLSPERSIDE*FCCspacing;
         latVec[1] = NCELLSPERSIDE*FCCspacing;
         latVec[2] = ((double) i)*FCCspacing;
         for (n = 0; n < DIM; ++n)
         {
            coords[a*DIM + n] = latVec[n];
         }
         a++;
      }
   }
   if (!periodic)
   {
      /* add in the remaining corner */
      for (n = 0; n < DIM; ++n)
      {
         coords[a*DIM + n] = NCELLSPERSIDE*FCCspacing;
      }
      a++;
   }

   return;
}

/********************************************************************************
 *
 * report_error function
 *
 ********************************************************************************/
static void report_error(int line, char* str, int status)
{
   printf("Error at line %i of %s: %s with status = %i.\n",line,__FILE__,str,status);
   exit(-1);
}
