/* SimpleDefaultTheory.c
 * Contains code to read a theory into a SimpleDefaultTheory structure
 *
 * Written by Matt Levy.
 * Modified by Artur Mikitiuk.
 * Modified 20 Aug 1999 by Neil Moore <neil@cs.uky.edu>
 * Modified  5 Sep 1999 by Neil Moore <neil@cs.uky.edu>
 * Modified 15 Jun 2000 by Neil Moore <neil@cs.uky.edu>:
 *   Fixed a bug that could cause CreateFormulaFromString to access memory
 *    outside its buffer when parsing a bad default.  Now defaults of the
 *    form `x -> y' are treated as `x : -> y' (previously, the caused a
 *    crash; in sderes, they are considered to be a syntax error).
 *
 * Part of lderes.  See COPYRIGHT for copyright information.
 */

#ifndef _SIMPLEDEFAULTTHEORY_C
#define _SIMPLEDEFAULTTHEORY_C	1

#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>
#include <stdio.h>
#include "SimpleDefaultTheory.h"
#include "global.h"
#include "hash.h"
#include "parse.h"
#include "token.h"
#include "default_types.h"

/* Inline function definitions */

inline void stub(const char *message)
{
	if(verbosity > 0)
		printf ("%s not implemented\n", message);
}

inline void fatal_error(const char *message)
{
	if(verbosity > 0)
		fprintf (stderr, "Error: %s\n", message);
	exit(2);
}


/* Input: a table of formulae (see default_types.h)
     the index of the formula to be asserted
     the prover relative to which the formula will be asserted
   Output: TRUE if the formula can be asserted (0 for the index means an empty
       formula) FALSE if an inconsistency is created */

inline Boolean AssertFormula(FormulaPtr formulae,int formula,TableProver prover)
{
  int litIndex;
  
  if (formula == 0)
     return TRUE;
  for (litIndex = 1; litIndex <= formulae[formula][0]; litIndex++){
     if (!AddFact(prover, formulae[formula][litIndex]))
	return FALSE;
  }
  return TRUE;
}

/* Retract a fact. Input is similar to AssertFormula, as is output. */

inline Boolean RetractFormula(FormulaPtr formulae,int formula,
    TableProver prover)
{
  int litIndex;
  
  if (formula == 0)
    return TRUE;
  for (litIndex = 1; litIndex <= formulae[formula][0]; litIndex++){
     if (!RemoveFact(prover, formulae[formula][litIndex]))
	return FALSE;
  }
  return TRUE;
}

inline Boolean AssertCandidate(SimpleDefaultTheory *theory, int startRule,
    int endRule, Set candidate, int *size)
{
  int rule;
  
  /* Assert the conclusions of the rules appearing in the candidate set */
  /* Return TRUE if all are consistent FALSE o.w. */
  *size = 0;
  for (rule = startRule; rule <= endRule; rule++){
     if (MEMBER(rule,candidate)){
	if (!AssertFormula(theory->formulae,
	    theory->DefaultTable[rule].consequent,theory->prover))
	      return FALSE;
	(*size)++;
     }
  }
  return TRUE;
}

inline void RetractCandidate(SimpleDefaultTheory *theory,int startRule,
    int endRule, Set candidate)
{
  /* Reset the conclusions of the rules in the candidate to DONT_KNOW */
  int rule;
  
  for (rule = startRule; rule <= endRule; rule++){
     if (MEMBER(rule,candidate))
	RetractFormula(theory->formulae,
	    theory->DefaultTable[rule].consequent,theory->prover);
  }
}
  
inline Boolean RuleIsConsistent(SimpleDefaultTheory *theory, int rule)
{
  int *justifications, just;
  
  justifications = theory->DefaultTable[rule].justifications;
  /* Check each justification to see if it is consistent */
  for (just = 1; just <= justifications[0]; just++)
     if (Prove(theory->prover,theory->formulae[justifications[just]]) == FALSE)
	return FALSE;
  return TRUE;
}

