/*************************************************************************
 *									 *
 *	 Yap Prolog 							 *
 *									 *
 *	Yap Prolog Was Developed At Nccup - Universidade Do Porto	 *
 *									 *
 * Copyright L.Damas, V.S.Costa And Universidade Do Porto 1985-1997	 *
 *									 *
 **************************************************************************
 *									 *
 * File:		errors.c						 *
 * Last Rev:								 *
 * Mods:									 *
 * Comments:	Yap'S error handlers					 *
 *									 *
 *************************************************************************/

#include "absmi.h"
#include "yapio.h"
#if HAVE_STDARG_H
#include <stdarg.h>
#endif
#include <stdlib.h>
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#if HAVE_STRING_H
#include <string.h>
#endif
#include "Foreign.h"


#if DEBUG
void Yap_PrintPredName(PredEntry *ap) {
  CACHE_REGS
  Term tmod = ap->ModuleOfPred;
  if (!tmod)
    tmod = TermProlog;
#if THREADS
  Yap_DebugPlWrite(MkIntegerTerm(worker_id));
  Yap_DebugPutc(stderr, ' ');
#endif
  Yap_DebugPutc(stderr, '>');
  Yap_DebugPutc(stderr, '\t');
  Yap_DebugPlWrite(tmod);
  Yap_DebugPutc(stderr, ':');
  if (ap->ModuleOfPred == IDB_MODULE) {
    Term t = Deref(ARG1);
    if (IsAtomTerm(t)) {
      Yap_DebugPlWrite(t);
    } else if (IsIntegerTerm(t)) {
      Yap_DebugPlWrite(t);
    } else {
      Functor f = FunctorOfTerm(t);
      Atom At = NameOfFunctor(f);
      Yap_DebugPlWrite(MkAtomTerm(At));
      Yap_DebugPutc(stderr, '/');
      Yap_DebugPlWrite(MkIntegerTerm(ArityOfFunctor(f)));
    }
  } else {
    if (ap->ArityOfPE == 0) {
      Atom At = (Atom)ap->FunctorOfPred;
      Yap_DebugPlWrite(MkAtomTerm(At));
    } else {
      Functor f = ap->FunctorOfPred;
      Atom At = NameOfFunctor(f);
      Yap_DebugPlWrite(MkAtomTerm(At));
      Yap_DebugPutc(stderr, '/');
      Yap_DebugPlWrite(MkIntegerTerm(ArityOfFunctor(f)));
    }
  }
  char s[1024];
  if (ap->PredFlags & StandardPredFlag)
    fprintf(stderr, "S");
  if (ap->PredFlags & CPredFlag)
    fprintf(stderr, "C");
  if (ap->PredFlags & UserCPredFlag)
    fprintf(stderr, "U");
  if (ap->PredFlags & SyncPredFlag)
    fprintf(stderr, "Y");
  if (ap->PredFlags & LogUpdatePredFlag)
    fprintf(stderr, "Y");
  if (ap->PredFlags & HiddenPredFlag)
    fprintf(stderr, "H");
  sprintf(s, "   %llx\n", ap->PredFlags);
  Yap_DebugPuts(stderr, s);
}
#endif

bool Yap_Warning(const char *s, ...) {
  CACHE_REGS
  va_list ap;
  PredEntry *pred;
  bool rc;
  Term ts[2];
  const char *format;
  char tmpbuf[MAXPATHLEN];
  
  if (LOCAL_within_print_message) {
    /* error within error */
    fprintf(stderr, "%% WARNING WITHIN WARNING\n");
    Yap_RestartYap(1);
  }
  LOCAL_DoingUndefp = true;
  LOCAL_within_print_message = true;
  pred =  RepPredProp(PredPropByFunc(FunctorPrintMessage, PROLOG_MODULE)); // PROCEDURE_print_message2
  if (pred->OpcodeOfPred == UNDEF_OPCODE) {
    //fprintf(stderr, "warning message:\n");
    //Yap_DebugPlWrite(twarning);
    //fprintf(stderr, "\n");
    LOCAL_DoingUndefp = false;
    LOCAL_within_print_message = false;
    return true;
  }
  
  va_start(ap, s);
  format = va_arg(ap, char *);
  if (format != NULL) {
#if HAVE_VSNPRINTF
    vsnprintf(tmpbuf, MAXPATHLEN-1, format, ap);
#else
    (void)vsprintf(tmpbuf, format, ap);
#endif
  } else
    return false;
  va_end(ap);
  ts[0] = MkAtomTerm(AtomWarning);
  ts[1] = MkAtomTerm(Yap_LookupAtom(tmpbuf));
  rc = Yap_execute_pred(pred, ts, true PASS_REGS);
  LOCAL_within_print_message = false;
  return rc;
}

