/* Keeping track of the flags that apply to a string extracted
   in a certain context.
   Copyright (C) 2001-2018 Free Software Foundation, Inc.

   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 3 of the License, or
   (at your option) 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, see <https://www.gnu.org/licenses/>.  */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

/* Specification.  */
#include "xg-arglist-context.h"

#include <stdlib.h>

#include "xalloc.h"
#include "xmalloca.h"


/* Null context.  */
flag_context_ty null_context = { undecided, false, undecided, false };

/* Transparent context.  */
flag_context_ty passthrough_context = { undecided, true, undecided, true };


flag_context_ty
inherited_context (flag_context_ty outer_context,
                   flag_context_ty modifier_context)
{
  flag_context_ty result = modifier_context;

  if (result.pass_format1)
    {
      result.is_format1 = outer_context.is_format1;
      result.pass_format1 = false;
    }
  if (result.pass_format2)
    {
      result.is_format2 = outer_context.is_format2;
      result.pass_format2 = false;
    }
  if (result.pass_format3)
    {
      result.is_format3 = outer_context.is_format3;
      result.pass_format3 = false;
    }
  return result;
}


/* Null context list iterator.  */
flag_context_list_iterator_ty null_context_list_iterator = { 1, NULL };

/* Transparent context list iterator.  */
static flag_context_list_ty passthrough_context_circular_list =
  {
    1,
    { undecided, true, undecided, true },
    &passthrough_context_circular_list
  };
flag_context_list_iterator_ty passthrough_context_list_iterator =
  {
    1,
    &passthrough_context_circular_list
  };


flag_context_list_iterator_ty
flag_context_list_iterator (flag_context_list_ty *list)
{
  flag_context_list_iterator_ty result;

  result.argnum = 1;
  result.head = list;
  return result;
}


flag_context_ty
flag_context_list_iterator_advance (flag_context_list_iterator_ty *iter)
{
  if (iter->head == NULL)
    return null_context;
  if (iter->argnum == iter->head->argnum)
    {
      flag_context_ty result = iter->head->flags;

      /* Special casing of circular list.  */
      if (iter->head != iter->head->next)
        {
          iter->head = iter->head->next;
          iter->argnum++;
        }

      return result;
    }
  else
    {
      iter->argnum++;
      return null_context;
    }
}


flag_context_list_ty *
flag_context_list_table_lookup (flag_context_list_table_ty *flag_table,
                                const void *key, size_t keylen)
{
  void *entry;

  if (flag_table->table != NULL
      && hash_find_entry (flag_table, key, keylen, &entry) == 0)
    return (flag_context_list_ty *) entry;
  else
    return NULL;
}


void
flag_context_list_table_add (flag_context_list_table_ty *table,
                             unsigned int index,
                             const char *name_start, const char *name_end,
                             int argnum, enum is_format value, bool pass)
{
  /* Insert the pair (VALUE, PASS) at INDEX in the element numbered ARGNUM
     of the list corresponding to NAME in the TABLE.  */
  if (table->table == NULL)
    hash_init (table, 100);
  {
    void *entry;

    if (hash_find_entry (table, name_start, name_end - name_start, &entry) != 0)
      {
        /* Create new hash table entry.  */
        flag_context_list_ty *list = XMALLOC (flag_context_list_ty);
        list->argnum = argnum;
        memset (&list->flags, '\0', sizeof (list->flags));
        switch (index)
          {
          case 0:
            list->flags.is_format1 = value;
            list->flags.pass_format1 = pass;
            break;
          case 1:
            list->flags.is_format2 = value;
            list->flags.pass_format2 = pass;
            break;
          case 2:
            list->flags.is_format3 = value;
            list->flags.pass_format3 = pass;
            break;
          default:
            abort ();
          }
        list->next = NULL;
        hash_insert_entry (table, name_start, name_end - name_start, list);
      }
    else
      {
        flag_context_list_ty *list = (flag_context_list_ty *)entry;
        flag_context_list_ty **lastp = NULL;
        /* Invariant: list == (lastp != NULL ? *lastp : entry).  */

        while (list != NULL && list->argnum < argnum)
          {
            lastp = &list->next;
            list = *lastp;
          }
        if (list != NULL && list->argnum == argnum)
          {
            /* Add this flag to the current argument number.  */
            switch (index)
              {
              case 0:
                list->flags.is_format1 = value;
                list->flags.pass_format1 = pass;
                break;
              case 1:
                list->flags.is_format2 = value;
                list->flags.pass_format2 = pass;
                break;
              case 2:
                list->flags.is_format3 = value;
                list->flags.pass_format3 = pass;
                break;
              default:
                abort ();
              }
          }
        else if (lastp != NULL)
          {
            /* Add a new list entry for this argument number.  */
            list = XMALLOC (flag_context_list_ty);
            list->argnum = argnum;
            memset (&list->flags, '\0', sizeof (list->flags));
            switch (index)
              {
              case 0:
                list->flags.is_format1 = value;
                list->flags.pass_format1 = pass;
                break;
              case 1:
                list->flags.is_format2 = value;
                list->flags.pass_format2 = pass;
                break;
              case 2:
                list->flags.is_format3 = value;
                list->flags.pass_format3 = pass;
                break;
              default:
                abort ();
              }
            list->next = *lastp;
            *lastp = list;
          }
        else
          {
            /* Add a new list entry for this argument number, at the beginning
               of the list.  Since we don't have an API for replacing the
               value of a key in the hash table, we have to copy the first
               list element.  */
            flag_context_list_ty *copy = XMALLOC (flag_context_list_ty);
            *copy = *list;

            list->argnum = argnum;
            memset (&list->flags, '\0', sizeof (list->flags));
            switch (index)
              {
              case 0:
                list->flags.is_format1 = value;
                list->flags.pass_format1 = pass;
                break;
              case 1:
                list->flags.is_format2 = value;
                list->flags.pass_format2 = pass;
                break;
              case 2:
                list->flags.is_format3 = value;
                list->flags.pass_format3 = pass;
                break;
              default:
                abort ();
              }
            list->next = copy;
          }
      }
  }
}
