/*
 * Copyright (C) 2019 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 GZ_SENSORS_SENSORFACTORY_HH_
#define GZ_SENSORS_SENSORFACTORY_HH_

#include <memory>
#include <string>
#include <type_traits>
#include <sdf/sdf.hh>

#include <gz/common/Console.hh>
#include <gz/utils/SuppressWarning.hh>

#include <gz/sensors/config.hh>
#include <gz/sensors/Export.hh>

#include "gz/sensors/Sensor.hh"

namespace gz
{
  namespace sensors
  {
    // Inline bracket to help doxygen filtering.
    inline namespace GZ_SENSORS_VERSION_NAMESPACE {
    // forward declaration
    class SensorFactoryPrivate;

    /// \brief A factory class for creating sensors
    /// This class instantiates sensor objects based on the sensor type and
    /// makes sure they're initialized correctly.
    // After removing plugin functionality, the sensor factory class doesn't
    // hold any internal state. Consider converting the functionality in this
    // class to helper functions.
    class GZ_SENSORS_VISIBLE SensorFactory
    {
      /// \brief Constructor
      public: SensorFactory();

      /// \brief Destructor
      public: ~SensorFactory();

      /// \brief Create a sensor from a SDF DOM object with a known sensor type.
      ///
      ///   This creates sensors by looking at the given SDF DOM object.
      ///   Sensors created with this API offer an gz-transport interface.
      ///   If you need a direct C++ interface to the data, you must get the
      ///   sensor pointer and cast to the correct type.
      ///
      /// \sa Sensor()
      /// \param[in] _sdf SDF Sensor DOM object.
      /// \tparam SensorType Sensor type
      /// \return A pointer to the created sensor. Null returned on error.
      public: template<typename SensorType>
              std::unique_ptr<SensorType> CreateSensor(const sdf::Sensor &_sdf)
              {
                auto sensor = std::make_unique<SensorType>();

                if (nullptr == sensor)
                {
                  gzerr << "Failed to create sensor [" << _sdf.Name()
                         << "] of type[" << _sdf.TypeStr() << "]" << std::endl;
                  return nullptr;
                }

                if (!sensor->Load(_sdf))
                {
                  gzerr << "Failed to load sensor [" << _sdf.Name()
                         << "] of type[" << _sdf.TypeStr() << "]" << std::endl;
                  return nullptr;
                }

                if (!sensor->Init())
                {
                  gzerr << "Failed to initialize sensor [" << _sdf.Name()
                         << "] of type[" << _sdf.TypeStr() << "]" << std::endl;
                  return nullptr;
                }

                return sensor;
              }

      /// \brief Create a sensor from an SDF element with a known sensor type.
      /// \sa Sensor()
      /// \param[in] _sdf pointer to the sdf element
      /// \tparam SensorType Sensor type
      /// \return A pointer to the created sensor. Null returned on
      /// error.
      public: template<typename SensorType>
              std::unique_ptr<SensorType> CreateSensor(sdf::ElementPtr _sdf)
              {
                if (nullptr == _sdf)
                {
                  gzerr << "Failed to create sensor, received null SDF "
                         << "pointer." << std::endl;
                  return nullptr;
                }

                auto sensor = std::make_unique<SensorType>();

                auto type = _sdf->Get<std::string>("type");
                auto name = _sdf->Get<std::string>("name");

                if (nullptr == sensor)
                {
                  gzerr << "Failed to create sensor [" << name
                         << "] of type[" << type << "]" << std::endl;
                  return nullptr;
                }

                if (!sensor->Load(_sdf))
                {
                  gzerr << "Failed to load sensor [" << name
                         << "] of type[" << type << "]" << std::endl;
                  return nullptr;
                }

                if (!sensor->Init())
                {
                  gzerr << "Failed to initialize sensor [" << name
                         << "] of type[" << type << "]" << std::endl;
                  return nullptr;
                }

                return sensor;
              }

      GZ_UTILS_WARN_IGNORE__DLL_INTERFACE_MISSING
      /// \brief private data pointer
      private: std::unique_ptr<SensorFactoryPrivate> dataPtr;
      GZ_UTILS_WARN_RESUME__DLL_INTERFACE_MISSING
    };
    }
  }
}

#endif