inline Boolean CanFire(SimpleDefaultTheory *theory,int rule)
{
  if ((theory->DefaultTable[rule].prerequisite == 0) || Prove(theory->prover,
    theory->formulae[theory->DefaultTable[rule].prerequisite]) == TRUE)
	 return TRUE;
  return FALSE;
}

inline Boolean DoUpdate(SimpleDefaultTheory *theory,int rule)
{
  /* Assert the consequent of the rule, and add any firable rules to the update
  queue */
  return AssertFormula(theory->formulae,
      theory->DefaultTable[rule].consequent,theory->prover);
}

inline void ResetStratum(SimpleDefaultTheory *theory, int startRule,
    int endRule)
{
  int rule;
  
  /* Reset the stratum */
  for (rule = startRule; rule <= endRule; rule++){
     RetractFormula(theory->formulae,
	 theory->DefaultTable[rule].consequent,theory->prover);
  }
}

/* Prototypes */
FILE *OpenDescribedFile(char *file, char marker, char *extension, long *size);
long FileSize(char *file);
void LoadKnowledge(SimpleDefaultTheory *theory, int *tokenString,
    int numberAssertions, int *index);
void LoadDefaults(SimpleDefaultTheory *theory, int *tokenString,
    int numberDefaults, int *index);
void LoadStratification(SimpleDefaultTheory *theory, char *file);

/* Globals */
int currentFormula = 0;

/* Determine if theory has given candidate for extension of stratum indicated
by startRule and endRule */

Boolean CandidateIsExtension(SimpleDefaultTheory *theory, int startRule,
    int endRule, Set candidate)
{ /* Returns TRUE if candidate is extension FALSE if candidate is not extension
     DONT_KNOW if candidate is not extension, and can prune supersets */
 /* IMPORTANT: This method assumes that the stratum size and starting place are
 correct. If parameters are not correct, may cause an overflow of queue. */
  int rule;
  int rulesFired = 0;
  int reductSize = 0, candidateSize, start;
  int formula;
  Boolean returnValue = TRUE;

  if ((endRule>theory->DefaultTable[0].prerequisite) || (startRule>endRule))
    return FALSE;
  /* Assert W */
  ResetStratum(theory,startRule, endRule);
  for (formula = 1; formula <= SIZE(theory->W); formula++)
  AssertFormula(theory->formulae,theory->W[formula], theory->prover);
  if (!AssertCandidate(theory,startRule,endRule,candidate,&candidateSize)) {
    /* Candidate is inconsistend with W, should prune */
    return DONT_KNOW;
  }
  /* Compute reduct */
  for (rule = startRule; rule <= endRule; rule++){
    if (RuleIsConsistent(theory, rule)){
      ADD_ELEMENT(rule,theory->Reduct);
      reductSize++;
    } else if (MEMBER(rule,candidate)){
      /* Can prune since element of candidate didn't make it into reduct */
      return DONT_KNOW;
    } else
      REMOVE_ELEMENT(rule,theory->Reduct);
  } /* for */
  SET_SIZE(theory->Reduct) = reductSize;
  RetractCandidate(theory, startRule, endRule, candidate);
  /* Assert W */
  for (formula = 1; formula <= SIZE(theory->W); formula++)
    AssertFormula(theory->formulae,theory->W[formula],theory->prover);
  start = startRule;
  while (!EMPTY(theory->Reduct)) {
    for (rule = start; rule <= endRule; rule++){
      if (MEMBER(rule,theory->Reduct) && CanFire(theory,rule)){
	if (!MEMBER(rule,candidate))
	  return FALSE;
	if (!DoUpdate(theory,rule))
	  return FALSE;
	rulesFired++;
	RemoveElement(theory->Reduct,rule);
	break;
      } /* if */
    } /* for */
    if (rule > endRule) /* No rule fired */
      break;
    while (start <= endRule && !MEMBER(start,theory->Reduct))
      start++;
  } /* while */
  if (returnValue != FALSE) {
    /* Compare against candidate */
    if (rulesFired == candidateSize)  /* They must be identical */
      return TRUE;
    else    /* Produced a subset of candidate, so can prune */
      return DONT_KNOW;
  }
  return returnValue;
}

