/*----------------------------------------------------------------------
  SerialReax - Reax Force Field Simulator

  Copyright (2010) Purdue University
  Hasan Metin Aktulga, haktulga@cs.purdue.edu
  Joseph Fogarty, jcfogart@mail.usf.edu
  Sagar Pandit, pandit@usf.edu
  Ananth Y Grama, ayg@cs.purdue.edu

  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:
  <http://www.gnu.org/licenses/>.
  ----------------------------------------------------------------------*/

#include "mytypes.h"

#include "analyze.h"
#include "control.h"
#include "ffield.h"
#include "forces.h"
#include "init_md.h"
#include "neighbors.h"
#include "geo_tools.h"
#include "print_utils.h"
#include "reset_utils.h"
#include "restart.h"
#include "system_props.h"
#include "tool_box.h"
#include "vector.h"


static static_storage workspace;
static reax_list *lists;
static output_controls out_control;


static void Post_Evolve( reax_system * const system, control_params * const control,
        simulation_data * const data, static_storage * const workspace,
        reax_list ** const lists, output_controls * const out_control )
{
    int i;
    rvec diff, cross;

    /* if velocity dependent force then
       {
       Generate_Neighbor_Lists( &system, &control, &lists );
       Compute_Charges(system, control, workspace, lists[FAR_NBRS]);
       Introduce compute_force here if we are using velocity dependent forces
       Compute_Forces(system,control,data,workspace,lists);
       } */

    /* compute kinetic energy of the system */
    Compute_Kinetic_Energy( system, data );

    /* remove rotational and translational velocity of the center of mass */
    if ( control->ensemble != NVE &&
            control->remove_CoM_vel &&
            data->step && data->step % control->remove_CoM_vel == 0 )
    {

        /* compute velocity of the center of mass */
        Compute_Center_of_Mass( system, data, out_control->prs );

        for ( i = 0; i < system->N; i++ )
        {
            // remove translational
            rvec_ScaledAdd( system->atoms[i].v, -1., data->vcm );

            // remove rotational
            rvec_ScaledSum( diff, 1., system->atoms[i].x, -1., data->xcm );
            rvec_Cross( cross, data->avcm, diff );
            rvec_ScaledAdd( system->atoms[i].v, -1., cross );
        }
    }
}


void static Read_System( char * const geo_file,
        char * const ffield_file,
        char * const control_file,
        reax_system * const system,
        control_params * const control,
        simulation_data * const data,
        static_storage * const workspace,
        output_controls * const out_control )
{
    FILE *ffield, *ctrl;

    if ( (ffield = fopen( ffield_file, "r" )) == NULL )
    {
        fprintf( stderr, "[ERROR] Error opening the ffield file!\n" );
        fprintf( stderr, "    [INFO] (%s)\n", ffield_file );
        exit( FILE_NOT_FOUND );
    }
    if ( (ctrl = fopen( control_file, "r" )) == NULL )
    {
        fprintf( stderr, "[ERROR] Error opening the ffield file!\n" );
        fprintf( stderr, "    [INFO] (%s)\n", control_file );
        exit( FILE_NOT_FOUND );
    }

    /* ffield file */
    Read_Force_Field( ffield, &(system->reaxprm) );

    /* control file */
    Read_Control_File( ctrl, system, control, out_control );

    /* geo file */
    if ( control->geo_format == CUSTOM )
    {
        Read_Geo( geo_file, system, control, data, workspace );
    }
    else if ( control->geo_format == PDB )
    {
        Read_PDB( geo_file, system, control, data, workspace );
    }
    else if ( control->geo_format == BGF )
    {
        Read_BGF( geo_file, system, control, data, workspace );
    }
    else if ( control->geo_format == ASCII_RESTART )
    {
        Read_ASCII_Restart( geo_file, system, control, data, workspace );
        control->restart = 1;
    }
    else if ( control->geo_format == BINARY_RESTART )
    {
        Read_Binary_Restart( geo_file, system, control, data, workspace );
        control->restart = 1;
    }
    else
    {
        fprintf( stderr, "[ERROR] unknown geo file format. terminating!\n" );
        exit( INVALID_GEO );
    }

    fclose( ffield );
    fclose( ctrl );

#if defined(DEBUG_FOCUS)
    fprintf( stderr, "input files have been read...\n" );
    Print_Box( &(system->box), stderr );
#endif
}


int Setup( char ** args, reax_system * const system, control_params * const control,
        simulation_data * const data )
{
    lists = (reax_list*) smalloc( sizeof(reax_list) * LIST_N,
           "Setup::lists" );

    Read_System( args[0], args[1], args[2], system, control,
            data, &workspace, &out_control );

    return SUCCESS;
}


int Run( reax_system * const system, control_params * const control, simulation_data * const data,
      const int output_enabled )
{
    int steps;
    evolve_function Evolve;

    Initialize( system, control, data, &workspace, &lists,
            &out_control, &Evolve, output_enabled );

    /* compute f_0 */
    //if( control.restart == 0 ) {
    Reset( system, control, data, &workspace, &lists );
    Generate_Neighbor_Lists( system, control, data, &workspace, 
            &lists, &out_control );

    //fprintf( stderr, "total: %.2f secs\n", data.timing.nbrs);
    Compute_Forces( system, control, data, &workspace, &lists, &out_control );
    Compute_Kinetic_Energy( system, data );
    if ( output_enabled == TRUE )
    {
        Output_Results( system, control, data, &workspace, &lists, &out_control );
    }
    ++data->step;
    //}
    //
    
    for ( ; data->step <= control->nsteps; data->step++ )
    {
        if ( control->T_mode )
        {
            Temperature_Control( control, data, &out_control );
        }

        Evolve( system, control, data, &workspace, &lists, &out_control );
        Post_Evolve( system, control, data, &workspace, &lists, &out_control );

        if ( output_enabled == TRUE )
        {
            Output_Results( system, control, data, &workspace, &lists, &out_control );
            Analysis( system, control, data, &workspace, &lists, &out_control );
        }

        steps = data->step - data->prev_steps;
        if ( steps && out_control.restart_freq &&
                steps % out_control.restart_freq == 0 &&
                output_enabled == TRUE )
        {
            Write_Restart( system, control, data, &workspace, &out_control );
        }
    }

    if ( out_control.write_steps > 0 && output_enabled == TRUE )
    {
        fclose( out_control.trj );
        Write_PDB( system, &(lists[BONDS]), data, control, &workspace, &out_control );
    }

    data->timing.end = Get_Time( );
    data->timing.elapsed = Get_Timing_Info( data->timing.start );
    if ( output_enabled == TRUE )
    {
        fprintf( out_control.log, "total: %.2f secs\n", data->timing.elapsed );
    }

    return SUCCESS;
}


int Cleanup( reax_system * const system, control_params * const control,
        simulation_data * const data, const int output_enabled )
{
    Finalize( system, control, data, &workspace, &lists, &out_control,
           output_enabled );

    sfree( lists, "main::lists" );

    return SUCCESS;
}