/*
 * Copyright (C) 2015 Open Source Robotics Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
*/
#ifndef _GAZEBO_ENUMITERATOR_HH_
#define _GAZEBO_ENUMITERATOR_HH_

#include <string>
#include <vector>
#include <algorithm>
#include "gazebo/util/system.hh"
#include "gazebo/common/Assert.hh"

namespace gazebo
{
  namespace common
  {
    /// \brief A macro that allows an enum to have an iterator and string
    /// conversion.
    /// \param[in] enumType Enum type
    /// \param[in] begin Enum value that marks the beginning of the enum
    /// values.
    /// \param[in] end Enum value that marks the end of the enum values.
    /// \param[in] names A vector of strings, one for each enum value.
    /// \sa EnumIface
    /// \sa EnumIterator
    #define GZ_ENUM(enumType, begin, end, ...) \
    template<> GZ_COMMON_VISIBLE enumType \
    common::EnumIface<enumType>::range[] = {begin, end}; \
    template<> GZ_COMMON_VISIBLE \
    std::vector<std::string> common::EnumIface<enumType>::names = {__VA_ARGS__};

#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundefined-var-template"
#endif
    /// \brief Enum interface. Use this interface to convert an enum to
    /// a string, and set an enum from a string.
    template<typename T>
    class EnumIface
    {
      /// \brief Get the beginning enum value.
      /// \return Enum value that marks the beginning of the enum list.
      public: static T Begin()
      {
        return range[0];
      }

      /// \brief Get the end enum value.
      /// \return Enum value that marks the end of the enum list.
      public: static T End()
      {
        return range[1];
      }

      /// \brief Convert enum value to string.
      /// \param[in] _e Enum value to convert.
      /// \return String representation of the enum. An empty string is
      /// returned if _e is invalid, or the names for the enum have not been
      /// set.
      static std::string Str(T const &_e)
      {
        if (static_cast<unsigned int>(_e) < names.size())
          return names[static_cast<unsigned int>(_e)];
        else
          return "";
      }

      /// \brief Set an enum from a string. This function requires a valid
      /// string, and an array of names for the enum must exist.
      /// \param[in] _str String value to convert to enum value.
      /// \param[in] _e Enum variable to set.
      /// \sa EnumIterator
      static void Set(T &_e, const std::string &_str)
      {
        static auto begin = std::begin(names);
        static auto end = std::end(names);

        auto find = std::find(begin, end, _str);
        if (find != end)
        {
          _e = static_cast<T>(std::distance(begin, find));
        }
      }

      /// \internal
      /// \brief The beginning and end values. Do not use this directly.
      public: static T range[2];

      /// \internal
      /// \brief Array of string names for each element in the enum. Do not
      /// use this directly.
      public: static std::vector<std::string> names;
    };
#ifdef __clang__
#pragma clang diagnostic pop
#endif

    /// \brief An iterator over enum types.
    ///
    ///  Example:
    ///
    /// \verbatim
    /// enum MyType
    /// {
    ///   MY_TYPE_BEGIN = 0,
    ///   TYPE1 = MY_TYPE_BEGIN,
    ///   TYPE2 = 1,
    ///   MY_TYPE_END
    /// };
    ///
    /// GZ_ENUM(MyType, MY_TYPE_BEGIN, MY_TYPE_END,
    ///  "TYPE1",
    ///  "TYPE2",
    ///  "MY_TYPE_END")
    ///
    /// int main()
    /// {
    ///   EnumIface<MyType> i = MY_TYPE_BEGIN;
    ///   std::cout << "Type Number[" << *i << "]\n";
    ///   std::cout << "Type Name[" << EnumIface::Str(*i) << "]\n";
    ///   i++;
    ///   std::cout << "Type++ Number[" << *i << "]\n";
    ///   std::cout << "Type++ Name[" << EnumIface::Str(*i) << "]\n";
    /// }
    /// \verbatim
    template<typename Enum>
    class EnumIterator
    : std::iterator<std::bidirectional_iterator_tag, Enum>
    {
      /// \brief Constructor
      public: EnumIterator() : c(this->End())
      {
      }

      /// \brief Constructor
      /// \param[in] _c Enum value
      // cppcheck-suppress noExplicitConstructor
      public: EnumIterator(const Enum _c) : c(_c)
      {
        GZ_ASSERT(this->c >= this->Begin() && this->c <= this->End(),
            "Invalid enum value in EnumIterator constructor");
      }

      /// \brief Equal operator
      /// \param[in] _c Enum value to copy
      public: EnumIterator &operator=(const Enum _c)
      {
        GZ_ASSERT(_c >= this->Begin() && _c <= this->End(),
            "Invalid operator= value in EnumIterator");
        this->c = _c;
        return *this;
      }

      /// \brief Get the beginning of the enum
      /// \return Value at the beginning of the enum list
      public: static Enum Begin()
      {
        return EnumIface<Enum>::Begin();
      }

      /// \brief Get the end of the enum
      /// \return Value at the end of the enum list
      public: static Enum End()
      {
        return EnumIface<Enum>::End();
      }

      /// \brief Prefix increment operator.
      /// \return Iterator pointing to the next value in the enum.
      public: EnumIterator &operator++()
      {
        GZ_ASSERT(this->c != this->End(), "Incrementing past end of enum");
        this->c = static_cast<Enum>(static_cast<int>(this->c) + 1);
        return *this;
      }

      /// \brief Postfix increment operator.
      /// \return Iterator pointing to the next value in the enum.
      public: EnumIterator operator++(const int)
      {
        GZ_ASSERT(this->c != this->End(), "Incrementing past end of enum");
        EnumIterator cpy(*this);
        ++*this;
        return cpy;
      }

      /// \brief Prefix decrement operator
      /// \return Iterator pointing to the previous value in the enum
      public: EnumIterator &operator--()
      {
        GZ_ASSERT(this->c != this->Begin(), "decrementing beyond begin?");
        this->c = static_cast<Enum>(static_cast<int>(this->c) - 1);
        return *this;
      }

      /// \brief Postfix decrement operator
      /// \return Iterator pointing to the previous value in the enum
      public: EnumIterator operator--(const int)
      {
        GZ_ASSERT(this->c != this->Begin(), "Decrementing beyond beginning.");
        EnumIterator cpy(*this);
        --*this;
        return cpy;
      }

      /// \brief Dereference operator
      /// \return Value of the iterator
      public: Enum operator*() const
      {
        GZ_ASSERT(this->c != this->End(), "Cannot dereference end iterator");
        return c;
      }

      /// \brief Get the enum value.
      /// \return Value of the iterator
      public: Enum Value() const
      {
        return this->c;
      }

      /// \brief Enum value
      /// Did not use a private data class since this should be the only
      /// member value ever used.
      private: Enum c;
    };

    /// \brief Equality operator
    /// \param[in] _e1 First iterator
    /// \param[in] _e1 Second iterator
    /// \return True if the two iterators contain equal enum values.
    template<typename Enum>
    bool operator==(EnumIterator<Enum> _e1, EnumIterator<Enum> _e2)
    {
      return _e1.Value() == _e2.Value();
    }

    /// \brief Inequality operator
    /// \param[in] _e1 First iterator
    /// \param[in] _e1 Second iterator
    /// \return True if the two iterators do not contain equal enum values.
    template<typename Enum>
    bool operator!=(EnumIterator<Enum> _e1, EnumIterator<Enum> _e2)
    {
      return !(_e1 == _e2);
    }
  }
}
#endif
