/*****************************************************
**
**  S-PRES Copyright (c) 2010
**
**  Joshua T Berryman
**
**  This file is part of S-PRES
**
**  S-PRES 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 3 of the License, or
**  (at your option) any later version.
**
**  S-PRES 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, see <http://www.gnu.org/licenses/>.
**
**  If you make use of this program for research 
**  resulting in a publication, please cite:
**
**  Joshua T Berryman and Tanja Schilling, 
**  Sampling rare events in nonequilibrium and nonstationary systems. 
**  
**  J Chem Phys, 133(24):244101, Dec 2010.
**
******************************************************/
#ifndef HAVE_SHEARISING
#define HAVE_SHEARISING

#include <fstream>
#include <iostream>
#include <math.h>

#include "../../spres/src/simulation.h"
#include "../../spres/src/spres.h"

#include "shiStats.h"
#include "ran2.h"

using namespace std;

#define ONSAGER_TCRIT (2.269185)

class spinCluster;     //class for tracking structure of nuclei
class shiStats;    // class for evaluating finer details of a given state

typedef bool SPIN_T;

#define SPIN_DOWN false
#define SPIN_UP   true

#define FLIP_SPIN( x ) ( ( x == SPIN_UP ) ? x = SPIN_DOWN : x = SPIN_UP )
#define PAIR_MATCH( i, j, k, l ) ( ( spins[i][j] == spins[k][l] ) ? -1.0 : 1.0 )


/* define state file format .shi */
typedef enum {SHI_FILE_ENTRY_XSIZE        = 0,
               SHI_FILE_ENTRY_YSIZE        = 1,
               SHI_FILE_ENTRY_PBCSHEAR     = 2,
               SHI_FILE_ENTRY_NUM_ENTRIES  = 3} shiFile;

typedef enum {SHI_DYNAMICS_NONCONSERVED      = 0,
               SHI_DYNAMICS_KAWASAKI          = 1,
               SHI_DYNAMICS_ASEP              = 2,
               SHI_NUM_DYNAMICS               = 3} shiDynamics;

//get rid of this annoying typedef by switching to C++ string file input instead of c_strings.
#define SHI_MAX_X_SIZE 200


/** main class for shear-ising model */
class shi : public simulation {

public:
    shiDynamics dynamicsType;

    RNG2        rng;
    

    SPIN_T       **spins;
    int           *upSpinXpos, *upSpinYpos, *upSpinListP, nUpSpins;
    int            xSize, ySize;
    int            pbcShear;
    
    bool           haveClusters; //track if cluster list is up to date

    double  shearRate;       /* chance of a shear event per strip per sweep */
    double  shearDecayRate;  /* half-life of shear decay */      
    double  fieldStrength, couplingConstant, temperature, negInvT;
    double  magnetisation;

    //variables describing ASEP (Asymmetric Exclusion Process) dynamics
    double  asepInsertP, asepEscapeP, asepRightHopP;
    int     nHops, nFluxEvents;

    void    buildSpinList(); /* manage list of up-spin locations for conserved magnetism */
    void    fillSpinList();
    void    printSpinList(){
        for( int i = 0; i < nUpSpins; i++ )
            cerr << i << " " << upSpinXpos[i] << " "  << upSpinYpos[i] << endl;
        cerr << endl;
    }


    void    run();
    void    mcSweep();
    void    readParameterFile();
    void    setupNew();
    void    setupFromFile( char *configFileName );
    shi    *copyShi( shi *old );
    
    void             saveFrameToFile( char *trajectoryFileName ); /** write to file with provision for further frames */
    void             usage();    

    long          *iran;         //pointer to RNG seed
    shiStats      *stats;
    string         configFileName, parameterFileName, logFileName;
    long           mcSteps, mcSnapEvery;       


    /** order parameter for most Ising calculations*/    
    int count_nUpSpins(){
        int n = 0;

        if( dynamicsType == SHI_DYNAMICS_ASEP ){
            return( nUpSpins );
        }
        for(int i = 0; i < xSize; i++ ){
            for(int j = 0; j < ySize; j++ ){
                n += (int)spins[i][j];            
            }
        }   
        return( n );
    }

    /** population as a (delta function) probability vector */
    double *writePopAsVec( int *pt_size ){
    
        double *pop;
        int     size;

        size = (xSize * ySize) + 1;
       *pt_size = size;
        pop = new double[size];
        memset( pop, 0, size * sizeof( double ) );
        pop[nUpSpins] = 1.0;

        return( pop );
    }

    /** system energy */
    double systemEnergy(){
        double  e = 0.0;
        for(int i = 0; i < xSize; i++ ){
            for(int j = 0; j < ySize; j++ ){
                e += siteEnergy( i, j );            
            }
        }   
        return( e );
    }
//count the number of particles which have optimal energetics - a cheap and cheerful order parameter
    int numberBuried() {

      int    i,j, ii, jj, ij;
      int    n;

      n     =  0;
      for( ij = 0; ij < nUpSpins; ij++ ){

            i = upSpinXpos[ij];
            j = upSpinYpos[ij];

            ii = i + 1;
            if( ii >= xSize )               ii -= xSize;
            if( spins[ii][j] == SPIN_DOWN ) continue; 

            jj = j + 1;
            if( jj > ySize - 1 )            jj -= ySize;
            if( spins[i][jj] == SPIN_DOWN ) continue; 

            ii = i - 1;
            if( ii < 0 )                              ii += xSize;
            if( spins[ii][j] == SPIN_DOWN ) continue; 

            jj = j - 1;
            if( jj < 0 )                              jj += ySize;
            if( spins[i][jj] != SPIN_DOWN ) n++; 
                
      }
      return( n );
    }
/** These functions are required for shi to implement the abstract "simulation" class described in
*** spres.h
**/
    void    reset();                            /* (re)-equilibrate routine */
    simulation *copySelf(){                     /* return a duplicate of my data structure */
      return copyShi(this);
    };
    