/* Initialize a theory, reading it in from the file specified by file */

void InitializeSimpleDefaultTheory(SimpleDefaultTheory *theory, char *file)
{
  FILE *knowledge, *defaults;
  int *tokenString;
  int numberDefaults, numberFormulas, numberAssertions, tokenIndex = 0,
	index,numberAtoms;
  long knowSize, defSize;
  
  knowledge = OpenDescribedFile(file, KNOWLEDGE_MARKER,
      KNOWLEDGE_FILE_EXTENSION, &knowSize);
  defaults = OpenDescribedFile(file, DEFAULT_MARKER, DEFAULT_FILE_EXTENSION,
      &defSize);
  tokenString = ParseTheory(knowledge, defaults, knowSize+defSize,
      &numberFormulas, &numberDefaults, &numberAssertions);
  fclose(knowledge);
  fclose(defaults);
  theory->formulae = (FormulaPtr) malloc(sizeof(Formula) * (numberFormulas+1));
  /* No, this isn't particularly clean. */
  theory->formulae[0] = (Formula) numberFormulas;
  theory->DefaultTable = (DefaultRulePtr) malloc(sizeof(DefaultRule) *
      (numberDefaults+1));
  theory->DefaultTable[0].prerequisite = numberDefaults;
  theory->W = (int *) malloc(sizeof(int) * (numberAssertions+1));
  theory->W[0] = numberAssertions;
  theory->Reduct = CreateSet(numberDefaults,0);
  numberAtoms = hashTableSize();
  theory->prover = (TableProver) malloc(sizeof(Boolean)*(numberAtoms+1));
  theory->prover[0] = (Boolean) numberAtoms;
  for (index = 1; index <= numberAtoms; index++){
     theory->prover[index] = DONT_KNOW;
  }
  DISPLAY(printf("%d atoms, %d formulas found total.\n", numberAtoms,
      numberFormulas));
  DISPLAY(printf("%d defaults found. %d formulas found in W.\n",
      numberDefaults, numberAssertions));
  LoadKnowledge(theory, tokenString, numberAssertions, &tokenIndex);
  LoadDefaults(theory, tokenString, numberDefaults, &tokenIndex);
  DISPLAY(printf("Input theory successfully loaded.\n"));
  LoadStratification(theory, file);
}

/* skip whitespace */

char readWhitespace(FILE *file)
{
  char currentC;
  
  do {
	 currentC = getc(file);
  }while (currentC == ' ' || currentC == '\t' || currentC == '\n');
  return currentC;
}

/* Extract a filename (indicated by the marker) from file and return it */

char *getName(FILE *file, char marker, int filenamesize)
{
  char currentC;
  char *name;
  int location = 0;

  name = (char *) malloc(filenamesize+1);
  currentC = getc(file);
  while (currentC != EOF) /* Read next line */ {
    /* Read any white space */
    currentC = readWhitespace(file);
    if (currentC == marker) /* We've got it! */ {
     /* Read past '=' */
     do {
	currentC = getc(file);
     } while (currentC != '=');
     /* Read whitespace */
     currentC = readWhitespace(file);
     while (location<filenamesize && !isspace(currentC) && !iscntrl(currentC)){
	name[location++] = currentC;
	currentC = getc(file);
     }
  } else /* read till end of line or end of file, whichever comes first */
      while (currentC != '\n' && currentC != EOF)
	 currentC = getc(file);
 }
  if (location == 0) {
    free(name);
    return NULL;
 }
  name[location] = '\0';
  return name;
}

