/*
    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.
*/

/*
 * Contains the parser (and related functions) that reads
 * the input file and figures out what it says, checks for errors,
 * and issues commands to do what the input files wants.
 *
 * The parser does not define any commands; that is done in the
 * files in the commands/ directory.
 *
 */

#include <time.h>
#include "header.h"
#include "commands/command.h"

//
// Special errors from processing go into
// this little buffer
//
char processing_error[DFT_LINE_LEN];

// Print out all the commands and their format
static void
print_commands(command *list)
{
  if (list == NULL)
    die("\nCan't print a null command list!\n");

  command *c = list;
  do
    {
      dft_log("%s\n",c->format);
      c = c->next_command;
    }
  while (c!=NULL);
}

// Read a non-empty, non-comment line from the input file
// and put it into the string s.  Up to n-1 chars are read.
// Returns NULL if EOF or some other fgets error is encountered.
static char *
read_a_line(char *s,int n,dft_text_FILE *fp)
{
  char key[DFT_MSG_LEN];
  while (1)
    {
      if (dft_text_fgets(s,n,'\\',fp)==NULL)
	return NULL;
      else if (s[0]=='#' || sscanf(s,"%s",key)<1)
	continue;
      else
	return s;
    }
}

// Returns command pointer for command in string str
// or NULL if it can't find it
static command *
find_command(char *str,command *list)
{
  command *c = list;

  while(c!=NULL)
    {
      if (strcmp(str,c->name) == 0)
	return c;
      else
	c = c->next_command;
    }
  return NULL;
}

// Returns command pointer for command number
// or NULL if it can't find it
static command *
find_command(int number,command *list)
{
  command *c = list;

  while(c!=NULL)
    {
      if (number == c->number)
	return c;
      else
	c = c->next_command;
    }
  return NULL;
}

// Go through dependency list of command cmnd
// and check to see if they are satisfied (return 0)
// or return the failed dependency number
static int
check_dependencies(command *cmnd,command *list)
{
  int idep,i=0;

  while( (idep=cmnd->dependencies[i]) != 0)
    {
      command *cdep = find_command(abs(idep),list);
      if (cdep==NULL)
	die("PROBLEM %d %d %s!!!\n",i,idep,cmnd->name);
      if ( (idep>0 && !cdep->found) || (idep<0 && cdep->found) )
	return idep;
      else
	i++;
    }
  return 0;
}

// Print out the status of all found commands with comments and formats
// for those commands preceding them
static void
print_all_command_status(Output *out,command *list,Everything &e)
{
  command *c = list;
  while (c!=NULL)
    {
      if (c->found)
	{
	  out->printf("%s\n# %s\n",c->comments,c->format);
	  c->print_status(e,out);
	  out->printf("\n");
	}
      c = c->next_command;
    }
  out->flush();
}


