/*
    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,                   November 18 1997
 *
 * Define our own version of IO functions.
 *
 *  dft_text_FILE* dft_text_fopen(...);         (done, wait for checking)
 *  int dft_text_fclose(dft_text_FILE*, ...);   (done, wait for checking)
 *
 *  dft_text_fgets(dft_text_FILE*, ...);        (done, wait for checking)
 *  dft_text_fscanf(dft_text_FILE*, ...);       (done, wait for checking)
 *  dft_text_rewind(dft_text_FILE*);
 *  dft_text_fputc_old(char c, dft_text_FILE *file);
 *
 */

/* $Id: dft_text_FILE.cpp,v 1.7.2.2 2003/05/29 18:54:17 ivan Exp $ */

#include <stdarg.h>
#include <stdio.h>
#include "header.h"
#include "parallel.h"


// Member function definitions for class  dft_text_FILE:

dft_text_FILE::dft_text_FILE()
{ 
  my_id = System::Get_procID(); 
  io_id = System::Get_IOprocID(); 
  file_length = 0L;
  file_string = current_pointer = old_pointer
    = (char*) 0;
  file_handle = (FILE*) 0;
}

dft_text_FILE::~dft_text_FILE()
{
  if (file_string != NULL)
    myfree(file_string); 
  if (file_handle != NULL) 
    fclose(file_handle); 
}

void
dft_text_FILE::init()
{
  current_pointer = old_pointer = file_string
    = (char*) mymalloc(file_length,"file_string","dft_text_FILE");
}

///////////////////////////////////////////////////////

dft_text_FILE *
dft_text_fopen(const char *filename, const char *type)
{
  dft_text_FILE *file;

  // Need to consider what we do if type is not "r"
  if (strncmp(type,"r",1)!=0)
    die("dft_text_fopen don't know what to do with non-read opens.");

  file = new dft_text_FILE;


#ifdef DFT_MPI

  int flag;

  if (file->my_id == file->io_id) {

    // get the length of file
    if ((file->file_handle = fopen(filename, "rb"))==NULL) 
      {  // open failed.
	flag = 0;
	// inform all other processors of failure.
	MPI_Bcast(&flag, 1, MPI_INT, file->io_id, MPI_COMM_WORLD);
	delete (file);
	return ( (dft_text_FILE*) 0 ); 
      }
    flag = 1;
    fseek(file->file_handle, 0L, SEEK_END); // move to the end
    file->file_length = ftell(file->file_handle); // get the length

    // inform all other processors of success.
    MPI_Bcast(&flag, 1, MPI_INT, file->io_id, MPI_COMM_WORLD);
  } else {
    MPI_Bcast(&flag, 1, MPI_INT, file->io_id, MPI_COMM_WORLD);
    if (flag == 0) { // open failure
      delete (file);
      return ( (dft_text_FILE*) 0 );
    }
  }
  MPI_Bcast(&(file->file_length), 1, MPI_LONG, file->io_id, MPI_COMM_WORLD);

#else // DFT_MPI

  // get the length of file
  if ((file->file_handle = fopen(filename, "rb"))==NULL) 
    {  // open failed.
      delete (file);
      return ( (dft_text_FILE*) 0 ); 
    }
  fseek(file->file_handle, 0L, SEEK_END); // move to the end
  file->file_length = ftell(file->file_handle); // get the length

#endif // DFT_MPI

  if (file->file_length == 0) { // file has no content
    delete(file);
    return ( (dft_text_FILE*) 0 );
  }

  // allocate file_string;
  file->init();

  if (file->my_id == file->io_id) {
    rewind(file->file_handle);
    fread(file->file_string,file->file_length,1,file->file_handle);
  }
#ifdef DFT_MPI
  MPI_Bcast(file->file_string, file->file_length, MPI_CHAR, file->io_id, MPI_COMM_WORLD);
  MPI_Barrier(MPI_COMM_WORLD);
#endif // DFT_MPI

  return (file);
}


/*
 * dft_text_FILE::expand_include()
 *
 * expand the includes to read all include files too.
 * not circular include checks!!!  Be careful.
 */