/* Open the file indicated by marker and named in file. Set size to the file
size. */

FILE *OpenDescribedFile(char *file, char marker, char *extension, long *size)
{
  char *describedName, *descriptorName;
  FILE *descFile, *openFile;
  int length = strlen(file);
  
  descriptorName = (char *) malloc(length+5);
  strcpy(descriptorName, file);
  if (file[length-1] != '.')
     strcat(descriptorName,".");
  strcat(descriptorName, INFORMATION_FILE_EXTENSION);
  if ((descFile = fopen(descriptorName,"r")) == NULL)
     fatal_error("Unable to open file.");
  describedName = getName(descFile, marker, length+5);
  fclose(descFile);
  if (describedName == NULL)
     fatal_error("Description file missing filename");
  strcat(describedName, extension);
  if ((openFile = fopen(describedName, "r")) == NULL)
     fatal_error("Unable to open input theory.");
  else
     DISPLAY(printf("Opening file %s.\n", describedName));
  if ((*size = FileSize(describedName)) < 0)
     fatal_error("Unable to get file information.");
  return openFile;
}

int countSpecifiedToken(int *tokenString, int startIndex, int endToken,
    int tokenToCount)
{
  int numberToken = 0;
  while (tokenString[startIndex] != endToken && tokenString[startIndex] != 0) {
  /* Not at end of formula yet */
    if (tokenString[startIndex] == tokenToCount)
      numberToken++;
    startIndex++;
 }
  return numberToken;
}

/* Given a theory, a string of tokens, the starting place in the string and the
token indicating the end of the formula, parse the next formula, putting it
into theory->formulae[currentFormula] */

int CreateFormulaFromString(SimpleDefaultTheory *theory, int *tokenString,
    int *index, int endToken)
{
  int currentIndex, numberLiterals, currentLiteral;
  Literal newLiteral;
  
  currentIndex = *index;
  /* Determine number of literals in the formula */
  numberLiterals = countSpecifiedToken(tokenString,currentIndex,endToken,VAR);
  if (numberLiterals > 0){
     /* Increment the global */
     currentFormula++;
     theory->formulae[currentFormula] = (Formula) malloc(sizeof(Literal) *
	 (numberLiterals+5));
     theory->formulae[currentFormula][0] = numberLiterals;
     if (countSpecifiedToken(tokenString, *index, endToken, OR_SYMB) == 0)
	theory->formulae[currentFormula][numberLiterals+1] = TRUE;
     else
	theory->formulae[currentFormula][numberLiterals+1] = FALSE;
     theory->formulae[currentFormula][numberLiterals+2] = 0;
     theory->formulae[currentFormula][numberLiterals+3] = 0;
     currentLiteral = 1;
     while (tokenString[currentIndex]!=endToken && tokenString[currentIndex]!=0) {
      /* Not at end of formula yet */
      if (tokenString[currentIndex] == VAR)  /* Always looking for a VAR */ {
	newLiteral = (tokenString[currentIndex-1] == NOT_SYMB)?
	  -(tokenString[currentIndex+1]):tokenString[currentIndex+1];
	theory->formulae[currentFormula][currentLiteral++] = newLiteral;
      }
       currentIndex++;
     }
  }
  *index = currentIndex+1;

  if (numberLiterals == 0)
     return 0;
  return currentFormula;
}

/* Load the assertions from the token string */

void LoadKnowledge(SimpleDefaultTheory *theory, int *tokenString,
    int numberAssertions, int *index)
{
  int assertion;

  if (numberAssertions < 1)
     return;
  for (assertion = 1; assertion <= theory->W[0]; assertion++)
     theory->W[assertion] = CreateFormulaFromString(theory, tokenString, index,
	 SEMI_COLON);
}

/* Load the defaults from the tokenString */

