/*
    DFT++ is a density functional package developed by the research group
    of Professor Tomas Arias

    Copyright 1996-2003 Sohrab Ismail-Beigi

    This file is part of DFT++.

    DFT++ 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.

    DFT++ 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 DFT++; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    Please see the file CREDITS for a list of authors.

    For academic users, we request that publications using results obtained with
    this software reference

    "New algebraic formulation of density functional calculation," by Sohrab Ismail-Beigi
    and T.A. Arias, Computer Physics Communications 128:1-2, 1-45 (June 2000).

    and, if using the wavelet basis, further reference

    "Multiresolution analysis of electronic structure: semicardinal and wavelet bases,"
    T.A. Arias, Reviews of Modern Physics 71:1, 267-311 (January 1999).

    and 

    "Robust ab initio calculation of condensed matter: transparent convergence through
    semicardinal multiresolution analysis,'' I.P. Daykov, T.A. Arias, and
    Torkel D. Engeness, Physical Review Letters, 90:21, 216402 (May 2003).

    For your convenience, preprints of the above articles may be obtained from
    http://arXiv.org/abs/cond-mat/9909130, 9805262, and 0204411, respectively.
*/

/*
 *            Tairan Wang                        January 8, 1998
 * 
 * System.c -- provide MPI setup.
 *
 *
 */

/* $Id: System.cpp,v 1.16.2.13 2003/05/29 18:54:06 ivan Exp $ */

#include "header.h"
#include "parallel.h"   // include MPI related info.

#ifdef _WIN32
#include <windows.h>
#else
#include <sys/time.h>
#endif

///////////////////////////////
//                           //
// Define Static Data Member //
//                           //
///////////////////////////////
Output * System::global_log = NULL;
int System::N_Procs = 0;
int System::My_procID = 0;

// memory manager stuff
void ** System::scratch_pointers = NULL;
int * System::scratch_locks = NULL;
int * System::scratch_lengths = NULL;
int * System::scratch_sizes = NULL;
int System::num_scratch = 0;
#ifdef DFT_THREAD
  // default number of threads
int System::N_threads = 1;
#endif


//////////////////////////////////////////////
/*                                          *
 * Static Member Function. Initialize       *
 * system configuration info.               *
 *                                          *
 * Must be called before any object of the  *
 * class is actually created                *
 *                                          */
//////////////////////////////////////////////

void
System::GlobalInit(int* argc, char*** argv)
{
#ifdef DFT_MPI
  int id, num_procs;

  MPI_Init(argc, argv);
  MPI_Comm_rank(MPI_COMM_WORLD, &id);
  MPI_Comm_size(MPI_COMM_WORLD, &num_procs);

  System::N_Procs = num_procs;
  System::My_procID = id;
#else   // DFT_MPI
  System::N_Procs = 1;
  System::My_procID = 0;
#endif  // DFT_MPI
}

void
System::GlobalFinalize()
{
#ifdef DFT_MPI
  MPI_Finalize();
#endif // DFT_MPI
}


// platform-dependent timer
double
System::get_time(void)
{
#ifdef DFT_MPI
  return MPI_Wtime();
#elif defined _WIN32
  static LARGE_INTEGER freq;
  static LARGE_INTEGER t;
  static int first=1;
  if(first) {
    QueryPerformanceFrequency(&freq);
    first=0;
  }
  QueryPerformanceCounter(&t);
  return (double)t.QuadPart/(double)freq.QuadPart;
#else
  // more sensitive timer
  struct timeval t;
  gettimeofday(&t,NULL);
  return t.tv_sec + 1.e-6*t.tv_usec;
#endif
}

// Seed the random number generator with the current timer.
// flag==0 means all procs get the same seed
// flag==1 means different seeds on different procs
void System::seed_with_time(int flag)
{
  if (flag!=0 && flag!=1)
    die("flag is not 0 or 1 in System::seed_with_time(flag)\n\n");

  unsigned int seed = (unsigned int)System::get_time();

#ifdef DFT_MPI
  if (flag == 0)
    // Same seed on all processors
    MPI_Bcast(&seed,1,MPI_INT,System::Get_IOprocID(),MPI_COMM_WORLD); 
  else
    // Processor dependent seed
    seed = seed+System::Get_procID() + (1000*seed)/(System::Get_procID()+1);
#endif

  srand(seed);
}

