// file      : xsde/cxx/serializer/substitution-map.cxx
// author    : Boris Kolpackov <boris@codesynthesis.com>
// copyright : Copyright (c) 2005-2008 Code Synthesis Tools CC
// license   : GNU GPL v2 + exceptions; see accompanying LICENSE file

#include <xsde/cxx/config.hxx>

#include <string.h> // strlen, strcmp, strncmp

#ifndef XSDE_EXCEPTIONS
#  include <assert.h> // assert
#  include <stdlib.h> // exit
#endif

#include <xsde/cxx/serializer/substitution-map.hxx>
#include <xsde/cxx/serializer/substitution-map-load.hxx>

namespace xsde
{
  namespace cxx
  {
    namespace serializer
    {
      substitution_map* substitution_map_init::map = 0;
      size_t substitution_map_init::count = 0;

      substitution_map::
      ~substitution_map ()
      {
        for (const_iterator i (begin ()), e (end ()); i != e; ++i)
        {
          delete *static_cast<hashmap**> (const_cast<void*> (*i));
        }
      }

      void substitution_map::
      insert (const char* root,
              const char* member_ns,
              const char* member_name,
              const char* member_type)
      {
        hashmap* m;

        if (const void* p = find (root))
          m = *static_cast<hashmap**> (const_cast<void*> (p));
        else
        {
          m = new hashmap (XSDE_SERIALIZER_SMAP_BUCKET_BUCKETS,
                           sizeof (value));

#ifndef XSDE_EXCEPTIONS
          if (m == 0 || m->_error () != hashmap::error_none)
          {
            error_ = error_no_memory;
            return;
          }
#endif
          hashmap::insert (root, &m);

#ifndef XSDE_EXCEPTIONS
          if (_error () != error_none)
            return;
#endif
        }

        value v;
        v.ns_ = member_ns;
        v.name_ = member_name;
        m->insert (member_type, &v);

#ifndef XSDE_EXCEPTIONS
        if (m->_error () != hashmap::error_none)
          error_ = error_no_memory;
#endif
      }

      bool substitution_map::
      check_ (const char*& ns,
              const char*& name,
              const char* type) const
      {
        size_t h = hash (name);

        if (ns)
        {
          h = hash (h, " ", 1);
          h = hash (h, ns);
        }

        const bucket* p = find (h);

        if (p == 0)
          return false;

        // Search for the entry in the bucket.
        //
        const size_t el_size = sizeof (element) + sizeof (hashmap*);
        const char* b = reinterpret_cast<const char*> (p) + sizeof (bucket);
        const char* e = b + p->size_ * el_size;

        size_t nl = ns ? strlen (name) : 0;

        for (; b < e; b += el_size)
        {
          const element* e = reinterpret_cast<const element*> (b);

          if (e->hash_ == h)
          {
            if (ns == 0)
            {
              if (strcmp (e->key_, name) == 0)
                break;
            }
            else
            {
              if (strncmp (e->key_, name, nl) == 0 &&
                  e->key_[nl] == ' ' &&
                  strcmp (e->key_ + nl + 1, ns) == 0)
                break;
            }
          }
        }

        if (b == e)
          return false;

        const hashmap* map = *reinterpret_cast<const hashmap* const*> (
          b + sizeof (element));

        // See if we have a direct substitution.
        //
        if (const value* v = static_cast<const value*> (map->find (type)))
        {
          ns = v->ns_;
          name = v->name_;
          return true;
        }

        // Otherwise we have to iterate over possible substitutions and
        // see if any of them can in turn be substituted with something
        // that we can use.
        //
        for (const_iterator i (map->begin ()), end (map->end ());
             i != end; ++i)
        {
          const value* v = static_cast<const value*> (*i);

          const char* tns = v->ns_;
          const char* tn = v->name_;

          if (check_ (tns, tn, type))
          {
            ns = tns;
            name = tn;
            return true;
          }
        }

        return false;
      }

      // substitution_map_init
      //
      substitution_map_init::
      substitution_map_init ()
      {
        if (count == 0)
        {
          map = new substitution_map (XSDE_SERIALIZER_SMAP_BUCKETS);

#ifndef XSDE_EXCEPTIONS
          if (map == 0 || map->_error () != substitution_map::error_none)
          {
            // This is static initialization so there is nothing we can do.
            // The best thing is to fail fast. abort() would have probably
            // been the better choice here but it is not available on some
            // platforms (notably, WinCE).
            //
            assert (false);
            exit (1);
          }
#endif
        }

        ++count;
      }

      substitution_map_init::
      ~substitution_map_init ()
      {
        if (--count == 0)
          delete map;
      }

      // substitution_map_entry
      //
      substitution_map_entry::
      substitution_map_entry (const char* root,
                              const char* member_ns,
                              const char* member_name,
                              const char* member_type)
      {
        substitution_map& m = substitution_map_instance ();
        m.insert (root, member_ns, member_name, member_type);

#ifndef XSDE_EXCEPTIONS
        if (m._error () != substitution_map::error_none)
        {
          // This is static initialization so there is nothing we can do.
          // The best thing is to fail fast. abort() would have probably
          // been the better choice here but it is not available on some
          // platforms (notably, WinCE).
          //
          assert (false);
          exit (1);
        }
#endif
      }

      //
      //
      size_t
      serializer_smap_elements ()
      {
        return substitution_map_instance ().size ();
      }

      size_t
      serializer_smap_bucket_elements ()
      {
        size_t r = 0;

        substitution_map& m = substitution_map_instance ();

        for (substitution_map::const_iterator i (m.begin ()), e (m.end ());
             i != e; ++i)
        {
          const hashmap* h = *static_cast<const hashmap* const*> (*i);

          if (h->size () > r)
            r = h->size ();
        }

        return r;
      }
    }
  }
}