void LoadDefaults(SimpleDefaultTheory *theory, int *tokenString,
    int numberDefaults, int *startIndex)
{
  int defaultNum, numberJustifications, just;
  
  if (numberDefaults < 1)
     return;
  for (defaultNum = 1; defaultNum <= theory->DefaultTable[0].prerequisite;
      defaultNum++){
     DefaultRulePtr newRule = theory->DefaultTable+defaultNum;
     newRule->prerequisite =  CreateFormulaFromString(theory, tokenString,
						      startIndex, COLON); 
     if (countSpecifiedToken(tokenString, *startIndex, ENTAIL, VAR) == 0)
	 /* No justifications */
	numberJustifications = 0;
     else
	numberJustifications = countSpecifiedToken(tokenString, *startIndex,
	    ENTAIL, COMMA) + 1;
     newRule->justifications = (int *) malloc ((numberJustifications+1)
	 *sizeof(int));
     newRule->justifications[0] = numberJustifications;
     for (just = 1; just < numberJustifications; just++)
	newRule->justifications[just] = 
	      CreateFormulaFromString(theory,tokenString, startIndex, COMMA);
     if (numberJustifications > 0)
	newRule->justifications[numberJustifications] = 
	      CreateFormulaFromString(theory, tokenString, startIndex, ENTAIL);
     newRule->consequent = CreateFormulaFromString(theory,tokenString,
	 startIndex, SEMI_COLON);
  }
}

/* Make a system call to find out the file size of the named file */

long FileSize (char *filename)
{
  struct stat buffer;
  
  if (stat(filename, &buffer) == 0)
     return (long) buffer.st_size;
  return -1;
}

void LoadStratification(SimpleDefaultTheory *theory, char *file)
{
  char *fileName, currentChar;
  FILE *stratFile;
  int numberStrata,length,*tempArray,size, currentStratum;

  length = strlen(file);
  fileName = (char *) malloc(length + 5);
  strcpy(fileName, file);
  if (file[length-1] != '.')
     strcat(fileName,".");
  strcat(fileName, STRAT_FILE_EXTENSION);
  if ((stratFile = fopen(fileName,"r")) == NULL){
    DISPLAY(printf("No stratification file found. Trivial stratification assumed.\n"));
     /* Only need two places: one for size, one for only stratum */
     theory->strata = (int *) malloc(2*sizeof(int));
     theory->strata[0] = 1;
     theory->strata[1] = theory->DefaultTable[0].prerequisite;
     return;
  }
  /* Get file size in order to allocate temporary array */
  if ((size = FileSize(fileName)) < 0)
     DISPLAY(printf("Unable to read stratification file. Trivial stratification assumed.\n"));
  tempArray = (int *) malloc(size*sizeof(int));
  /* Make a count of the number of strata */
  numberStrata = 0;
  currentChar = getc(stratFile);
  while (!isdigit(currentChar) && (currentChar != EOF))
     currentChar = getc(stratFile);
  while (currentChar != EOF) /* Read next number */ {
    int multiplier = 1;
    numberStrata++;
    tempArray[numberStrata] = 0;
    /* Read in number (scanf is unreliable!) */
    do{
      tempArray[numberStrata] *= multiplier;
      tempArray[numberStrata] += currentChar - '0';
      multiplier *= 10;
      currentChar = getc(stratFile);
    } while (isdigit(currentChar) && (currentChar != EOF));
    while (!isdigit(currentChar) && (currentChar != EOF))
      currentChar = getc(stratFile);
 }
  theory->strata = (int *) malloc((numberStrata+1)*sizeof(int));
  theory->strata[0] = numberStrata;
  for (currentStratum = 1; currentStratum <= numberStrata; currentStratum++)
     theory->strata[currentStratum] = tempArray[currentStratum];
  DISPLAY(printf("Theory has %d strata.\n", numberStrata));
}

#endif /* _SIMPLEDEFAULTTHEORY_C */
