/*****************************************************
**
**  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.
**
******************************************************/
#include <iostream>
#include <string>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <cstring>
#include "../../shi/src/shi.h"
#include "../../spres/src/spres.h"


//build clusters using a breadth-first search
//Algorithm is on page 470 of Cormen, Leiserson & Rivest.
void shi::buildClusters() {

    spinCluster  *c;
    int           j, *clustFlags, p, wrapX2, wrapY2;
    int           clusterQueueHead, clusterQueueTail;      

    //clusterCount tracks number of clusters actually allocated
    while( clust != 0 ){
      c     = clust;
      clust = c->next;
      delete c;
    }
    clusterCount = 0;
    clustFlags   = new int[nUpSpins];

    wrapX2 = ( xSize - 1 ) * ( xSize - 1 );
    wrapY2 = ( ySize - 1 ) * ( ySize - 1 );

    //initialise the clustering
    for (int i = 0; i < nUpSpins; i++){
        upSpinListP[i]    = -1;
        clustFlags[i]     = -2; //setting cluster flag to -2
                                //corresponds to coloring the node black 
                                //in the CLR algorithm
    }

    //now search for cluster roots
    for (int i = 0; i < nUpSpins; i++){

      //if the spin is not already clustered, then it is a cluster
      //root. 
      if( clustFlags[i] == -2 ) {
        
        //setting cluster id to -1 
        //corresponds to coloring the node grey 
        //in the algorithm from CLR
        clustFlags[i] = -1;

        //start a new cluster
        c = new spinCluster;
        c->init( i );

        //this queue stores the next particles to add to the cluster
        clusterQueueHead = i;
        clusterQueueTail = i;

        while( clusterQueueHead != -1 ) {

          p = clusterQueueHead;

          //label the immediate neighbours
          for( j = 0; j < nUpSpins; j++ )
          {
      
              if( clustFlags[j] == -2 && j != p ) { 

                  int dX, dY;
    
                  //test that the two spins are neighbours
                  dX  = upSpinXpos[j] - upSpinXpos[p];
                  dX *= dX;
                  if( dX > 1 && dX < wrapX2 ){
                      continue;
                  }
                  dY  = upSpinYpos[j] - upSpinYpos[p];
                  dY *= dY;
                  if( dY > 1 && dY < wrapX2 ){
                      continue;
                  }
                  if( dX != 0 && dY != 0 ){ // no diagonal neighbours
                      continue;
                  }

                  //mark this node 'grey'
                  clustFlags[j] = -1; 

                  //add it to back of the queue 
                  upSpinListP[clusterQueueTail] = j;
                  clusterQueueTail              = j;
              }
          }
        
          //take the particle off the head of the queue
          //because all its neighbours should be on the queue now
          clusterQueueHead = upSpinListP[p];
        
          //add it fully to the cluster
          c->size++;

        }
        c->next = clust;
        clust   = c;
        clusterCount++;
      }
    }

    delete[] clustFlags;

    haveClusters = true;

    return;

}


double *shi::writeClusterVec( int *pt_size ){

   double      *dVec;
   spinCluster *c;

  *pt_size = nUpSpins + 1;

   if( !haveClusters ){
      buildClusters();
   }

   dVec = clusterSizeCount();

   while( clust != 0 ){
       c     = clust->next;
       delete clust;
       clust = c;
   }
   haveClusters = false;

   return( dVec );
}

int shi::largestCluster() {

        spinCluster *c;
        int          l;

        l = 0;
        if( !haveClusters ){
          buildClusters();
        }
        c = clust;
        while( c != 0 ){
          if( c->size > l ){
              l = c->size;
          }
            c = c->next;
        }

        return( l );
}
/** this handy reaction coordinate increases whenever two clusters of any size are joined */
int shi::clusterMoment() {

        spinCluster *c;
        int          m;

        m = 0;

        if( !haveClusters ){
          buildClusters();
        }
        c = clust;
        while( c != 0 ){
          m += ( c->size - 1 );
          c = c->next;
        }

        return( m );
}

double *shi::clusterSizeCount() {

        spinCluster *c;
        double      *csc;
        int          total;

        csc   = new double[nUpSpins + 1];
        memset( csc, 0, (nUpSpins + 1) * sizeof( double ) );
        total = 0;

        c = clust;
        while( c != 0 ){
          csc[c->size] += 1.0;
          total        += c->size;
          c = c->next;
        }

        if( total != nUpSpins ){
            cerr << "Some problem in cluster assignment\n";
            cerr << "total spins: " << total << " of " << nUpSpins << endl; 
        }

        return( csc );
}