bool Yap_PrintWarning(Term twarning) {
  CACHE_REGS
  PredEntry *pred = RepPredProp(PredPropByFunc(
                                               FunctorPrintMessage, PROLOG_MODULE)); // PROCEDURE_print_message2;
  bool rc;
  Term ts[2];
  
  if (LOCAL_within_print_message) {
    /* error within error */
    fprintf(stderr, "%% WARNING WITHIN WARNING\n");
    Yap_RestartYap(1);
  }
  LOCAL_DoingUndefp = true;
  LOCAL_within_print_message = true;
  if (pred->OpcodeOfPred == UNDEF_OPCODE) {
    //fprintf(stderr, "warning message:\n");
    //Yap_DebugPlWrite(twarning);
    //fprintf(stderr, "\n");
    LOCAL_DoingUndefp = false;
    LOCAL_within_print_message = false;
    return true;
  }
  ts[0] = MkAtomTerm(AtomWarning);
  ts[1] = twarning;
  rc = Yap_execute_pred(pred, ts, true PASS_REGS);
  LOCAL_within_print_message = false;
  LOCAL_DoingUndefp = false;
  return rc;
}

int Yap_HandleError(const char *s, ...) {
  CACHE_REGS
  yap_error_number err = LOCAL_Error_TYPE;
  const char *serr;
  
  LOCAL_Error_TYPE = YAP_NO_ERROR;
  if (LOCAL_ErrorMessage) {
    serr = LOCAL_ErrorMessage;
  } else {
    serr = s;
  }
  switch (err) {
    case RESOURCE_ERROR_STACK:
      if (!Yap_gc(2, ENV, gc_P(P, CP))) {
        Yap_Error(RESOURCE_ERROR_STACK, ARG1, serr);
        return (FALSE);
      }
      return TRUE;
    case RESOURCE_ERROR_AUXILIARY_STACK:
      if (LOCAL_MAX_SIZE < (char *)AuxSp - AuxBase) {
        LOCAL_MAX_SIZE += 1024;
      }
      if (!Yap_ExpandPreAllocCodeSpace(0, NULL, TRUE)) {
        /* crash in flames */
        Yap_Error(RESOURCE_ERROR_AUXILIARY_STACK, ARG1, serr);
        return FALSE;
      }
      return TRUE;
    case RESOURCE_ERROR_HEAP:
      if (!Yap_growheap(FALSE, 0, NULL)) {
        Yap_Error(RESOURCE_ERROR_HEAP, ARG2, serr);
        return FALSE;
      }
    default:
      Yap_Error(err, LOCAL_Error_Term, serr);
      return (FALSE);
  }
}

int Yap_SWIHandleError(const char *s, ...) {
  CACHE_REGS
  yap_error_number err = LOCAL_Error_TYPE;
  char *serr;
  
  LOCAL_Error_TYPE = YAP_NO_ERROR;
  if (LOCAL_ErrorMessage) {
    serr = LOCAL_ErrorMessage;
  } else {
    serr = (char *)s;
  }
  switch (err) {
    case RESOURCE_ERROR_STACK:
      if (!Yap_gc(2, ENV, gc_P(P, CP))) {
        Yap_Error(RESOURCE_ERROR_STACK, TermNil, serr);
        return (FALSE);
      }
      return TRUE;
    case RESOURCE_ERROR_AUXILIARY_STACK:
      if (LOCAL_MAX_SIZE < (char *)AuxSp - AuxBase) {
        LOCAL_MAX_SIZE += 1024;
      }
      if (!Yap_ExpandPreAllocCodeSpace(0, NULL, TRUE)) {
        /* crash in flames */
        Yap_Error(RESOURCE_ERROR_AUXILIARY_STACK, ARG1, serr);
        return FALSE;
      }
      return TRUE;
    case RESOURCE_ERROR_HEAP:
      if (!Yap_growheap(FALSE, 0, NULL)) {
        Yap_Error(RESOURCE_ERROR_HEAP, ARG2, serr);
        return FALSE;
      }
    default:
      Yap_Error(err, LOCAL_Error_Term, serr);
      return (FALSE);
  }
}

void Yap_RestartYap(int flag) {
  CACHE_REGS
#if PUSH_REGS
  restore_absmi_regs(&Yap_standard_regs);
#endif
  siglongjmp(LOCAL_RestartEnv, 1);
}

static void error_exit_yap(int value) {
  CACHE_REGS
  if (!(LOCAL_PrologMode & BootMode)) {
    
#if DEBUG
#endif
  }
  fprintf(stderr, "\n   Exiting ....\n");
  Yap_exit(value);
}

