/*
 * Mesa 3-D graphics library
 *
 * Copyright (C) 2010 LunarG Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * Authors:
 *    Chia-I Wu <olv@lunarg.com>
 */

#include <stdlib.h>
#include <string.h>

#include "u_current.h"
#include "u_thread.h"
#include "mapi.h"
#include "stub.h"
#include "table.h"

/* dynamic stubs will run out before this array */
static const struct mapi_stub *mapi_stub_map[MAPI_TABLE_NUM_SLOTS];
static int mapi_num_stubs;

static const struct mapi_stub *
get_stub(const char *name, const struct mapi_stub *alias)
{
   const struct mapi_stub *stub;

   stub = stub_find_public(name);
   if (!stub) {
      struct mapi_stub *dyn = stub_find_dynamic(name, 1);
      if (dyn) {
         stub_fix_dynamic(dyn, alias);
         stub = dyn;
      }
   }

   return stub;
}

/**
 * Initialize mapi.  spec consists of NULL-separated strings.  The first string
 * denotes the version.  It is followed by variable numbers of entries.  Each
 * entry can have multiple names.  An empty name terminates an entry.  An empty
 * entry terminates the spec.  A spec of two entries, Foo and Bar, is as
 * follows
 *
 *   "1\0"
 *   "Foo\0"
 *   "FooEXT\0"
 *   "\0"
 *   "Bar\0"
 *   "\0"
 */
void
mapi_init(const char *spec)
{
   static mtx_t mutex = _MTX_INITIALIZER_NP;
   const char *p;
   int ver, count;

   mtx_lock(&mutex);

   /* already initialized */
   if (mapi_num_stubs) {
      mtx_unlock(&mutex);
      return;
   }

   count = 0;
   p = spec;

   /* parse version string */
   ver = atoi(p);
   if (ver != 1) {
      mtx_unlock(&mutex);
      return;
   }
   p += strlen(p) + 1;

   while (*p) {
      const struct mapi_stub *stub;

      stub = get_stub(p, NULL);
      /* out of dynamic entries */
      if (!stub)
         break;
      p += strlen(p) + 1;

      while (*p) {
         get_stub(p, stub);
         p += strlen(p) + 1;
      }

      mapi_stub_map[count++] = stub;
      p++;
   }

   mapi_num_stubs = count;

   mtx_unlock(&mutex);
}

/**
 * Return the address of an entry.  Optionally generate the entry if it does
 * not exist.
 */
mapi_proc
mapi_get_proc_address(const char *name)
{
   const struct mapi_stub *stub;

   stub = stub_find_public(name);
   if (!stub)
      stub = stub_find_dynamic(name, 0);

   return (stub) ? (mapi_proc) stub_get_addr(stub) : NULL;
}

/**
 * Create a dispatch table.
 */
struct mapi_table *
mapi_table_create(void)
{
   const struct mapi_table *noop = table_get_noop();
   struct mapi_table *tbl;

   tbl = malloc(MAPI_TABLE_SIZE);
   if (tbl)
      memcpy(tbl, noop, MAPI_TABLE_SIZE);

   return tbl;
}

/**
 * Destroy a dispatch table.
 */
void
mapi_table_destroy(struct mapi_table *tbl)
{
   free(tbl);
}

/**
 * Fill a dispatch table.  The order of the procs is determined when mapi_init
 * is called.
 */
void
mapi_table_fill(struct mapi_table *tbl, const mapi_proc *procs)
{
   const struct mapi_table *noop = table_get_noop();
   int i;

   for (i = 0; i < mapi_num_stubs; i++) {
      const struct mapi_stub *stub = mapi_stub_map[i];
      int slot = stub_get_slot(stub);
      mapi_func func = (mapi_func) procs[i];

      if (!func)
         func = table_get_func(noop, slot);
      table_set_func(tbl, slot, func);
   }
}

/**
 * Make a dispatch table current.
 */
void
mapi_table_make_current(const struct mapi_table *tbl)
{
   u_current_set_table(tbl);
}