/*   Cataract - Static web photo gallery generator
 *   Copyright (C) 2009 Tomas Bzatek <tbzatek@users.sourceforge.net>
 *
 *   This program 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 any later version.
 *
 *   This program 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 this program; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>

#include <glib.h>
#include <glib/gprintf.h>

#include "block-parser.h"
#include "gallery-utils.h"
#include "replace-table.h"



#define BUFFER_SIZE  65536    /*  line cannot be longer than this  */


struct BlockParser {
  GHashTable *table;
  GQueue *active_tree;
  gchar *current_line;
};

typedef struct {
  gchar *replace_key;
  gchar *data;
  gboolean used;
  gboolean finished;
} BlockData;



static void
block_parser_destroy_notify (gpointer data)
{
  BlockData *d = data;

  if (data == NULL)
    return;

  g_free (d->replace_key);
  g_free (d->data);
  g_free (d);
}

BlockParser *
block_parser_new ()
{
  BlockParser *parser;

  parser = g_new0 (BlockParser, 1);
  parser->table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, block_parser_destroy_notify);
  parser->active_tree = g_queue_new ();

  return parser;
}

void
block_parser_free (BlockParser *parser)
{
  g_return_if_fail (parser != NULL);

  g_hash_table_destroy (parser->table);
  g_queue_free (parser->active_tree);
  g_free (parser);
}


/*
 *   block_parser_register_key: tell parser to expect the key to catch
 *      key: part of the BEGIN_xxx placeholder, matched BEGIN_xxx and END_xxx as one block
 *      replace_key: placeholder to replace finished block with, for later use in replace_table,
 *                   placeholder will be surrounded by "<!-- $(replace_key) -->"
 *
 */
void
block_parser_register_key (BlockParser *parser, const gchar *key, const gchar *replace_key)
{
  BlockData *data;

  g_return_if_fail (parser != NULL);

  data = g_new0 (BlockData, 1);
  if (replace_key)
    data->replace_key = g_strdup_printf ("<!-- $(%s) -->", replace_key);
  data->used = TRUE;    /*  buffer is empty, mask it for block_parser_has_unused_data()  */
  g_hash_table_replace (parser->table, g_strdup (key), data);
}


/*
 *   block_parser_get_data: return retrieved data or NULL if none read yet
 *      returns newly allocated string, caller is responsible for freeing
 *
 */
gchar *
block_parser_get_data (BlockParser *parser, const gchar *key)
{
  BlockData *data = NULL;

  g_return_val_if_fail (parser != NULL, NULL);

  data = g_hash_table_lookup (parser->table, key);
  if (data != NULL) {
    data->used = TRUE;
    return g_strdup (data->data);
  }

  return NULL;
}

/*
 *   block_parser_has_unused_data: indicates whether the data have already been read and used (by calling block_parser_get_data)
 *
 */
gboolean
block_parser_has_unused_data (BlockParser *parser, const gchar *key)
{
  BlockData *data = NULL;

  g_return_val_if_fail (parser != NULL, FALSE);

  data = g_hash_table_lookup (parser->table, key);
  if (data != NULL) {
    return (data->used == FALSE) && (data->finished == TRUE);
  }

  return FALSE;
}




/* -------------------------------------------------------------------------------------------------------- */

static void
push_string (BlockParser *parser, const gchar *piece)
{
  BlockData *data;
  gchar *s;

  data = g_queue_peek_head (parser->active_tree);
  if (data) {
    if (data->data)
      s = g_strdup_printf ("%s%s", data->data, piece);
    else
      s = g_strdup (piece);
    g_free (data->data);
    data->data = s;
    data->used = FALSE;
  }
  else {
    s = g_strdup_printf ("%s%s", parser->current_line, piece);
    g_free (parser->current_line);
    parser->current_line = s;
  }
}

/*
 *   block_parser_read_and_parse: reads input from the file and returns parsed line
 *      - if there's a multiline block, string before the opening placeholder and
 *        string after closing placeholder are returned on one line,
 *        separated by "replace_key" placeholder as specified in registered blocks
 *      - only the outmost "replace_key" placeholder will be used,
 *        no matter how many nested blocks were inside
 *      - returns newly allocated string, caller is responsible for freeing
 *
 */
gchar *
block_parser_read_and_parse (BlockParser *parser, FILE *stream)
{
  gchar *buffer;
  gchar *b;
  gchar *token;
  gchar *start, *end;
  gchar *s;
  GList *keys;
  GList *l;
  BlockData *data;
  gboolean handled;

  g_return_val_if_fail (parser != NULL, NULL);

  buffer = g_malloc0 (BUFFER_SIZE);
  if (! fgets (buffer, BUFFER_SIZE, stream) || strlen (buffer) == 0) {
    g_free (buffer);
    return NULL;
  }
  parser->current_line = g_strdup ("");

  keys = g_hash_table_get_keys (parser->table);

  /*  find tokens  */
  b = buffer;
  while ((token = get_next_token (b, &start, &end, NULL))) {
    handled = FALSE;

    /*  push the string before the token  */
    s = g_strndup (b, start - b);
    push_string (parser, s);
    g_free (s);

    /*  match known tags  */
    for (l = keys; l != NULL; l = l->next) {
      /*  test BEGIN_ tokens  */
      s = g_strdup_printf ("BEGIN_%s", (gchar *) l->data);
      if (strcmp (s, token) == 0) {
        data = g_hash_table_lookup (parser->table, (gchar *) l->data);
        if (data) {
          g_queue_push_head (parser->active_tree, data);
          handled = TRUE;
        }
      }
      g_free (s);

      /*  test END_ tokens  */
      s = g_strdup_printf ("END_%s", (gchar *) l->data);
      if (strcmp (s, token) == 0) {
        data = g_hash_table_lookup (parser->table, (gchar *) l->data);
        if (data == NULL || data != g_queue_peek_head (parser->active_tree)) {
          log_error ("block_parser_read_and_parse: something is wrong with the parser table: expected '%s'\n", (gchar *) l->data);
        }
        else {
          g_queue_pop_head (parser->active_tree);
          /*  push the replacement placeholder  */
          if (data->replace_key)
            push_string (parser, data->replace_key);
          data->finished = TRUE;
          handled = TRUE;
        }
      }
      g_free (s);
    }

    /*  push the tag if not matched above  */
    if (! handled) {
      s = g_strndup (start, end - start + 1);
      push_string (parser, s);
      g_free (s);
    }

    g_free (token);
    b = end + 1;
  }

  /*  push rest of the buffer till the end of the line  */
  push_string (parser, b);

  g_list_free (keys);
  g_free (buffer);
  return parser->current_line;
}
