text: /*
   * #%L
   * OME-FILES C++ library for image IO.
   * Copyright © 2006 - 2015 Open Microscopy Environment:
   *   - Massachusetts Institute of Technology
   *   - National Institutes of Health
   *   - University of Dundee
   *   - Board of Regents of the University of Wisconsin-Madison
   *   - Glencoe Software, Inc.
   * %%
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   *
   * 1. Redistributions of source code must retain the above copyright notice,
   *    this list of conditions and the following disclaimer.
   * 2. Redistributions in binary form must reproduce the above copyright notice,
   *    this list of conditions and the following disclaimer in the documentation
   *    and/or other materials provided with the distribution.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   * POSSIBILITY OF SUCH DAMAGE.
   *
   * The views and conclusions contained in the software and documentation are
   * those of the authors and should not be interpreted as representing official
   * policies, either expressed or implied, of any organization.
   * #L%
   */
  
  #ifndef OME_FILES_METADATAMAP_H
  #define OME_FILES_METADATAMAP_H
  
  #include <algorithm>
  #include <cmath>
  #include <cstdint>
  #include <iomanip>
  #include <iterator>
  #include <map>
  #include <ostream>
  #include <sstream>
  #include <string>
  #include <vector>
  
  #include <ome/compat/variant.h>
  
  namespace ome
  {
    namespace files
    {
  
      /**
       * Metadata key-value map using a restricted set of value types.
       *
       * Permitted types include @c std::string, @c bool, integer types
       * (@c uint8_t, @c uint16_t, @c uint32_t, @c uint64_t, @c int8_t,
       * @c int16_t, @c int32_t, @c int64_t) and floating point types
       * (@c float, @c double, @c long @c double).  Additionally a @c
       * std::vector specialised for each of the aforementioned types
       * may also be stored.  However, types may not be mixed within a
       * single @c std::vector.
       *
       * The set() method permits setting of values, which includes both
       * simple values and vectors of any supported type.  The
       * convenience method append() allows appending of a single value
       * to a list.
       *
       * The get() methods permit retrieval of values.  There are
       * various forms, which offer different tradeoffs, for example
       * copying the value and returning an error, versus returning a
       * direct reference but throwing an exception on error.
       */
      class MetadataMap
      {
      public:
        /// Key type.
        typedef std::string key_type;
  
        /// Value type (all storable types),
        using value_type =
          ome::compat::variant<std::string,
                               bool,
                               uint8_t,
                               uint16_t,
                               uint32_t,
                               uint64_t,
                               int8_t,
                               int16_t,
                               int32_t,
                               int64_t,
                               float,
                               double,
                               long double,
                               std::vector<std::string>,
                               std::vector<bool>,
                               std::vector<uint8_t>,
                               std::vector<uint16_t>,
                               std::vector<uint32_t>,
                               std::vector<uint64_t>,
                               std::vector<int8_t>,
                               std::vector<int16_t>,
                               std::vector<int32_t>,
                               std::vector<int64_t>,
                               std::vector<float>,
                               std::vector<double>,
                               std::vector<long double>>;
  
        /// std::string to discriminated type mapping.
        typedef std::map<key_type, value_type> map_type;
  
        /// Size type.
        typedef map_type::size_type size_type;
  
        /// Iterator.
        typedef map_type::iterator iterator;
  
        /// Constant iterator.
        typedef map_type::const_iterator const_iterator;
  
        /// Reverse iterator.
        typedef map_type::reverse_iterator reverse_iterator;
  
        /// Constant reverse iterator.
        typedef map_type::const_reverse_iterator const_reverse_iterator;
  
      private:
        /// Key-value mapping.
        map_type discriminating_map;
  
      public:
        /// Constructor.
        MetadataMap()
        {}
  
        /// Destructor.
        ~MetadataMap()
        {}
  
        /**
         * Add a key-value pair to the map.
         *
         * @note If a key by the same already exists in the map, it will
         * be removed and replaced.
         *
         * @param key the key name.
         * @param value the abstract value.
         */
        void
        set(const key_type&   key,
            const value_type& value)
        {
          iterator i = find(key);
          if (i != end())
            erase(i);
          map_type::value_type newvalue(key, value);
          insert(newvalue);
        }
  
        /**
         * Add a key-value pair to the map.
         *
         * @note If a key by the same already exists in the map, it will
         * be removed and replaced.
         *
         * @param key the key name.
         * @param value the value.
         */
        template <typename T>
        void
        set(const key_type& key,
            const T&        value)
        {
          value_type v = value;
          set(key, v);
        }
  
        /**
         * Append a value to a vector.
         *
         * @note If a key by the same already exists in the map and is
         * of the wrong type or is not a vector, it will be removed and
         * replaced.
         *
         * @param key the key name.
         * @param value the value to append.
         */
        template <typename T>
        void
        append(const key_type& key,
               const T&        value)
        {
          typedef typename std::vector<T> list_type;
  
          try
            {
              list_type& list(get<list_type>(key));
              list.push_back(value);
            }
          catch (const ome::compat::bad_variant_access&)
            {
              list_type new_list;
              new_list.push_back(value);
              set(key, new_list);
            }
        }
  
        /**
         * Get the value of a particular key from the map.
         *
         * If the key was not found, @p value will remain unmodified.
         *
         * @param key the key to find.
         * @param value a reference to store a copy of the value.
         * @returns @c true if the key was found, @c false otherwise.
         */
        bool
        get(const key_type& key,
            value_type&     value) const
        {
          const_iterator i = find(key);
          if (i == end())
            return false;
  
          value = i->second;
          return true;
        }
  
        /**
         * Get the value of a particular key from the map.
         *
         * If the key was not found, or the type of @p value does not
         * match the stored value type, @p value will remain unmodified.
         *
         * @param key the key to find.
         * @param value a reference to store a copy of the value.
         * @returns @c true if the key was found and the value was of
         * the correct type, @c false otherwise.
         */
        template <typename T>
        bool
        get(const key_type& key,
            T&              value) const
        {
          try
            {
              value = get<T>(key);
              return true;
            }
          catch (const ome::compat::bad_variant_access&)
            {
              return false;
            }
        }
  
        /**
         * Get a reference to the value of a particular key from the map.
         *
         * If the key was not found, or the type of @p value does not
         * match the stored value type, @p value will remain unmodified.
         *
         * @param key the key to find.
         * @returns a reference to the stored value.
         * @throws ome::compat::bad_variant_access on failure if the key
         * was not found or if the type did not match the stored value
         * type.
         */
        template <typename T>
        T&
        get(const key_type& key)
        {
          return ome::compat::get<T>(get<value_type>(key));
        }
  
        /**
         * Get a reference to the value of a particular key from the map.
         *
         * If the key was not found, or the type of @p value does not
         * match the stored value type, @p value will remain unmodified.
         *
         * @param key the key to find.
         * @returns a reference to the stored value.
         * @throws ome::compat::bad_variant_access on failure if the key
         * was not found or if the type did not match the stored value
         * type.
         */
        template <typename T>
        const T&
        get(const key_type& key) const
        {
          return ome::compat::get<T>(get<value_type>(key));
        }
  
        /**
         * Find a key in the map.
         *
         * @param key the key to find.
         * @returns an iterator to the key-value pair.
         */
        iterator
        find(const key_type& key)
        {
          return discriminating_map.find(key);
        }
  
        /**
         * Find a key in the map.
         *
         * @param key the key to find.
         * @returns an iterator to the key-value pair.
         */
        const_iterator
        find(const key_type& key) const
        {
          return discriminating_map.find(key);
        }
  
        /**
         * Insert a value into the map.
         *
         * Note that this method is an alternative to set(), which
         * retains the insert semantics of std::map::insert(), i.e. it
         * will not replace keys which already exist in the map, and so
         * will be a no-op if the key is already present.
         *
         * @param value the value to insert.
         * @returns an iterator to the inserted key, or prexisting key
         * if present, and @c true if the value was inserted, @c false
         * otherwise.
         */
        std::pair<iterator, bool>
        insert(map_type::value_type& value)
        {
          return discriminating_map.insert(value);
        }
  
        /**
         * Erase a key from the map by name.
         *
         * @param key the key to erase.
         */
        void
        erase(const key_type& key)
        {
          discriminating_map.erase(key);
        }
  
        /**
         * Erase a key from the map by an iterator position.
         *
         * @param pos the iterator position to erase.
         */
        void
        erase(iterator pos)
        {
          discriminating_map.erase(pos);
        }
  
      private:
        /// Functor to get a map key.
        struct getkey
        {
          /**
           * Get key from pair.
           *
           * @param pair the pair to use.
           * @returns the key.
           */
          template <typename T>
          typename T::first_type operator()(T pair) const
          {
            return pair.first;
          }
        };
  
      public:
        /**
         * Get a list of keys in the map.
         *
         * @note This is inefficient, and exists solely for
         * compatibility with the Java implementation.  Using the
         * iterator interface directly should be preferred.
         *
         * @returns a sorted list of keys.
         */
        std::vector<key_type>
        keys() const
        {
          std::vector<key_type> ret;
          std::transform(begin(), end(), std::back_inserter(ret), getkey());
          std::sort(ret.begin(), ret.end());
  
          return ret;
        }
  
        /**
         * Merge a separate map into this map.
         *
         * @param map the map to merge.
         * @param prefix a prefix to append to the keys of the map being
         * merged.
         */
        void
        merge(const MetadataMap& map,
              const std::string& prefix)
        {
          for (const auto& m : map)
            {
              map_type::value_type v(prefix + m.first, m.second);
              insert(v);
            }
        }
  
        /**
         * Create a flattened map.
         *
         * All vectors in the map will be flattened, which is the
         * replacement of each vector by a key per element with with a
         * @c \#n key suffix.
         *
         * @returns the flattened map.
         */
        MetadataMap
        flatten() const;
  
        /**
         * Get the underlying map.
         *
         * @returns a reference to the map.
         */
        map_type&
        map()
        {
          return discriminating_map;
        }
  
        /**
         * Get the underlying map.
         *
         * @returns a reference to the map.
         */
        const map_type&
        map() const
        {
          return discriminating_map;
        }
  
        /**
         * Get an iterator to the beginning of the map.
         *
         * @returns an iterator.
         */
        iterator
        begin()
        {
          return discriminating_map.begin();
        }
  
        /**
         * Get a constant iterator to the beginning of the map.
         *
         * @returns a constant iterator.
         */
        const_iterator
        begin() const
        {
          return discriminating_map.begin();
        }
  
        /**
         * Get an iterator to the end of the map.
         *
         * @returns an iterator.
         */
        iterator
        end()
        {
          return discriminating_map.end();
        }
  
        /**
         * Get a constant iterator to the end of the map.
         *
         * @returns a constant iterator.
         */
        const_iterator
        end() const
        {
          return discriminating_map.end();
        }
  
        /**
         * Get a reverse iterator to the end of the map.
         *
         * @returns an iterator.
         */
        reverse_iterator
        rbegin()
        {
          return discriminating_map.rbegin();
        }
  
        /**
         * Get a constant reverse iterator to the end of the map.
         *
         * @returns an iterator.
         */
        const_reverse_iterator
        rbegin() const
        {
          return discriminating_map.rbegin();
        }
  
        /**
         * Get a reverse iterator to the beginning of the map.
         *
         * @returns an iterator.
         */
        reverse_iterator
        rend()
        {
          return discriminating_map.rend();
        }
  
        /**
         * Get a constant reverse iterator to the beginning of the map.
         *
         * @returns an iterator.
         */
        const_reverse_iterator
        rend() const
        {
          return discriminating_map.rend();
        }
  
        /**
         * Get or set a value by key index.
         *
         * Access the metadata map as an associative array.  This may be
         * used to get or set a value, with the value being created if
         * the key was not already present in the map.
         *
         * @param key the key to retrieve.
         * @returns a reference to the value associated with the @p key.
         */
        value_type&
        operator[] (const key_type& key)
        {
          return discriminating_map[key];
        }
  
        /**
         * Compare maps for equality.
         *
         * @param rhs the map to compare.
         * @returns @c true if equal to @p rhs, @c false otherwise.
         */
        bool
        operator == (const MetadataMap& rhs) const
        {
          return discriminating_map == rhs.discriminating_map;
        }
  
        /**
         * Compare maps for non-equality.
         *
         * @param rhs the map to compare.
         * @returns @c true if not equal to @p rhs, @c false otherwise.
         */
        bool
        operator != (const MetadataMap& rhs) const
        {
          return discriminating_map != rhs.discriminating_map;
        }
  
        /**
         * Check if map is less than another map.
         *
         * @param rhs the map to compare.
         * @returns @c true if less than @p rhs, @c false otherwise.
         */
        bool
        operator < (const MetadataMap& rhs) const
        {
          return discriminating_map < rhs.discriminating_map;
        }
  
        /**
         * Check if map is less than or equal to another map.
         *
         * @param rhs the map to compare.
         * @returns @c true if less than or equal to @p rhs, @c false
         * otherwise.
         */
        bool
        operator <= (const MetadataMap& rhs) const
        {
          return discriminating_map <= rhs.discriminating_map;
        }
  
        /**
         * Check if map is greater than another map.
         *
         * @param rhs the map to compare.
         * @returns @c true if greater than @p rhs, @c false otherwise.
         */
        bool
        operator > (const MetadataMap& rhs) const
        {
          return discriminating_map > rhs.discriminating_map;
        }
  
        /**
         * Check if map is greater than or equal to another map.
         *
         * @param rhs the map to compare.
         * @returns @c true if greater than or equal to @p rhs, @c false
         * otherwise.
         */
        bool
        operator >= (const MetadataMap& rhs) const
        {
          return discriminating_map >= rhs.discriminating_map;
        }
  
        /**
         * Get the size of the map.
         *
         * Note that vectors are counted as a single item; child
         * elements are not included.
         *
         * @returns the number of elements.
         */
        size_type
        size() const
        {
          return discriminating_map.size();
        }
  
        /**
         * Check if the map is empty.
         *
         * @returns @c true if empty, @c false otherwise.
         */
        bool
        empty() const
        {
          return discriminating_map.empty();
        }
  
        /**
         * Clear the map.
         *
         * All keys are cleared from the map.
         */
        void
        clear()
        {
          return discriminating_map.clear();
        }
      };
  
      namespace detail
      {
  
        /**
         * Visitor template for output of MetadataMap values to an ostream.
         */
        struct MetadataMapValueTypeOStreamVisitor
        {
          /// The stream to output to.
          std::ostream& os;
  
          /**
           * Constructor.
           *
           * @param os the output stream to output to.
           */
          MetadataMapValueTypeOStreamVisitor(std::ostream& os):
            os(os)
          {}
  
          /**
           * Output a vector value of arbitrary type.
           *
           * Each value will be output in a comma-separated list.
           *
           * @param c the container values to output.
           */
          template <typename T>
          void
          operator() (const std::vector<T> & c) const
          {
            for (typename std::vector<T>::const_iterator i = c.begin();
                 i != c.end();
                 ++i)
              {
                os << *i;
                if (i + 1 != c.end())
                  os << ", ";
              }
          }
  
          /**
           * Output a scalar value of arbitrary type.
           *
           * @param v the value to output.
           */
          template <typename T>
          void
          operator() (const T& v) const
          {
            os << v;
          }
        };
  
        /**
         * Visitor template for output of MetadataMap values to an ostream.
         */
        struct MetadataMapOStreamVisitor
        {
          /// The stream to output to.
          std::ostream& os;
          /// The key of the value being output.
          const MetadataMap::key_type& key;
  
          /**
           * Constructor.
           *
           * @param os the output stream to output to.
           * @param key the key being output.
           */
          MetadataMapOStreamVisitor(std::ostream&                os,
                                    const MetadataMap::key_type& key):
            os(os),
            key(key)
          {}
  
          /**
           * Output a vector value of arbitrary type.
           *
           * Each value will be output as a separate line of the form @c
           * "key #n = value", where @c n is the index into the vector,
           * indexed from 1.  The suffix will be left zero-padded to the
           * width of the largest suffix.
           *
           * @param c the container values to output.
           */
          template <typename T>
          void
          operator() (const std::vector<T> & c) const
          {
            typename std::vector<T>::size_type idx = 1;
            // Determine the optimal padding based on the maximum digit count.
            int sf = static_cast<int>(std::log10(static_cast<float>(c.size()))) + 1;
            for (typename std::vector<T>::const_iterator i = c.begin();
                 i != c.end();
                 ++i, ++idx)
              {
                os << key << " #" << std::setw(sf) << std::setfill('0') << std::right << idx << " = " << *i << '\n';
              }
          }
  
          /**
           * Output a scalar value of arbitrary type.
           *
           * The value will be output as a separate line of the form
           * @c "key = value".
           *
           * @param v the value to output.
           */
          template <typename T>
          void
          operator() (const T& v) const
          {
            os << key << " = " << v << '\n';
          }
        };
  
        /**
         * Visitor template for flattening of MetadataMap vector values.
         */
        struct MetadataMapFlattenVisitor
        {
          /// The map in which to set the flattened elements.
          MetadataMap& map;
          /// The key of the value being flattened.
          const MetadataMap::key_type& key;
  
          /**
           * Constructor.
           *
           * @param map the map in which to set flattened elements.
           * @param key the key being output.
           */
          MetadataMapFlattenVisitor(MetadataMap&                 map,
                                    const MetadataMap::key_type& key):
            map(map),
            key(key)
          {}
  
          /**
           * Output a vector value of arbitrary type.
           *
           * Each value will be set as a separate entry of with the a @c
           * \#n suffix added to the key, where @c n is the index into the
           * vector, indexed from 1.  The suffix will be left zero-padded
           * to the width of the largest suffix.
           *
           * @param c the container values to output.
           */
          template <typename T>
          void
          operator() (const std::vector<T> & c) const
          {
            typename std::vector<T>::size_type idx = 1;
            // Determine the optimal padding based on the maximum digit count.
            int sf = static_cast<int>(std::log10(static_cast<float>(c.size()))) + 1;
            for (typename std::vector<T>::const_iterator i = c.begin();
                 i != c.end();
                 ++i, ++idx)
              {
                std::ostringstream os;
                os << key << " #" << std::setw(sf) << std::setfill('0') << std::right << idx;
                map.set(os.str(), *i);
              }
          }
  
          /**
           * Output a scalar value of arbitrary type.
           *
           * The value will be output as a separate line of the form
           * @c "key = value".
           *
           * @param v the value to output.
           */
          template <typename T>
          void
          operator() (const T& v) const
          {
            map.set(key, v);
          }
        };
  
      }
  
      /**
       * Get a reference to the value of a particular key from the map.
       *
       * If the key was not found, or the type of @p value does not
       * match the stored value type, @p value will remain unmodified.
       *
       * @param key the key to find.
       * @returns a reference to the stored value.
       * @throws ome::compat::bad_variant_access on failure if the key
       * was not found or if the type did not match the stored value
       * type.
       */
      template<>
      inline MetadataMap::value_type&
      MetadataMap::get<MetadataMap::value_type>(const key_type& key)
      {
        map_type::iterator i = discriminating_map.find(key);
        if (i == discriminating_map.end())
          throw ome::compat::bad_variant_access();
  
        return i->second;
      }
  
      /**
       * Get a reference to the value of a particular key from the map.
       *
       * If the key was not found, or the type of @p value does not
       * match the stored value type, @p value will remain unmodified.
       *
       * @param key the key to find.
       * @returns a reference to the stored value.
       * @throws ome::compat::bad_variant_access on failure if the key
       * was not found or if the type did not match the stored value
       * type.
       */
      template<>
      inline const MetadataMap::value_type&
      MetadataMap::get<MetadataMap::value_type>(const key_type& key) const
      {
        map_type::const_iterator i = discriminating_map.find(key);
        if (i == discriminating_map.end())
          throw ome::compat::bad_variant_access();
  
        return i->second;
      }
  
      inline
      MetadataMap
      MetadataMap::flatten() const
      {
        MetadataMap newmap;
        for (const auto& m : discriminating_map)
          {
            ome::compat::visit(detail::MetadataMapFlattenVisitor(newmap, m.first), m.second);
          }
        return newmap;
      }
  
    }
  }
  
  namespace std
  {
  
      /**
       * Output MetadataMap::value_type to output stream.
       *
       * @param os the output stream.
       * @param vt the MetadataMap::value_type to output.
       * @returns the output stream.
       */
      template<class charT, class traits>
      inline basic_ostream<charT,traits>&
      operator<< (basic_ostream<charT,traits>& os,
                  const ::ome::files::MetadataMap::value_type& vt)
      {
        ome::compat::visit(::ome::files::detail::MetadataMapValueTypeOStreamVisitor(os), vt);
        return os;
      }
  
      /**
       * Output MetadataMap to output stream.
       *
       * @param os the output stream.
       * @param map the MetadataMap to output.
       * @returns the output stream.
       */
      template<class charT, class traits>
      inline basic_ostream<charT,traits>&
      operator<< (basic_ostream<charT,traits>& os,
                  const ::ome::files::MetadataMap& map)
      {
        for (const auto& m : map)
          {
            ome::compat::visit(::ome::files::detail::MetadataMapOStreamVisitor(os, m.first), m.second);
          }
        return os;
      }
  
  }
  
  #endif // OME_FILES_METADATAMAP_H
  
  /*
   * Local Variables:
   * mode:C++
   * End:
   */
  