void 
dft_text_FILE::expand_include()
{

  if (my_id == io_id) {

    // file_string is already pointed at a character string of the file.
    int done = FALSE, n_includes = 0, i, found = FALSE;
    FILE ** inc_file;
    long int *inc_file_length, tot_length = file_length;
    char file_name[DFT_FILENAME_LEN], **inc_file_ptr0, **inc_file_ptr1, *p;
    char line[DFT_LINE_LEN], key[DFT_MSG_LEN];

    while (! done) {
      n_includes = 0;

      // go and count all includes, 
      while (dft_text_fgets(line,DFT_LINE_LEN,'\\',this) != NULL) {
	if (sscanf(line,"%s",key) > 0) {
	  if ( MATCH(key,"include") ) {
	    n_includes++;
	  }
	}
      }
      dft_text_rewind(this);

      if ( n_includes > 0 ) {

	inc_file = (FILE**) mymalloc(sizeof(FILE*)*n_includes,
				     "inc_file",
				     "dft_text_FILE::expand_include");
	inc_file_length = (long int*)mymalloc(sizeof(long int)*n_includes,
					      "inc_file_length",
					      "dft_text_FILE::expand_include");
	inc_file_ptr0 = (char**) mymalloc(sizeof(char*)*n_includes,
					  "inc_file_ptr0",
					  "dft_text_FILE::expand_include");
	inc_file_ptr1 = (char**) mymalloc(sizeof(char*)*n_includes,
					  "inc_file_ptr1",
					  "dft_text_FILE::expand_include");

	for (i = 0; i < n_includes; i++) {
	  // find next include, get name to file_name, inc_file_ptr0/1[i] set.
	  found = FALSE;
	  while ( (!found ) && (dft_text_fgets(line,DFT_LINE_LEN,'\\',this) != NULL)) {
	    if (sscanf(line,"%s",key) > 0)
	      if ( MATCH(key,"include") )
		if (sscanf(line,"%*s %s",file_name) > 0) {
		  inc_file_ptr0[i] = old_pointer;
		  inc_file_ptr1[i] = current_pointer;
		  found = TRUE;
		}
	  }
	  if ( (inc_file[i] = fopen(file_name,"rb")) != NULL) {
	    fseek(inc_file[i], 0L, SEEK_END);
	    inc_file_length[i] = ftell(inc_file[i]);
	    tot_length += inc_file_length[i]
	      - (inc_file_ptr1[i]  - inc_file_ptr0[i]);
	    rewind(inc_file[i]);
	  } else {
	    inc_file_length[i] = 0L;
	  }
	}
	dft_text_rewind(this);

	// now has the tot_length;
	long int section_length;
	p = (char*) mymalloc(tot_length,"","");
	file_string = p;
	for (i = 0; i < n_includes; i++) {
	  section_length = inc_file_ptr0[i] - current_pointer;
	  strncpy(p, current_pointer, section_length);
	  p += section_length;
	  current_pointer = inc_file_ptr1[i];

	  if (inc_file_length[i] > 0L) 
	    fread(p,inc_file_length[i],1,inc_file[i]);
	  p += inc_file_length[i];
	  fclose(inc_file[i]);
	}
	// now the last chunk:
	strncpy(p,current_pointer, old_pointer+file_length-current_pointer-1);

	// release temporary variables
	myfree(inc_file);
	myfree(inc_file_length);
	myfree(inc_file_ptr0);
	myfree(inc_file_ptr1);

	// now can deallocate old string, 
	myfree(old_pointer);
	dft_text_rewind(this);
	file_length = tot_length;

      } else {
	done = TRUE;
      }
    }
  }

#ifdef DFT_MPI
  // now also need to tell all other processors to
  // receive this new file string.
  MPI_Bcast(&file_length, 1, MPI_LONG, io_id, MPI_COMM_WORLD);

  if (file_length <= 0L) { // file has no content. How does this happen?
    die("Fatal error in dft_text_FILE::expand_include, file length <=0\n");
  }
  
  if (my_id != io_id) {
    if (file_string != NULL) 
      myfree(file_string);
    init();
  } 

  MPI_Bcast(file_string, file_length, MPI_CHAR, io_id, MPI_COMM_WORLD);
  MPI_Barrier(MPI_COMM_WORLD);
#endif // DFT_MPI
      
}