    string  get_stateVectorFileName(){
      string *sfn;
      sfn = new string;
      sfn->assign("shiObservable.dat");
      return( *sfn );
    };
    
    void    reLoadFromFile( char *fileName );   /* load new state into existing system */
    void    reLoad( istream *inStream );        /* load new state into existing system */
    unsigned int     save( ostream *outStream );/* write current state to iostream */
    void    saveToFile( char *restartFileName ){/* write current state to file, overwrite if exists */
             ofstream  restartFile;
             restartFile.open( restartFileName );
             if( !restartFile.good() ){
                cerr << "Error, could not open file:" << restartFileName << endl;
                exit( 8 );
             }

             save( &restartFile );
             restartFile.close();  

    }

    void            stepTime(){  for( int t = 0; t < spresSteps; t++ ) mcSweep(); }
    void            calcMyStats(){stats->calcFrameStats( this );}
    
    string          get_spresLogFileName(){return   (spresLogFileName);};
    string          get_spresRatesFileName(){return (spresRatesFileName);};
    long int       *get_iran(){return (iran);};
    double          get_spresCriterionMax(){return (spresCriterionMax);};
    double          get_spresCriterionMin(){return (spresCriterionMin);};
    int             get_spresNumCompleteMatrices(){return (spresNumCompleteMatrices);};
    int             get_nFluxEvents(){return(nFluxEvents);};
    double         *get_derivedData(int* pt_size){
        if( dynamicsType == SHI_DYNAMICS_KAWASAKI || dynamicsType == SHI_DYNAMICS_NONCONSERVED ){
            return( writeClusterVec( pt_size ) );
        }else if( dynamicsType == SHI_DYNAMICS_ASEP ){
             return( writePopAsVec( pt_size ) );
        }else{
            *pt_size = 0;
             return( 0 );
        }
    };
    double          get_reactionCoordinate(){

      switch( spresCriterion ){
            case SHI_STATSOP_NUM_UPSPINS:
                  return( count_nUpSpins() );
            case SHI_STATSOP_NUM_BURIED:
                  return( numberBuried() );
            case SHI_STATSOP_CLUSTER_MOMENT:
                  return( double(clusterMoment()) );
            case SHI_STATSOP_LARGEST_CLUSTER:
                  return( double(largestCluster()) );
            default:
                  cerr << "Error: spres criterion unrecognised\n";
                  exit( 8 );
        }
    };
/** *************************************************************************/
/** These variables are used to fulfill the SPRES api, but are not exposed directly */
    string restartFileName;
    string spresLogFileName;    // # name of log file for spres run
    string spresRatesFileName;  // # name of output file for spres rates, 
    double spresCriterionMin;   // # treat the rare event as possibly underway >= this point 
    double spresCriterionMax;   // # treat the rare event as finished > this point
    int    spresNumBins;        // # staging post for input to spres algorithm 
    int    spresSteps;          // # staging post for input to spres algorithm 
    int    spresPathsPerBin;    // # staging post for input to spres algorithm 
    int    spresNumMatrices;    // # number of spres matrices
    int    spresNumCompleteMatrices; // # number of spres matrices
/** *************************************************************************/

    void      buildClusters();
    int       clusterMoment();
    int       largestCluster();
    double   *clusterSizeCount();


private:
    long              seed;
    enum              sim_stage { MC, SPRES }; 
    void              evolve(sim_stage stg);// run whatever was requested  

    void              shear( int stripIndex );
    void              kawasakiFlip( int spinIndex );
    void              asepHop( int spinIndex );
    void              deterministicAsep();

    double            siteEnergy( int i, int j);

    inline void  wrap_ij( int *i, int *j ){
        /* handle PBCs */
        if( *j < 0 ) {
            *j += ySize;
            *i += pbcShear;
        }else if ( *j >= ySize ){
            *j -= ySize;
            *i -= pbcShear;
        } 
        if( *i < 0 ) {
              *i += xSize;
        }else if( *i >=  xSize ){
              *i -= xSize;
        }
    }


    ofstream ofile;

    spinCluster      *clust;
    int               clusterCount;
    double           *writeClusterVec(int* pt_size);
      
/**Required for spres */
    long     spresEvalSteps;       // # measurements for averages
    long     spresSnapSteps;       // # snapshots during mc
    shiOpId  spresCriterion;       // # quantity which must monotonically increase 
};


class spinCluster { 
 public:
    int           size;
    int           rootMember;
    spinCluster  *next;

    //function to start a new cluster
    void init( int root ) {
      size       = 0;
      next       = 0;
      rootMember = root;
    }
};

#endif