/* This needs to be a static because I can't trust the stack (WIN32), and
 I can't trust the Yap stacks  (error) */
#define YAP_BUF_SIZE 512

static char tmpbuf[YAP_BUF_SIZE];

// error classes: based on OSI errors.
//
//     - The extra argument says whether there different instances
//
//     - Events are treated within the same pipeline as errors.
//

#undef BEGIN_ERROR_CLASSES
#undef ECLASS
#undef END_ERROR_CLASSES
#undef BEGIN_ERRORS
#undef E0
#undef E
#undef E2
#undef END_ERRORS

#define BEGIN_ERROR_CLASSES()					\
static Term mkerrorct(yap_error_class_number c, Term *ts) {	\
switch (c) {

#define ECLASS(CL, A, B)						\
case CL:								\
if (A == 0)							\
return MkAtomTerm(Yap_LookupAtom(A));				\
else {								\
return Yap_MkApplTerm(Yap_MkFunctor(Yap_LookupAtom(A), B), B, ts); \
}

#define END_ERROR_CLASSES()			\
}						\
}

#define BEGIN_ERRORS()					\
static Term mkerrort(yap_error_number e, Term *ts) {	\
switch (e) {

#define E0(A, B)				\
case A:					\
return mkerrorct(B, ts);

#define E(A, B, C)				\
case A:					\
ts -= 1;				\
ts[0] = MkAtomTerm(Yap_LookupAtom(C));	\
return mkerrorct(B, ts);

#define E2(A, B, C, D)				\
case A:					\
ts -= 2;				\
ts[0] = MkAtomTerm(Yap_LookupAtom(C));	\
ts[1] = MkAtomTerm(Yap_LookupAtom(D));	\
return mkerrorct(B, ts);

#define END_ERRORS()				\
}						\
}

#include "YapErrors.h"

/**
 * @brief Yap_Error
 *   This function handles errors in the C code. Check errors.yap for the
 *corresponding Prolog code.
 *
 * @param file      C source
 * @param function  C function
 * @param lineno    C exact line
 * @param type      the error ID (in YAP this is a single integer)
 * @param where     the culprit
 * @return usually FAILCODE
 *
 * In a good day, the error handler's job is to generate a throw. This includes:
 *   - constructing an ISO style error term;
 *   - constructing a list with all available info on the bug
 *   - generating the throw
 *   - forcing backtracking in order to restart.
 *
 * In a bad day, it has to deal with OOM, abort, and errors within errorts.
 *
 * The list includes the following options:
 *   + c=c(file, line, function): where the bug was detected;
 *
 *   + e=p(mod, name, arity, cl, file, lin): where the code was entered;
 *
 *   + p=p(mod, name, arity, cl, file, line): the prolog procedure that caused the bug,
 *and optionally,
 *
 *   + g=g(Goal):   the goal that created this mess
 *
 *   + i=i(Comment): an user-written comment on this bug.
 */