int 
dft_text_fclose(dft_text_FILE* file)
{
  delete file;
  return 0; // assume success.
}

/*
 *  dft_text_fgets()  reads  in at most one less than n characters from
 *  stream and stores them into the buffer pointed  to  by  s.
 *  Reading  stops after an EOF or a newline.  If a newline is
 *  read, it is stored into the  buffer.   A  '\0'  is  stored
 *  after the last character in the buffer.
 */
char *
dft_text_fgets(char * s, int n, dft_text_FILE * file)
{
  int i;

  file->old_pointer = file->current_pointer; // trickery to track the beginning.

  if (file->current_pointer + n - 1 > file->file_string + file->file_length)
    {
      n = file->file_string + file->file_length - file->current_pointer + 1;
    }

  for (i = 0; i < n-1; i++) 
    {
      s[i] = file->current_pointer[i];
      if (file->current_pointer[i] == '\n')
	{ // end of line
	  i ++;
	  break;  // break the for loop, stop reading.
	}
      else if (file->current_pointer[i] == '\0')
	{ // encounter string termination character
	  break; // break the for loop, stop reading.
	}
    }

  if (i > 0) {
    s[i] = '\0';  // put terminating character in s;
    file->current_pointer += i; // increment pointer

    return s; // upon success
  } else {

    return ( (char *) 0 ); // upon failure
  }
}


/*
 *  This function reads  in at most one less than n characters from
 *  stream and stores them into the buffer pointed  to  by  s.
 *
 *  Reading will continue across newlines if they are terminated with 
 *  special character 'c', say '\'.
 *
 *  Reading  stops after an EOF or a newline.  If a newline is
 *  read, it is stored into the  buffer.   A  '\0'  is  stored
 *  after the last character in the buffer.
 */
char *
dft_text_fgets(char * s, int n, char c, dft_text_FILE * file)
{
  int i, j, m = n;

  file->old_pointer = file->current_pointer; // trickery to track the beginning.

  if (file->current_pointer + m - 1 > file->file_string + file->file_length)
    {
      m = file->file_string + file->file_length - file->current_pointer + 1;
    }

  for (i = j = 0; i < m-1; i++, j++) 
    {
      s[i] = file->current_pointer[j];
      if (file->current_pointer[j] == '\n')
	{ 
	  if ( (j == 0) || (file->current_pointer[j-1] != c) ) {
	    // end of line
	    i++; j++;
	    break; // break the for loop, stop reading.
	  } else {
	    // read line continuation marker
	    // back track i;
	    i -= 2;
	  }
	}
      else if (file->current_pointer[j] == '\0')
	{ // encounter string termination character
	  break; // break the for loop, stop reading.
	}
    }

  if (i == n-1) {
    die(">>Within dft_text_FILE dft_text_fgets(). Line too long. Increase line length");
  }

  if (i > 0) {
    s[i] = '\0';  // put terminating character in s;
    file->current_pointer += j; // increment pointer

    return s; // upon success
  } else {

    return ( (char *) 0 ); // upon failure
  }
}

void
dft_text_rewind(dft_text_FILE * file)
{
  file->current_pointer = file->old_pointer
    = file->file_string;
}

// MODIFIES input stream!! USE WITH CAUTION!!
void 
dft_text_fputc_old(char c, dft_text_FILE * file)
{
  file->old_pointer[0] = c;
}

/* 

   Prof Arias's scanf, "pascanf".  It should would just like dft_text_fscanf
   except that the first argument is a pointer to the string holding the
   input stream, rather than a file-pointer.  (By string, I mean a
   pointer to the first character of the string.)  See below for an
   example for the use of this program.

   NOTE: the string is updated as the input is read so that the next
   call will start reading at the appropriate place.  This is
   convenient, but it does mean that the FIRST ARGUMENT IS DESROYED by
   the time you are done reading the file.

   ERRORS generated here by sscanf are passed back as the value EOF
   for the function call.

   BUGS: The code should work, except for those special cases for
   which it traps and complains about, before terminating execution.
   Hopefully, the cases I haven't handled are rare.  We could easily
   take care of those cases if they start comming up a lot.
*/

