// file      : xsd/cxx/tree/type-factory-map.hxx
// author    : Boris Kolpackov <boris@codesynthesis.com>
// copyright : Copyright (c) 2005-2006 Code Synthesis Tools CC
// license   : GNU GPL v2 + exceptions; see accompanying LICENSE file

#ifndef XSD_CXX_TREE_TYPE_FACTORY_MAP_HXX
#define XSD_CXX_TREE_TYPE_FACTORY_MAP_HXX

#include <map>
#include <string>
#include <memory> // std::auto_ptr

#include <xsd/cxx/xml/elements.hxx>
#include <xsd/cxx/xml/bits/literals.hxx>

#include <xsd/cxx/tree/elements.hxx>
#include <xsd/cxx/tree/types.hxx>

namespace xsd
{
  namespace cxx
  {
    namespace tree
    {
      namespace bits
      {
        template<typename C, typename X>
        struct factory
        {
          static std::auto_ptr<type>
          impl (xml::dom::element<C> const& e, flags f, type* c)
          {
            return std::auto_ptr<type> (new X (e, f, c));
          }

          static std::auto_ptr<type>
          traits_adapter (xml::dom::element<C> const& e, flags f, type* c)
          {
            std::auto_ptr<X> r (traits<X>::create (e, f, c));
            return std::auto_ptr<type> (r.release ());
          }
        };
      }

      template <typename C>
      struct type_factory_map
      {
        typedef std::auto_ptr<type> (*factory) (
          xml::dom::element<C> const&, flags, type*);

        void
        register_type (std::basic_string<C> const& type_id, factory f)
        {
          type_map_[type_id] = f;
        }

        void
        register_element (std::basic_string<C> const& root_id,
                          std::basic_string<C> const& subst_id,
                          factory f)
        {
          element_map_[root_id][subst_id] = f;
        }


        template <typename X>
        std::auto_ptr<X>
        create (std::basic_string<C> const& name, // element name
                std::basic_string<C> const& ns,   // element namespace
                bool global,
                bool qualified,
                xml::dom::element<C> const& e,
                tree::flags flags,
                type* c) const
        {
          factory f = 0;

          // See if we've got a straight match.
          //
          if (e.name () == name &&
              (qualified ? e.namespace_ () == ns : ns.empty ()))
          {
            f = &bits::factory<C, X>::traits_adapter;
          }
          else if (global)
          {
            // See if we have a substitution.
            //
            typename element_map::const_iterator i (
              element_map_.find (name + C (' ') + ns));

            if (i != element_map_.end ())
            {
              f = find_substitution (i->second,
                                     e.name () + C (' ') + e.namespace_ ());
            }
          }

          if (f == 0)
            return std::auto_ptr<X> (0); // No match.

          // Check for xsi:type
          //
          {
            std::basic_string<C> xsi_type (
              e.attribute (xml::bits::type<C> (),
                           xml::bits::xsi_namespace<C> ()));

            if (!xsi_type.empty ())
              f = find_type (xsi_type, e);
          }

          // Now create and cast the instance.
          //
          std::auto_ptr<type> tmp (f (e, flags, c));
          std::auto_ptr<X> r (dynamic_cast<X*> (tmp.get ()));

          if (r.get ())
            tmp.release ();
          else
            throw not_derived<C> (
              std::basic_string<C> (), std::basic_string<C> (),
              std::basic_string<C> (), std::basic_string<C> ()); //@@ TODO

          return r;
        }

        type_factory_map ();

      private:
        typedef
        std::map<std::basic_string<C>, factory>
        type_map;

        // Map of (root-element to map of (subst-element to factory)).
        //
        typedef
        std::map<std::basic_string<C>, factory>
        subst_map;

        typedef
        std::map<std::basic_string<C>, subst_map>
        element_map;

        type_map type_map_;
        element_map element_map_;

      private:


      private:
        factory
        find_substitution (subst_map const& start,
                           std::basic_string<C> const& id) const
        {
          typename subst_map::const_iterator i (start.find (id));

          if (i != start.end ())
            return i->second;
          else
          {
            for (i = start.begin (); i != start.end (); ++i)
            {
              typename element_map::const_iterator j (
                element_map_.find (i->first));

              if (j != element_map_.end ())
              {
                if (factory f = find_substitution (j->second, id))
                  return f;
              }
            }
          }

          return 0;
        }

        // The name argument is as specified in xsi:type.
        //
        factory
        find_type (std::basic_string<C> const& name,
                   xml::dom::element<C> const& e) const
        {
          std::basic_string<C> uq_name (xml::uq_name (name));
          std::basic_string<C> ns_name;

          try
          {
            ns_name = xml::dom::ns_name (e, name);
          }
          catch (xml::dom::no_mapping const&)
          {
            std::basic_string<C> prefix (xml::prefix (name));

            if (prefix.empty ())
              ns_name.clear (); // See if we've got any no-namespace types.
            else
              throw no_prefix_mapping<C> (prefix);
          }

          typename type_map::const_iterator i (
            type_map_.find (uq_name + C (' ') + ns_name));

          if (i == type_map_.end ())
            throw no_type_info<C> (uq_name, ns_name);

          return i->second;
        }
      };


      //
      //
      template<unsigned long id, typename C>
      struct type_factory_plate
      {
        static type_factory_map<C>* map;
        static unsigned long count;

        type_factory_plate ()
        {
          if (count == 0)
            map = new type_factory_map<C>;

          ++count;
        }

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

      template<unsigned long id, typename C>
      type_factory_map<C>* type_factory_plate<id, C>::map = 0;

      template<unsigned long id, typename C>
      unsigned long type_factory_plate<id, C>::count = 0;


      //
      //
      template<unsigned long id, typename C>
      type_factory_map<C>&
      type_factory_map_instance ()
      {
        return *type_factory_plate<id, C>::map;
      }


      //
      //
      template<unsigned long id, typename C, typename X>
      struct type_factory_initializer
      {
        // Register type.
        //
        type_factory_initializer (C const* type_id)
        {
          type_factory_map_instance<id, C> ().register_type (
            type_id, &bits::factory<C, X>::impl);

          // std::cerr << "registered " << type_id << std::endl;
        }

        // Register element.
        //
        type_factory_initializer (C const* root_id, C const* subst_id)
        {
          type_factory_map_instance<id, C> ().register_element (
            root_id, subst_id, &bits::factory<C, X>::impl);

          // std::cerr << "registered " << type_id << std::endl;
        }
      };
    }
  }
}


#endif // XSD_CXX_TREE_TYPE_FACTORY_MAP_HXX

#include <xsd/cxx/tree/type-factory-map.ixx>