// Bite the dust with some last words
void die(const char *last_words, ...)
{
  // variable argument list
  va_list ap;
  va_start(ap, last_words);

  System::global_log->vprintf(DFT_SILENCE, last_words, ap);
  System::global_log->flush();

#ifdef DFT_MPI
  MPI_Abort(MPI_COMM_WORLD, 1);
#endif // DFT_MPI

  exit(1);
}

/**********************************************************************
 *                                                                    *
 * scratch space manager                                              *
 *                                                                    *
 **********************************************************************/

// at the beginning of the program, we set up a number of scratch spaces;
// this function can be called more than once, with different arguments.
void 
System::setup_scratch(int howmany, int length, int datasize)
{
  // make sure none of the scratch spaces are in use while we 
  // do setup.

  dft_log("Setting up %d scratch spaces of length %d.\n", howmany, length);

  int i;
  for(i = 0; i < System::num_scratch; i++)
    if(System::scratch_locks[i] == TRUE)
      die("You tried to call System::setup_scratch() while some scratch spaces were in use.  How naughty! Anyways, ciao...\n");


  System::scratch_pointers = (void **) myrealloc(System::scratch_pointers,
					 (System::num_scratch + howmany)*sizeof(void *),
					 "scratch_pointers", "System::setup_scratch");
  System::scratch_locks = (int *) myrealloc(System::scratch_locks,
				    (System::num_scratch + howmany)*sizeof(int),
				    "lock_scratch", "System::setup_scratch");
  System::scratch_lengths = (int *) myrealloc(System::scratch_lengths,
				      (System::num_scratch + howmany)*sizeof(int),
				      "scratch_lengths", "System::setup_scratch");
  System::scratch_sizes = (int *) myrealloc(System::scratch_sizes,
				    (System::num_scratch + howmany)*sizeof(int),
				    "scratch_sizes", "System::setup_scratch");

  // allocate the newly requested scratchspaces

  for(i = 0; i < howmany; i++){
    System::scratch_pointers[System::num_scratch+i] =
      (void *) mymalloc(length*datasize,
                        "scratch space", "System::setup_scratch()");
    System::scratch_locks[System::num_scratch+i] = FALSE;
    System::scratch_lengths[System::num_scratch+i] = length;
    System::scratch_sizes[System::num_scratch+i] = datasize;
  }
  
  System::num_scratch += howmany;

}


// call this function to request a scratch space pointer.
void*
System::gimme_scratch(int length, int datasize)
{
  int i;
  int found_larger = -1;

  // search for an unlocked scratch
  for(i = 0; i < System::num_scratch; i++)
    if(System::scratch_sizes[i] == datasize &&
       System::scratch_locks[i] == FALSE)
      if(System::scratch_lengths[i] == length){
	// found an unlocked scratch, so lock it and return it
	System::scratch_locks[i] = TRUE;
	return System::scratch_pointers[i];
      }else if(System::scratch_lengths[i] > length){
	found_larger = i;
      }

  if(found_larger > -1){
    System::scratch_locks[found_larger] = TRUE;
    return System::scratch_pointers[found_larger];
  }
    

  // if we got here, there are no more scratches available, 
  // so die. we do this, to force the developers to think 
  // about how much scratch space they need ahead of time
  die("System::gimme_scratch(%d, %d) failed, not enough scratch space\n",
      length, datasize);

  return NULL;
}


// when you are done, you have to release the scratch space
void
System::release_scratch(void *ptr)
{
  int i;

  
  // find which scratch is being released
  for(i = 0; i < System::num_scratch; i++)
    if(System::scratch_pointers[i] == ptr){
      // gotcha. release it
      System::scratch_locks[i] = FALSE;
      return;
    }

  // if we got here, then the pointer passed in was not
  // a valid scratch.

  die("Invalid scratch pointer in System::release_scratch()\n");

}