int 
dft_text_fscanf( dft_text_FILE *file, const char *fmtin, ...)
{
#define Lenmx 1024 /* Maximum allowable length for the format specifier */
  /* Variable argument list pointer */
  va_list ap;

  /* Internal temporary variables */
  char format[Lenmx]; /* Stores writable copy of input conversion specifier */
  char fmt2[Lenmx]; /* Used to store input conversions one item at a time */
  char *fmt,*fmt1; /* Pointers into format for parsing each specifier */
  char c; /* Temp storage for characters we set to \0 in while parsing */
  void *item; /* Used with va_arg to hold pointers poped off the stack */
  int assigned_items = 0; /* Keep track of number of successful conversion made */
  int used; /* Used with sscanf to determine length of input stream consumed */
  int len; /* Counts up string length for safety */
  char** stream;

  file->old_pointer = file->current_pointer; // trickery to track the beginning.
  stream = &(file->current_pointer);

  /* Copy format string into local writable space (fomat), and check for length
     and presence of unwated *'s (a smarter version could handle the *'s */
  strncpy(format,fmtin,Lenmx); 
  for (len=0; len<Lenmx; len++)
    if (format[len]=='\0') break;
  format[Lenmx-1]='\0';
  if (len+3>=Lenmx)
    die("\n\nError: not enough space (Lenmx) in pascanf for input conversion string!\n(conversion string=\"%s\")\n\n",format);
  

  fmt=format;
  while (*fmt) /* Check for '*' characters and length */
    if (*(fmt++)=='*')
      die("\n\nError: pascanf does not treat '*' in input conversion strings!\n(conversion string=\"%s\")\n\n",format);
  fmt=format;
  /* printf("INPUT: %d character format: \"%s\"\n",len,fmt); */

/* Initialize variable argument list to proper point in the stack */
  va_start(ap, fmtin); 


 /* This loop parses the format, picking out each %-field */
  while (*fmt)
    {
      fmt1=fmt;
      fmt++;
      while (*fmt != '\0' && *fmt != '%') fmt++; /* Locate % or "EOF" */

      /* We have now identified a single input field, prepare to use sscanf
	 to read it in.  Note that to do this, we have to update the pointer
	 into the input stream.  To do this we use the %n option on sscanf
	 and put the number of characters consumed from the stream into 
	 the variable "used". */

      if (*fmt=='%' && *fmt1=='%' && fmt==fmt1+1)
	{ /* %% constitutes a special case where the input field contains 
	     two %'s and which is the only field beginning with % which 
	     doesn't consume a pointer from the stack. */
 
	  fmt++; /* Claim the second % as part of this input field */

	  strcpy(fmt2,"%% %n"); /* Build format string, note we need
				 the %n to get the amount of stream
				 consumed */
	  /* printf("Input stream: \"%s\" / Format: \"%s\"\n",(*stream),fmt2); */

	  /* Scan stream with error check */
	  if (sscanf((*stream),fmt2,&used)!=0) return(EOF);
	}
      else
	{ 
	  /* Build into fmt2 format string for only the field from
             [fmt,fmt1] including the %n which we need for acounting
             consumption of the stream */
	  c=*fmt; *fmt='\0'; strcpy(fmt2,fmt1); *fmt=c;
	  strcat(fmt2,"%n");

	  /* printf("Input stream: \"%s\" / Format: \"%s\"\n",(*stream),fmt2); */

	  if (*fmt2=='%') 
	    { /* An actual input item!  Consume one argument from stack!!! */
	      item=va_arg(ap, void *); 

	      /* Scan stream with error check */
	      if (sscanf((*stream),fmt2,item,&used)!=1) return(EOF);

	      assigned_items ++;
	    }
	  else 
	    { /* Simple text advance through stream (no argument from stack) */

	      /* Scan stream with error check */
	      if (sscanf((*stream),fmt2,&used)!=0) return(EOF);
	    }

      	}
      /* Advance stream pointer */
      (*stream)+=used; 
    }

  return assigned_items;

}
