/*
 * Copyright (c) 2002, 2003, 2004 Jean-Yves Lefort
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of Jean-Yves Lefort nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include <stdarg.h>
#include <string.h>
#include <glib/gi18n.h>
#include <glib-object.h>
#include "sg-parser.h"
#include "sg-util.h"

#define EXPECT_EOF			(1 << 0)
#define EXPECT_SYMBOL			(1 << 1)
#define EXPECT_MINUS			(1 << 2)
#define EXPECT_VALUE			(1 << 3)
#define EXPECT_LEFT_CURLY		(1 << 4)
#define EXPECT_RIGHT_CURLY		(1 << 5)
#define EXPECT_SEMICOLON		(1 << 6)

/*** function declarations ***************************************************/

static int	sg_parser_expect_mask		(GTokenType	type);
static void	sg_parser_skip_statement	(SGParser	*parser);
static gboolean	sg_parser_statement_validate	(SGParserStatement *statement);

/*** implementation **********************************************************/

SGParser *
sg_parser_new (const char *filename, GError **err)
{
  SGParser *parser;
  GError *tmp_err = NULL;

  g_return_val_if_fail(filename != NULL, NULL);

  parser = g_new(SGParser, 1);

  if (! (parser->io_channel = g_io_channel_new_file(filename, "r", &tmp_err)))
    {
      g_set_error(err, 0, 0, _("unable to open %s: %s"), filename, tmp_err->message);
      g_error_free(tmp_err);
      g_free(parser);

      return NULL;
    }

  parser->filename = g_strdup(filename);
  parser->scanner = g_scanner_new(NULL);
  parser->scope_stack = NULL;
  
  parser->scanner->input_name = parser->filename;
  parser->scanner->user_data = parser;
  
  g_scanner_input_file(parser->scanner,
		       g_io_channel_unix_get_fd(parser->io_channel));

  return parser;
}

void
sg_parser_free (SGParser *parser)
{
  g_return_if_fail(parser != NULL);

  g_io_channel_shutdown(parser->io_channel, TRUE, NULL);
  g_io_channel_unref(parser->io_channel);

  g_free(parser->filename);
  g_scanner_destroy(parser->scanner);
  g_slist_free(parser->scope_stack);

  g_free(parser);
}

void
sg_parser_define (SGParser *parser, SGParserDefinition *definition)
{
  g_return_if_fail(parser != NULL);
  g_return_if_fail(definition != NULL);
  g_return_if_fail(definition->id != 0);
  g_return_if_fail(definition->name != NULL);
  
  g_scanner_scope_add_symbol(parser->scanner,
			     definition->parent,
			     definition->name,
			     definition);
}

void
sg_parser_definev (SGParser *parser, SGParserDefinition *definitionv)
{
  g_return_if_fail(parser != NULL);
  g_return_if_fail(definitionv != NULL);

  while (definitionv->name)
    sg_parser_define(parser, definitionv++);
}

/*
 * Returns a SGParserStatement object, or NULL if sold out.
 */
SGParserStatement *
sg_parser_get_statement (SGParser *parser)
{
  SGParserStatement *statement;
  GTokenType type;
  int expect;
  gboolean minus = FALSE;

  g_return_val_if_fail(parser != NULL, NULL);

 loop:
  statement = g_new0(SGParserStatement, 1);
  statement->parser = parser;
  expect = EXPECT_EOF | EXPECT_SYMBOL | EXPECT_MINUS | EXPECT_VALUE | EXPECT_RIGHT_CURLY;

  for (;;)
    {
      type = g_scanner_get_next_token(parser->scanner);

      if (type == G_TOKEN_EOF)
	{
	  if (SG_PARSER_SCOPE(parser) != 0)
	    g_scanner_warn(parser->scanner, _("EOF not in root scope"));
	  
	  sg_parser_statement_free(statement);
	  
	  return NULL;
	}
      
      if (! (expect & sg_parser_expect_mask(type)))
	{			/* G_TOKEN_ERROR also ends up here */
	  g_scanner_unexp_token(parser->scanner,
				G_TOKEN_NONE,
				NULL,
				"keyword",
				NULL,
				NULL,
				FALSE);

	  sg_parser_skip_statement(parser);
	  sg_parser_statement_free(statement);

	  goto loop;
	}

#define NO_MINUS		\
      {				\
	if (minus)		\
	  {			\
	    g_scanner_warn(parser->scanner, _("unexpected minus sign")); \
	    minus = FALSE;	\
	  }			\
      }
#define MINUS(var)		\
      {				\
	if (minus)		\
	  {			\
	    (var) = 0 - (var);	\
	    minus = FALSE;	\
	  }			\
      }
      
      switch (type)
	{
	case G_TOKEN_SYMBOL:
	  NO_MINUS;

	  statement->definition = parser->scanner->value.v_symbol;
	  
	  expect = EXPECT_VALUE | EXPECT_LEFT_CURLY | EXPECT_SEMICOLON;

	  if (statement->definition->value_type == G_TYPE_INT
	      || statement->definition->value_type == G_TYPE_DOUBLE)
	    expect |= EXPECT_MINUS;

	  break;
	  
	case '-':
	  NO_MINUS;
	  minus = TRUE;
	  break;
	  
	case G_TOKEN_IDENTIFIER:
	  {
	    gboolean v_gboolean;

	    NO_MINUS;

	    v_gboolean = ! strcmp(parser->scanner->value.v_identifier, "yes");

	    g_value_init(&statement->value, G_TYPE_BOOLEAN);
	    g_value_set_boolean(&statement->value, v_gboolean);

	    expect = EXPECT_LEFT_CURLY | EXPECT_SEMICOLON;
	  
	    break;
	  }

	case G_TOKEN_INT:
	  {
	    if (statement->definition && statement->definition->value_type == G_TYPE_BOOLEAN)
	      {
		/* support for old configuration files using integers instead of booleans */
		NO_MINUS;
		g_value_init(&statement->value, G_TYPE_BOOLEAN);
		g_value_set_boolean(&statement->value, parser->scanner->value.v_int);
	      }
	    else if (statement->definition && statement->definition->value_type == G_TYPE_UINT)
	      {
		NO_MINUS;
		g_value_init(&statement->value, G_TYPE_UINT);
		g_value_set_uint(&statement->value, parser->scanner->value.v_int);
	      }
	    else
	      {
		int v_int = parser->scanner->value.v_int;

		MINUS(v_int);

		g_value_init(&statement->value, G_TYPE_INT);
		g_value_set_int(&statement->value, v_int);
	      }
	    
	    expect = EXPECT_LEFT_CURLY | EXPECT_SEMICOLON;
	  
	    break;
	  }

	case G_TOKEN_FLOAT:
	  {
	    double v_double = parser->scanner->value.v_float;

	    MINUS(v_double);

	    g_value_init(&statement->value, G_TYPE_DOUBLE);
	    g_value_set_double(&statement->value, v_double);
	  
	    expect = EXPECT_LEFT_CURLY | EXPECT_SEMICOLON;
	  
	    break;
	  }

	case G_TOKEN_STRING:
	  {
	    char *unescaped;

	    NO_MINUS;

	    unescaped = *parser->scanner->value.v_string
	      ? g_strcompress(parser->scanner->value.v_string)
	      : NULL;

	    g_value_init(&statement->value, G_TYPE_STRING);
	    g_value_set_string(&statement->value, unescaped);
	    g_free(unescaped);
	    
	    expect = EXPECT_LEFT_CURLY | EXPECT_SEMICOLON;
	  
	    break;
	  }

	case G_TOKEN_LEFT_CURLY:
	case G_TOKEN_RIGHT_CURLY:
	case ';':
	  NO_MINUS;

	  statement->terminator = type;
	  
	  break;
	  
	default:			/* only to suppress compiler warnings */
	  break;
	}
      
#undef NO_MINUS
#undef MINUS

      if (statement->terminator)
	{			/* got a complete statement */
	  if (sg_parser_statement_validate(statement))
	    break;		/* statement ok, exit the loop */
	  else
	    {
	      sg_parser_statement_free(statement);
	      goto loop;
	    }
	}
    }

  return statement;
}

