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

#ifndef XSD_CXX_TREE_TYPE_NAME_MAP_HXX
#define XSD_CXX_TREE_TYPE_NAME_MAP_HXX

#include <map>
#include <string>
#include <typeinfo>

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

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

namespace xsd
{
  namespace cxx
  {
    namespace tree
    {
      template <typename C>
      struct qualified_name
      {
        qualified_name (std::basic_string<C> name,
                        std::basic_string<C> namespace_)
            : name_ (name), namespace__ (namespace_)
        {
        }

        std::basic_string<C> const&
        name () const
        {
          return name_;
        }

        std::basic_string<C>
        namespace_ () const
        {
          return namespace__;
        }

      private:
        std::basic_string<C> name_;
        std::basic_string<C> namespace__;
      };

      template <typename C>
      inline bool
      operator== (qualified_name<C> const& x, qualified_name<C> const& y)
      {
        return x.name () == y.name () && x.namespace_ () == y.namespace_ ();
      }

      template <typename C>
      inline bool
      operator!= (qualified_name<C> const& x, qualified_name<C> const& y)
      {
        return !(x == y);
      }

      template <typename C>
      inline bool
      operator< (qualified_name<C> const& x, qualified_name<C> const& y)
      {
        return (x.name () < y.name ()) ||
          (x.name () == y.name () && x.namespace_ () < y.namespace_ ());
      }


      template <typename C>
      struct type_name_map
      {
        typedef std::type_info type_id;

        typedef void (*serializer) (xml::dom::element<C>&, type const&);

        void
        register_type (type_id const& tid,
                       qualified_name<C> const& name,
                       serializer s)
        {
          type_map_[&tid] = type_info (name, s);
        }

        void
        register_element (qualified_name<C> const& root,
                          qualified_name<C> const& subst,
                          type_id const& tid,
                          serializer s)
        {
          element_map_[root][&tid] = type_info (subst, s);
        }

      public:
        template<typename X>
        void
        serialize (std::basic_string<C> const& name, // element name
                   std::basic_string<C> const& ns,   // element namespace
                   bool global,
                   bool qualified,
                   xml::dom::element<C>& parent,
                   X const& x) const
        {
          type_id const& tid (typeid (x));

          // If dynamic type and static type are the same then
          // we are set for serialization.
          //
          if (typeid (X) == tid)
          {
            if (qualified)
            {
              xml::dom::element<C> e (name, ns, parent);
              e << x;
            }
            else
            {
              xml::dom::element<C> e (name, parent);
              e << x;
            }

            return;
          }

          // First see if we can find a substitution.
          //
          if (global)
          {
            qualified_name<C> qname (name, ns);

            typename element_map::const_iterator i (
              element_map_.find (qname));

            if (i != element_map_.end ())
            {
              if (type_info const* ti = find_substitution (i->second, tid))
              {
                xml::dom::element<C> e (ti->name ().name (),
                                        ti->name ().namespace_ (),
                                        parent);

                ti->serializer () (e, x);
                return;
              }
            }
          }

          // The last resort is xsi:type.
          //
          if (type_info const* ti = find_type (tid))
          {
            if (qualified)
            {
              xml::dom::element<C> e (name, ns, parent);
              ti->serializer () (e, x);

              xml::dom::attribute<C> a (
                xml::bits::type<C> (), xml::bits::xsi_namespace<C> (), e);

              a << qname_template<C> (ti->name ().namespace_ (),
                                      ti->name ().name ());
            }
            else
            {
              xml::dom::element<C> e (name, parent);
              ti->serializer () (e, x);

              xml::dom::attribute<C> a (
                xml::bits::type<C> (), xml::bits::xsi_namespace<C> (), e);

              a << qname_template<C> (ti->name ().namespace_ (),
                                      ti->name ().name ());
            }

            return;
          }

          throw no_type_info<C> (std::basic_string<C> (),
                                 std::basic_string<C> ()); //@@ TODO
        }


        // Serialize into existing element.
        //
        template<typename X>
        void
        serialize (std::basic_string<C> const& root_name,
                   std::basic_string<C> const& root_ns,
                   xml::dom::element<C>& e,
                   X const& x) const
        {
          std::basic_string<C> name (e.name ());
          std::basic_string<C> ns (e.namespace_ ());

          type_id const& tid (typeid (x));

          // If dynamic type and static type are the same then
          // we are set for serialization.
          //
          if (typeid (X) == tid)
          {
            if (root_name != name || root_ns != ns)
              throw unexpected_element<C> (name, ns, root_name, root_ns);

            e << x;

            return;
          }

          // First see if this is a substitution.
          //
          if (root_name != name || root_ns != ns)
          {
            qualified_name<C> qname (root_name, root_ns);

            typename element_map::const_iterator i (
              element_map_.find (qname));

            if (i != element_map_.end ())
            {
              if (type_info const* ti = find_substitution (i->second, tid))
              {

                if (ti->name ().name () != name ||
                    ti->name ().namespace_ () != ns)
                  throw unexpected_element<C> (
                    name, ns, ti->name ().name (), ti->name ().namespace_ ());

                ti->serializer () (e, x);
                return;
              }
            }

            // This is not a valid substitution.
            //
            throw unexpected_element<C> (name, ns, root_name, root_ns);
          }

          // The last resort is xsi:type.
          //
          if (type_info const* ti = find_type (tid))
          {
            ti->serializer () (e, x);

            xml::dom::attribute<C> a (
              xml::bits::type<C> (), xml::bits::xsi_namespace<C> (), e);

            a << qname_template<C> (ti->name ().namespace_ (),
                                    ti->name ().name ());

            return;
          }

          throw no_type_info<C> (std::basic_string<C> (),
                                 std::basic_string<C> ()); //@@ TODO
        }


