#include "internal.h"
#include "parse.h"
#include "print.h"
#include "profile.h"
#include "resize.h"

#include <ctype.h>
#include <inttypes.h>

static int
next (file * file, uint64_t * lineno_ptr)
{
  int ch = kissat_getc (file);
  if (ch == '\n')
    *lineno_ptr += 1;
  return ch;
}

#define NEXT() \
  next (file, lineno_ptr)

static const char *
nonl (int ch, const char *str, uint64_t * lineno_ptr)
{
  if (ch == '\n')
    {
      assert (*lineno_ptr > 1);
      *lineno_ptr -= 1;
    }
  return str;
}

#define TRY_RELAXED_PARSING "(try '--relaxed' parsing)"

static const char *
parse_dimacs (kissat * solver, strictness strict,
	      file * file, uint64_t * lineno_ptr, int *max_var_ptr)
{
  *lineno_ptr = 1;
  bool first = true;
  int ch;
  for (;;)
    {
      ch = NEXT ();
      if (ch == 'p')
	break;
      else if (ch == EOF)
	{
	  if (first)
	    return "empty file";
	  else
	    return "end-of-file before header";
	}
      first = false;
      if (ch == '\r')
	{
	  ch = NEXT ();
	  if (ch != '\n')
	    return "expected new-line after carriage-return";
	  if (strict == PEDANTIC_PARSING)
	    return nonl (ch, "unexpected empty line", lineno_ptr);
	}
      else if (ch == '\n')
	{
	  if (strict == PEDANTIC_PARSING)
	    return nonl (ch, "unexpected empty line", lineno_ptr);
	}
      else if (ch == 'c')
	{
	START:
	  ch = NEXT ();
	  if (ch == '\n')
	    continue;
	  else if (ch == '\r')
	    {
	      ch = NEXT ();
	      if (ch != '\n')
		return "expected new-line after carriage-return";
	      continue;
	    }
	  else if (ch == EOF)
	    return "end-of-file in header comment";
	  else if (ch == ' ' || ch == '\t')
	    goto START;
#if !defined(NOPTIONS) && !defined(NEMBEDDED)
	  else if (ch == '-' && GET_OPTION (embedded))
	    {
	      ch = NEXT ();
	      if (ch != '-')
		goto COMPLETE;

	      char name[32];
	      unsigned pos = 0;
	      while ('a' <= (ch = NEXT ()) && ch <= 'z')
		{
		  assert (pos < sizeof name);
		  name[pos++] = ch;
		  if (pos == sizeof name)
		    goto COMPLETE;
		}
	      if (ch == '\r')
		{
		  ch = NEXT ();
		  if (ch != '\n')
		    return "expected new-line after carriage-return";
		}
	      if (ch == '\n')
		continue;
	      if (ch != '=')
		goto COMPLETE;
	      assert (pos < sizeof name);
	      name[pos++] = 0;

	      pos = 0;
	      ch = NEXT ();
	      int sign;
	      if (ch == '-')
		{
		  ch = NEXT ();
		  sign = -1;
		}
	      else
		sign = 1;
	      if (!isdigit (ch))
		goto COMPLETE;
	      int arg = ch - '0';
	      while (isdigit (ch = NEXT ()))
		{
		  if (INT_MAX / 10 < arg)
		    goto COMPLETE;
		  arg *= 10;
		  int digit = ch - '0';
		  if (INT_MAX - digit < arg)
		    goto COMPLETE;
		  arg += digit;
		}
	      while (ch == ' ' || ch == '\t')
		ch = NEXT ();
	      if (ch == '\r')
		{
		  ch = NEXT ();
		  if (ch != '\n')
		    return "expected new-line after carriage-return";
		}
	      if (ch != '\n')
		goto COMPLETE;
	      arg *= sign;
	      const opt *opt = kissat_options_has (name);
	      if (opt)
		{
		  (void) kissat_options_set_opt (&solver->options, opt, arg);
		  kissat_verbose (solver,
				  "parsed embedded option '--%s=%d'",
				  name, arg);
		}
	      else
		kissat_warning (solver,
				"invalid embedded option '--%s=%d'",
				name, arg);
	      continue;
	    }
	  else
#endif
	    {
	      while ((ch = NEXT ()) != '\n')
#if !defined(NOPTIONS) && !defined(NEMBEDDED)
	      COMPLETE:
#endif
		if (ch == EOF)
		  return "end-of-file in header comment";
		else if (ch == '\r')
		  {
		    ch = NEXT ();
		    if (ch != '\n')
		      return "expected new-line after carriage-return";
		    break;
		  }
	    }
	}
      else
	return "expected 'c' or 'p' at start of line";
    }
  assert (ch == 'p');
  ch = NEXT ();
  if (ch != ' ')
    return nonl (ch, "expected space after 'p'", lineno_ptr);
  ch = NEXT ();
  if (strict != PEDANTIC_PARSING)
    {
      while (ch == ' ' || ch == '\t')
	ch = NEXT ();
    }
  if (ch != 'c')
    return nonl (ch, "expected 'c' after 'p '", lineno_ptr);
  ch = NEXT ();
  if (ch != 'n')
    return nonl (ch, "expected 'n' after 'p c'", lineno_ptr);
  ch = NEXT ();
  if (ch != 'f')
    return nonl (ch, "expected 'n' after 'p cn'", lineno_ptr);
  ch = NEXT ();
  if (ch != ' ')
    return nonl (ch, "expected space after 'p cnf'", lineno_ptr);
  ch = NEXT ();
  if (strict != PEDANTIC_PARSING)
    {
      while (ch == ' ' || ch == '\t')
	ch = NEXT ();
    }
  if (!isdigit (ch))
    return nonl (ch, "expected digit after 'p cnf '", lineno_ptr);
  int variables = ch - '0';
  while (isdigit (ch = NEXT ()))
    {
      if (EXTERNAL_MAX_VAR / 10 < variables)
	return "maximum variable too large";
      variables *= 10;
      const int digit = ch - '0';
      if (EXTERNAL_MAX_VAR - digit < variables)
	return "maximum variable too large";
      variables += digit;
    }
  if (ch == EOF)
    return "unexpected end-of-file while parsing maximum variable";
  if (ch == '\r')
    {
      ch = NEXT ();
      if (ch != '\n')
	return "expected new-line after carriage-return";
    }
  if (ch == '\n')
    return nonl (ch, "unexpected new-line after maximum variable",
		 lineno_ptr);
  if (ch != ' ')
    return "expected space after maximum variable";
  ch = NEXT ();
  if (strict != PEDANTIC_PARSING)
    {
      while (ch == ' ' || ch == '\t')
	ch = NEXT ();
    }
  if (!isdigit (ch))
    return "expected number of clauses after maximum variable";
  uint64_t clauses = ch - '0';
  while (isdigit (ch = NEXT ()))
    {
      if (UINT64_MAX / 10 < clauses)
	return "number of clauses too large";
      clauses *= 10;
      const int digit = ch - '0';
      if (UINT64_MAX - digit < clauses)
	return "number of clauses too large";
      clauses += digit;
    }
  if (ch == EOF)
    return "unexpected end-of-file while parsing number of clauses";
  if (strict != PEDANTIC_PARSING)
    {
      while (ch == ' ' || ch == '\t')
	ch = NEXT ();
    }
  if (ch == '\r')
    {
      ch = NEXT ();
      if (ch != '\n')
	return "expected new-line after carriage-return";
    }
  if (ch == EOF)
    return "unexpected end-of-file after parsing number of clauses";
  if (ch != '\n')
    return "expected new-line after parsing number of clauses";
//   kissat_message (solver,
// 		  "parsed 'p cnf %d %" PRIu64 "' header", variables, clauses);
  *max_var_ptr = variables;
  kissat_reserve (solver, variables);
  uint64_t parsed = 0;
  int lit = 0;
  for (;;)
    {
      ch = NEXT ();
      if (ch == ' ')
	continue;
      if (ch == '\t')
	continue;
      if (ch == '\n')
	continue;
      if (ch == '\r')
	{
	  ch = NEXT ();
	  if (ch != '\n')
	    return "expected new-line after carriage-return";
	  continue;
	}
      if (ch == 'c')
	{
	  while ((ch = NEXT ()) != '\n')
	    if (ch == EOF)
	      {
		if (strict != PEDANTIC_PARSING)
		  break;
		return "unexpected end-of-file in comment after header";
	      }
	  if (ch == EOF)
	    break;
	  continue;
	}
      if (ch == EOF)
	break;
      int sign;
      if (ch == '-')
	{
	  ch = NEXT ();
	  if (ch == EOF)
	    return "unexpected end-of-file after '-'";
	  if (ch == '\n')
	    return nonl (ch, "unexpected new-line after '-'", lineno_ptr);
	  if (!isdigit (ch))
	    return "expected digit after '-'";
	  if (ch == '0')
	    return "expected non-zero digit after '-'";
	  sign = -1;
	}
      else if (!isdigit (ch))
	return "expected digit or '-'";
      else
	sign = 1;
      assert (isdigit (ch));
      int idx = ch - '0';
      while (isdigit (ch = NEXT ()))
	{
	  if (EXTERNAL_MAX_VAR / 10 < idx)
	    return "variable index too large";
	  idx *= 10;
	  const int digit = ch - '0';
	  if (EXTERNAL_MAX_VAR - digit < idx)
	    return "variable index too large";
	  idx += digit;
	}
      if (ch == EOF)
	{
	  if (strict == PEDANTIC_PARSING)
	    {
	      if (idx)
		return "unexpected end-of-file after literal";
	      else
		return "unexpected end-of-file after trailing zero";
	    }
	}
      else if (ch == '\r')
	{
	  ch = NEXT ();
	  if (ch != '\n')
	    return "expected new-line after carriage-return";
	}
      else if (ch == 'c')
	{
	  while ((ch = NEXT ()) != '\n')
	    if (ch == EOF)
	      {
		if (strict != PEDANTIC_PARSING)
		  break;
		return "unexpected end-of-file in comment after literal";
	      }
	}
      else if (ch != ' ' && ch != '\t' && ch != '\n')
	return "expected white space after literal";
      if (strict != RELAXED_PARSING && idx > variables)
	return nonl (ch, "maximum variable index exceeded "
		     TRY_RELAXED_PARSING, lineno_ptr);
      if (idx)
	{
	  assert (sign == 1 || sign == -1);
	  assert (idx != INT_MIN);
	  lit = sign * idx;
	}
      else
	{
	  if (strict != RELAXED_PARSING && parsed == clauses)
	    return "too many clauses " TRY_RELAXED_PARSING;
	  parsed++;
	  lit = 0;
	}
      kissat_add (solver, lit);
    }
  if (lit)
    return "trailing zero missing";
  if (strict != RELAXED_PARSING && parsed < clauses)
    {
      if (parsed + 1 == clauses)
	return "one clause missing " TRY_RELAXED_PARSING;
      return "more than one clause missing " TRY_RELAXED_PARSING;
    }
  return 0;
}

const char *
kissat_parse_dimacs (kissat * solver,
		     strictness strict,
		     file * file, uint64_t * lineno_ptr, int *max_var_ptr)
{
  const char *res;
  START (parse);
  res = parse_dimacs (solver, strict, file, lineno_ptr, max_var_ptr);
  STOP (parse);
  return res;
}