/*
 * Modifies the parser's state (scoping) according to a statement.
 */
void
sg_parser_statement_evaluate (SGParserStatement *statement)
{
  int scope;

  g_return_if_fail(statement != NULL);

  if (statement->terminator == '{')
    {
      g_return_if_fail(statement->definition != NULL);

      scope = statement->definition->id;
      sg_slist_push(&statement->parser->scope_stack, GINT_TO_POINTER(scope));
      g_scanner_set_scope(statement->parser->scanner, scope);
    }
  else if (statement->terminator == '}')
    {
      sg_slist_pop(&statement->parser->scope_stack);
      scope = GPOINTER_TO_INT(sg_slist_peek(statement->parser->scope_stack));
      g_scanner_set_scope(statement->parser->scanner, scope);
    }
}

void
sg_parser_statement_free (SGParserStatement *statement)
{
  g_return_if_fail(statement != NULL);

  if (G_IS_VALUE(&statement->value))
    g_value_unset(&statement->value);

  g_free(statement);
}

static int
sg_parser_expect_mask (GTokenType type)
{
  switch (type)
    {
    case G_TOKEN_EOF:				return EXPECT_EOF;
    case G_TOKEN_SYMBOL:			return EXPECT_SYMBOL;
    case '-':					return EXPECT_MINUS;
    case G_TOKEN_INT:
    case G_TOKEN_FLOAT:
    case G_TOKEN_STRING:
    case G_TOKEN_IDENTIFIER:			return EXPECT_VALUE;
    case G_TOKEN_LEFT_CURLY:			return EXPECT_LEFT_CURLY;
    case G_TOKEN_RIGHT_CURLY:			return EXPECT_RIGHT_CURLY;
    case ';':					return EXPECT_SEMICOLON;
    default:					return 0;
    }
}

static void
sg_parser_skip_statement (SGParser *parser)
{
  GTokenType type;

  do
    type = g_scanner_get_next_token(parser->scanner);
  while (type != G_TOKEN_EOF
	 && type != G_TOKEN_LEFT_CURLY
	 && type != G_TOKEN_RIGHT_CURLY
	 && type != ';');
}

/*
 * Checks a statement against its definition and validates it or not.
 */
static gboolean
sg_parser_statement_validate (SGParserStatement *statement)
{
  if (statement->definition)
    {
      if ((statement->definition->scope && statement->terminator != '{')
	  || (! statement->definition->scope && statement->terminator != ';'))
	{
	  g_scanner_warn(statement->parser->scanner, _("wrong terminator"));
	  return FALSE;
	}
      else if (statement->definition->value_type == G_TYPE_NONE)
	{
	  if (G_IS_VALUE(&statement->value))
	    {
	      g_scanner_warn(statement->parser->scanner, _("value encountered, but none expected"));
	      return FALSE;
	    }
	}
      else if (! G_VALUE_HOLDS(&statement->value, statement->definition->value_type))
	{
	  g_scanner_warn(statement->parser->scanner, _("incorrect value type"));
	  return FALSE;
	}
    }

  if (statement->terminator == '}' && ! sg_slist_peek(statement->parser->scope_stack))
    {
      g_scanner_warn(statement->parser->scanner, _("unmatched start of scope"));
      return FALSE;
    }

  return TRUE;
}