        // Create DOMDocument with root element suitable for serializing
        // X into it.
        //
        template<typename X>
        xml::dom::auto_ptr <xercesc::DOMDocument>
        dom (std::basic_string<C> const& name, // element name
             std::basic_string<C> const& ns,   // element namespace
             xml::dom::namespace_infomap<C> const& m,
             X const& x) const
        {
          type_id const& tid (typeid (x));

          // If dynamic type and static type are the same then
          // we are set for serialization.
          //
          if (typeid (X) == tid)
          {
            return xml::dom::dom<C> (name, ns, m);
          }

          // See if we can find a substitution.
          //
          {
            qualified_name<C> qname (name, ns);

            typename element_map::const_iterator i (
              element_map_.find (qname));

            if (i != element_map_.end ())
            {
              if (type_info const* ti = find_substitution (i->second, tid))
              {
                return xml::dom::dom<C> (
                  ti->name ().name (), ti->name ().namespace_ (), m);
              }
            }
          }

          // If there is no substitution then serialize() will have to
          // find suitable xsi:type.
          //
          return xml::dom::dom<C> (name, ns, m);
        }

      public:
        type_name_map ();

      private:
        struct type_info
        {
          type_info (qualified_name<C> const& name,
                     typename type_name_map::serializer serializer)
              : name_ (name), serializer_ (serializer)
          {
          }

          qualified_name<C> const&
          name () const
          {
            return name_;
          }

          typename type_name_map::serializer
          serializer () const
          {
            return serializer_;
          }

          // For std::map.
          //
          type_info ()
              : name_ (std::basic_string<C> (), std::basic_string<C> ()),
                serializer_ (0)
          {
          }

        private:
          qualified_name<C> name_;
          typename type_name_map::serializer serializer_;
        };

        struct type_id_comparator
        {
          bool
          operator() (type_id const* x, type_id const* y) const
          {
            return x->before (*y);
          }
        };

        typedef
        std::map<type_id const*, type_info, type_id_comparator>
        type_map;

        // Map of (root-element to map of (type_id to type_info)).
        // Note that here type_info::name is element name.
        //
        typedef
        std::map<type_id const*, type_info, type_id_comparator>
        subst_map;

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

        type_map type_map_;
        element_map element_map_;

      private:
        type_info const*
        find_substitution (subst_map const& start,
                           type_id const& tid) const
        {
          typename subst_map::const_iterator i (start.find (&tid));

          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->second.name ()));

              if (j != element_map_.end ())
              {
                if (type_info const* ti = find_substitution (j->second, tid))
                  return ti;
              }
            }
          }

          return 0;
        }

        // The name argument is as specified in xsi:type.
        //
        type_info const*
        find_type (type_id const& tid) const
        {
          typename type_map::const_iterator i (type_map_.find (&tid));

          if (i != type_map_.end ())
            return &i->second;
          else
            return 0;
        }
      };


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

        type_name_plate ()
        {
          if (count == 0)
            map = new type_name_map<C>;

          ++count;
        }

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

      template<unsigned long id, typename C>
      type_name_map<C>* type_name_plate<id, C>::map = 0;

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


      //
      //
      template<unsigned long id, typename C>
      type_name_map<C>&
      type_name_map_instance ()
      {
        return *type_name_plate<id, C>::map;
      }


      //
      //
      namespace bits
      {
        template<typename C, typename X>
        struct serializer
        {
          static void
          impl (xml::dom::element<C>& e, type const& x)
          {
            e << dynamic_cast<X const&> (x);
          }
        };
      }

      template<unsigned long id, typename C, typename X>
      struct type_name_initializer
      {
        // Register type.
        //
        type_name_initializer (C const* name, C const* ns)
        {
          type_name_map_instance<id, C> ().register_type (
            typeid (X),
            qualified_name<C> (name, ns),
            &bits::serializer<C, X>::impl);
        }

        // Register element.
        //
        type_name_initializer (C const* root_name,
                               C const* root_ns,
                               C const* subst_name,
                               C const* subst_ns)
        {
          type_name_map_instance<id, C> ().register_element (
            qualified_name<C> (root_name, root_ns),
            qualified_name<C> (subst_name, subst_ns),
            typeid (X),
            &bits::serializer<C, X>::impl);
        }
      };
    }
  }
}


#endif // XSD_CXX_TREE_TYPE_NAME_MAP_HXX

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