yamop *Yap_Error__(const char *file, const char *function, int lineno,
                   yap_error_number type, Term where, ...) {
  CACHE_REGS
  va_list ap;
  CELL nt[3];
  Functor fun;
  bool serious;
  Term tf, error_t, comment, culprit;
  char *format;
  char s[MAXPATHLEN];
  
  /* disallow recursive error handling */
  if (LOCAL_PrologMode & InErrorMode) {
    fprintf(stderr, "%% ERROR WITHIN ERROR %d: %s\n", LOCAL_CurrentError,
            tmpbuf);
    Yap_RestartYap(1);
  }
  LOCAL_PrologMode |= InErrorMode;
  LOCAL_Error_TYPE = YAP_NO_ERROR;
  Yap_ClearExs();
  if (where == 0L) {
    where = TermNil;
  }
  // first, obtain current location
  sprintf(LOCAL_FileNameBuf, "%s:%d in C-function %s ", file, lineno, function);
  tf = MkAtomTerm(Yap_LookupAtom(LOCAL_FileNameBuf));
#if DEBUG_STRICT
  if (Yap_heap_regs && !(LOCAL_PrologMode & BootMode))
    fprintf(stderr, "***** Processing Error %d (%lx,%x) %s***\n", type,
            (unsigned long int)LOCAL_Signals, LOCAL_PrologMode, format);
  else
    fprintf(stderr, "***** Processing Error %d (%x) %s***\n", type,
            LOCAL_PrologMode, format);
#endif
  if (type == INTERRUPT_EVENT) {
    fprintf(stderr, "%% YAP exiting: cannot handle signal %d\n",
            (int)IntOfTerm(where));
    LOCAL_PrologMode &= ~InErrorMode;
    Yap_exit(1);
  }
  if (LOCAL_within_print_message) {
    /* error within error */
    fprintf(stderr, "%% ERROR WITHIN WARNING %d: %s\n", LOCAL_CurrentError,
            tmpbuf);
    LOCAL_PrologMode &= ~InErrorMode;
    Yap_exit(1);
  }
  if (LOCAL_PrologMode == BootMode) {
    Yap_detect_bug_location(P, FIND_PRED_FROM_ANYWHERE, (char *)HR, 256);
    fprintf(stderr, "%%\n%% PC: %s\n", (char *)HR);
    Yap_detect_bug_location(CP, FIND_PRED_FROM_ANYWHERE, (char *)HR, 256);
    fprintf(stderr, "%%   Continuation: %s\n", (char *)HR);
    error_exit_yap(1);
  }
     va_start(ap, where);
    format = va_arg(ap, char *);
    if (format != NULL) {
#if HAVE_VSNPRINTF
      (void)vsnprintf(s, MAXPATHLEN-1, format, ap);
#else
      (void)vsprintf(s, format, ap);
#endif
      //fprintf(stderr, "warning: ");
      comment = MkAtomTerm(Yap_LookupAtom(s));
    } else if (LOCAL_ErrorSay && LOCAL_ErrorSay[0])
      comment  = MkAtomTerm(Yap_LookupAtom( LOCAL_ErrorSay ) );
    else
      comment = TermNil;
    va_end(ap);
    if (P == (yamop *)(FAILCODE)) {
      LOCAL_PrologMode &= ~InErrorMode;
      return P;
    }
  /* PURE_ABORT may not have set where correctly, BootMode may not have the data
   * terms ready */
  if (type == ABORT_EVENT || LOCAL_PrologMode & BootMode) {
    where = TermNil;
    LOCAL_PrologMode &= ~AbortMode;
    LOCAL_CurrentError = type;
    LOCAL_PrologMode |= InErrorMode;
    /* make sure failure will be seen at next port */
    // no need to lock & unlock
    if (LOCAL_PrologMode & AsyncIntMode)
      Yap_signal(YAP_FAIL_SIGNAL);
    P = FAILCODE;
  } else {
    if (IsVarTerm(where)) {
      /* we must be careful someone gave us a copy to a local variable */
      Term t = MkVarTerm();
      Yap_unify(t, where);
      where = Deref(where);
    }
    /* Exit Abort Mode, if we were there */
    LOCAL_PrologMode &= ~AbortMode;
    LOCAL_CurrentError = type;
    LOCAL_PrologMode |= InErrorMode;
    if (!(where = Yap_CopyTerm(where))) {
      where = TermNil;
    }
  }

  if (LOCAL_PrologMode & BootMode) {
    /* crash in flames! */
    fprintf(stderr, "%% YAP Fatal Error: %s exiting....\n", tmpbuf);
    error_exit_yap(1);
  }
#ifdef DEBUG
  // DumpActiveGoals( USES_REGS1 );
#endif /* DEBUG */
  switch (type) {
    case SYSTEM_ERROR_INTERNAL: {
      fprintf(stderr, "%% Internal YAP Error: %s exiting....\n", tmpbuf);
      serious = TRUE;
      if (LOCAL_PrologMode & BootMode) {
        fprintf(stderr, "%% YAP crashed while booting %s\n", tmpbuf);
      } else {
        Yap_detect_bug_location(P, FIND_PRED_FROM_ANYWHERE, tmpbuf, YAP_BUF_SIZE);
        fprintf(stderr, "%% Bug found while executing %s\n", tmpbuf);
      }
      error_exit_yap(1);
    }
    case SYSTEM_ERROR_FATAL: {
      fprintf(stderr, "%% Fatal YAP Error: %s exiting....\n", tmpbuf);
      error_exit_yap(1);
    }
    case INTERRUPT_EVENT: {
      error_exit_yap(1);
    }
    case ABORT_EVENT:
      nt[0] = MkAtomTerm(AtomDAbort);
      fun = FunctorVar;
      serious = TRUE;
      break;
    case CALL_COUNTER_UNDERFLOW_EVENT:
      /* Do a long jump */
      LOCAL_ReductionsCounterOn = FALSE;
      LOCAL_PredEntriesCounterOn = FALSE;
      LOCAL_RetriesCounterOn = FALSE;
      Yap_JumpToEnv(MkAtomTerm(AtomCallCounter));
      P = (yamop *)FAILCODE;
      LOCAL_PrologMode &= ~InErrorMode;
      return (P);
    case PRED_ENTRY_COUNTER_UNDERFLOW_EVENT:
      /* Do a long jump */
      LOCAL_ReductionsCounterOn = FALSE;
      LOCAL_PredEntriesCounterOn = FALSE;
      LOCAL_RetriesCounterOn = FALSE;
      Yap_JumpToEnv(MkAtomTerm(AtomCallAndRetryCounter));
      P = (yamop *)FAILCODE;
      LOCAL_PrologMode &= ~InErrorMode;
      return (P);
    case RETRY_COUNTER_UNDERFLOW_EVENT:
      /* Do a long jump */
      LOCAL_ReductionsCounterOn = FALSE;
      LOCAL_PredEntriesCounterOn = FALSE;
      LOCAL_RetriesCounterOn = FALSE;
      Yap_JumpToEnv(MkAtomTerm(AtomRetryCounter));
      P = (yamop *)FAILCODE;
      LOCAL_PrologMode &= ~InErrorMode;
      return (P);
    default: {
      Term ts[3];
      ts[2] = where;
      nt[0] = mkerrort(type, ts + 2);
    }
  }
  if (type != ABORT_EVENT) {
    /* This is used by some complex procedures to detect there was an error */
    if (IsAtomTerm(nt[0])) {
      strncpy(LOCAL_ErrorSay, (char *)RepAtom(AtomOfTerm(nt[0]))->StrOfAE,
              MAX_ERROR_MSG_SIZE);
      LOCAL_ErrorMessage = LOCAL_ErrorSay;
    } else {
      strncpy(LOCAL_ErrorSay,
              (char *)RepAtom(NameOfFunctor(FunctorOfTerm(nt[0])))->StrOfAE,
              MAX_ERROR_MSG_SIZE);
      LOCAL_ErrorMessage = LOCAL_ErrorSay;
    }
  }
  switch (type) {
    case RESOURCE_ERROR_HEAP:
    case RESOURCE_ERROR_STACK:
    case RESOURCE_ERROR_TRAIL:
      comment  = MkAtomTerm(Yap_LookupAtom(tmpbuf));
    default:
      nt[1] = TermNil;
      if (comment != TermNil)
        nt[1] = MkPairTerm(MkPairTerm(MkAtomTerm(Yap_LookupAtom("i")),comment), nt[1]);
      if (file && function) {
        Term ts[3], t3;
        ts[0] = MkAtomTerm(Yap_LookupAtom(file));
        ts[1] = MkIntegerTerm(lineno);
        ts[2] = MkAtomTerm(Yap_LookupAtom(function));
        t3 = Yap_MkApplTerm(Yap_MkFunctor(Yap_LookupAtom("c"),3),3,ts);
        nt[1] = MkPairTerm(MkPairTerm(MkAtomTerm(Yap_LookupAtom("c")),t3), nt[1]);
      }
      if ((culprit=Yap_pc_location( P, B, ENV)) != TermNil ) {
        nt[1] = MkPairTerm(MkPairTerm(MkAtomTerm(Yap_LookupAtom("p")),culprit), nt[1]);
      }
      if ((culprit=Yap_env_location( CP, B, ENV, 0)) != TermNil ) {
        nt[1] = MkPairTerm(MkPairTerm(MkAtomTerm(Yap_LookupAtom("e")),culprit), nt[1]);
      }
  }
  /* disable active signals at this point */
  LOCAL_Signals = 0;
  CalculateStackGap(PASS_REGS1);
  LOCAL_PrologMode &= ~InErrorMode;
  /* we might be in the middle of a critical region */
  if (LOCAL_InterruptsDisabled) {
    LOCAL_InterruptsDisabled = 0;
    LOCAL_UncaughtThrow = TRUE;
    Yap_RestartYap(1);
  }
#if DEBUG
  //    DumpActiveGoals( PASS_REGS1 );
#endif
  /* wait if we we are in user code,
   it's up to her to decide */
  fun = FunctorError;
  if (LOCAL_PrologMode & UserCCallMode) {
    error_t = Yap_MkApplTerm(fun, 2, nt);
    if (!(EX = Yap_StoreTermInDB(error_t, 2))) {
      /* fat chance */
      Yap_RestartYap(1);
    }
  } else {
    if (type == ABORT_EVENT) {
      error_t = MkAtomTerm(AtomDAbort);
    } else {
      error_t = Yap_MkApplTerm(fun, 2, nt);
      
    }
    Yap_JumpToEnv(error_t);
    P = (yamop *)FAILCODE;
  }
  
  LOCAL_PrologMode &= ~InErrorMode;
  return P;
  
}