//
// The parser!  (Relatively) short and sweet.
//
void
parse(dft_text_FILE *input_file,Everything &everything)
{
  // Create the command list
  command *list = create_command_list();

  // For now, let's say we have a max of 1000 lines... this
  // will be dynamically changed if there are more lines
  int max_lines = 200;
  command **line_status = (command **)mymalloc(sizeof(command *)*max_lines,
					       "line_status","parse");

  // Count lines, check for command validity, and store
  // the command number for that line
  char input_line[DFT_LINE_LEN];
  int n_lines = 0;
  dft_text_rewind(input_file);
  while (read_a_line(input_line,DFT_LINE_LEN,input_file) != NULL)
    {
      char key[DFT_MSG_LEN];

      // Is the command recognized?
      sscanf(input_line,"%s",key);
      command *cmnd = find_command(key,list);
      if (cmnd == NULL)
	{
	  dft_log(DFT_SILENCE,"\nValid commands are:\n\n");
	  print_commands(list);
	  die("\nCommand '%s' is unknown\n\n",key);
	}

      // Store the command and increment counter
      line_status[n_lines] = cmnd;
      n_lines++;
      
      // If we are going to run out of space, get some more!
      if (n_lines >= max_lines)
	{
	  max_lines += 100;
	  line_status = (command **)myrealloc(line_status,
					      sizeof(command *)*max_lines,
					      "line_status","parse");
	}
    }

  // Go through all the commands we have and find the
  // ones not specified in the input.
  // For each such command, if it has a default, do the
  // default setup and mark it as found.
  // Also, print out the default corresponding command line.
  dft_log("\nIssuing the following default commands since they\n");
  dft_log("were not explicitely specified.\n\n");
  command *cmnd = list;
  while(cmnd != NULL)
    {
      int found=0;
      for (int l=0; l < n_lines; l++)
	if (cmnd==line_status[l])
	  found=1;
      if (!found && cmnd->has_default)
	{
	  cmnd->set_default(everything);
	  cmnd->print_status(everything,dft_global_log);
	  cmnd->found = TRUE;
	}
      cmnd = cmnd->next_command;
    }
  dft_log("\n");
  
  // Now we are ready to parse the lines.
  // We'll parse the file repeatedly until we're done, or
  // we stop because of an error.
  // We also assume that we have nothing to do, unless
  // we encounter an "overall_action" command.
  int nothing_todo = TRUE;
  int done = FALSE;
  do
    {
      // Go through the input lines and work on unprocessed lines
      dft_text_rewind(input_file);
      int n_failed = 0;
      int n_succeeded = 0;
      int line_counter = 0;
      while (read_a_line(input_line,DFT_LINE_LEN,input_file) != NULL)
	{
	  // Get the command corresponding to this line
	  command *cmnd = line_status[line_counter];
	  line_counter++;

	  // If already processed, skip it
	  if (cmnd == NULL)
	    continue;

	  // If you can't use it more than once, die
	  if(cmnd->found && !cmnd->allow_multiple)
	    die("\nCommand '%s' can not be used more than once\n\n",
		cmnd->name);

	  // Check the dependencies
	  int err=check_dependencies(cmnd,list);
	  if (err!=0)
	    {
	      n_failed++;
	      continue;
	    }
	      
	  // Try to process the command
	  err=cmnd->process_command(input_line,everything);
	  if (err!=0)
	    {
	      n_failed++;
	      continue;
	    }
	      
	  // It succeeded!  Mark it found and remove from list
	  cmnd->found=1;
	  line_status[line_counter-1]=NULL;
	  n_succeeded++;

	  // If this command is an overall activity to be performed,
	  // then set the flag saying that there is something to do
	  if (cmnd->is_overall_action)
	    nothing_todo = FALSE;

	} // while (current pass)

      // The pass over the input has ended.
      // If all the lines we processed failed, we should die.
      if (n_failed>0 && n_succeeded==0)
	{
	  dft_log(DFT_SILENCE,"\nConsistency problems:\n\n");
	  dft_text_rewind(input_file);
	  line_counter = 0;
	  while (read_a_line(input_line,DFT_LINE_LEN,input_file) != NULL)
	    {
	      command *cmnd = line_status[line_counter];
	      line_counter++;
	      if (cmnd == NULL)
		continue;
	      int err=check_dependencies(cmnd,list);
	      if (err!=0)
		{
		  dft_log(DFT_SILENCE,
			  "Command '%s' failed because it\n",cmnd->name);
		  if (err>0)
		    dft_log(DFT_SILENCE,
			    "requires command '%s' as well.\n\n\n",
			    find_command(err,list)->name);
		  else
		    dft_log(DFT_SILENCE,
			    "can not be used simultaneously with '%s'.\n\n\n",
			    find_command(-err,list)->name);
		}
	      else
		{
		  cmnd->process_command(input_line,everything);
		  dft_log(DFT_SILENCE,
			  "Command '%s' had a processing error:\n",
			  cmnd->name);
		  dft_log(DFT_SILENCE,"%s",processing_error);
		  dft_log(DFT_SILENCE,
			  "Correct command format is:\n%s\n",
			  cmnd->format);
		  dft_log(DFT_SILENCE,"The input was:\n%s\n\n\n",input_line);
		}
	    }
	  die("Exiting.\n\n");
	}

      // No failures:  as long as we have something to do, we're done!
      if (n_failed==0)
	{
	  // We sucessfuly parse everything, but were we given
	  // something to do?
	  if (nothing_todo)
	    {
	      dft_log("\nInput file does not specify an overall activity,\n");
	      dft_log("so there is nothing to do!\n\n");
	      dft_log("Issue at least one of the following commands in the input file:\n\n");
	      
	      command *c=list;
	      while (c!=NULL)
		{
		  if (c->is_overall_action)
		    dft_log("%s\n",c->name);
		  c = c->next_command;
		}
	      die("\nExiting.\n\n");
	    }
	  else
	    done = TRUE;
	}
      
    } while (!done);

  dft_log("The following is a template of all commands that have been\n");
  dft_log("specified in the input file or have been called with\n");
  dft_log("automatic defaults.\n");
  dft_log("\n>>>>>>>>>>>>>>>>>>Template Begins<<<<<<<<<<<<<<<<<<<<\n\n");
  dft_log("# Unit length is the Bohr radius.  Energies in Hartrees.\n\n");
  print_all_command_status(dft_global_log,list,everything);
  dft_log("\n>>>>>>>>>>>>>>>>>>Template Ends<<<<<<<<<<<<<<<<<<<<<<\n\n");

  free_command_list(list);
}

//
// Create a command list, execute default setups, and print
// out the resulting template.  Useful for when the user wants
// a default template.
//
void
print_default_template(Output *out,Everything &everything)
{
  // Create the command list
  command *list = create_command_list();

  // Go through all the commands and execute default setup
  // For those that have it
  command *c = list;
  while(c != NULL)
    {
      if (c->has_default)
	c->set_default(everything);
      c = c->next_command;
    }

  // Now print out the current status of all commands
  out->printf("\n>>>>>>>>>>>>>>>>>>Template Begins<<<<<<<<<<<<<<<<<<<<\n\n");
  out->printf("# Unit length is the Bohr radius.  Energies in Hartrees\n\n");
  c = list;
  while (c!=NULL)
    {
      out->printf("%s\n# %s\n",c->comments,c->format);
      if (c->has_default)
	c->print_status(everything,out);
      out->printf("\n");
      c = c->next_command;
    }
  out->printf("\n>>>>>>>>>>>>>>>>>>Template Ends<<<<<<<<<<<<<<<<<<<<<<\n\n");
  out->flush();

  // Free command list
  free_command_list(list);
}
