// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level
// directory of this distribution and at http://opencv.org/license.html.

#ifndef OPENCV_UTILS_TLS_HPP
#define OPENCV_UTILS_TLS_HPP

#ifndef OPENCV_CORE_UTILITY_H
#error                                                                         \
    "tls.hpp must be included after opencv2/core/utility.hpp or opencv2/core.hpp"
#endif

namespace cv {

//! @addtogroup core_utils
//! @{

namespace details {
class TlsStorage;
}

/** TLS container base implementation
 *
 * Don't use directly.
 *
 * @sa TLSData, TLSDataAccumulator templates
 */
class CV_EXPORTS TLSDataContainer {
protected:
  TLSDataContainer();
  virtual ~TLSDataContainer();

  /// @deprecated use detachData() instead
  void gatherData(std::vector<void *> &data) const;
  /// get TLS data and detach all data from threads (similar to cleanup() call)
  void detachData(std::vector<void *> &data);

  void *getData() const;
  void release();

protected:
  virtual void *createDataInstance() const = 0;
  virtual void deleteDataInstance(void *pData) const = 0;

private:
  int key_;

  friend class cv::details::TlsStorage; // core/src/system.cpp

public:
  void cleanup(); //!< Release created TLS data container objects. It is similar
                  //!< to release() call, but it keeps TLS container valid.

private:
  // Disable copy/assign (noncopyable pattern)
  TLSDataContainer(TLSDataContainer &) = delete;
  TLSDataContainer &operator=(const TLSDataContainer &) = delete;
};

/** @brief Simple TLS data class
 *
 * @sa TLSDataAccumulator
 */
template <typename T> class TLSData : protected TLSDataContainer {
public:
  inline TLSData() {}
  inline ~TLSData() { release(); }

  inline T *get() const {
    return (T *)getData();
  } //!< Get data associated with key
  inline T &getRef() const {
    T *ptr = (T *)getData();
    CV_DbgAssert(ptr);
    return *ptr;
  } //!< Get data associated with key

  /// Release associated thread data
  inline void cleanup() { TLSDataContainer::cleanup(); }

protected:
  /// Wrapper to allocate data by template
  virtual void *createDataInstance() const CV_OVERRIDE { return new T; }
  /// Wrapper to release data by template
  virtual void deleteDataInstance(void *pData) const CV_OVERRIDE {
    delete (T *)pData;
  }
};

/// TLS data accumulator with gathering methods
template <typename T> class TLSDataAccumulator : public TLSData<T> {
  mutable cv::Mutex mutex;
  mutable std::vector<T *> dataFromTerminatedThreads;
  std::vector<T *> detachedData;
  bool cleanupMode;

public:
  TLSDataAccumulator() : cleanupMode(false) {}
  ~TLSDataAccumulator() { release(); }

  /** @brief Get data from all threads
   * @deprecated replaced by detachData()
   *
   * Lifetime of vector data is valid until next
   * detachData()/cleanup()/release() calls
   *
   * @param[out] data result buffer (should be empty)
   */
  void gather(std::vector<T *> &data) const {
    CV_Assert(cleanupMode == false); // state is not valid
    CV_Assert(data.empty());
    {
      std::vector<void *> &dataVoid =
          reinterpret_cast<std::vector<void *> &>(data);
      TLSDataContainer::gatherData(dataVoid);
    }
    {
      AutoLock lock(mutex);
      data.reserve(data.size() + dataFromTerminatedThreads.size());
      for (typename std::vector<T *>::const_iterator i =
               dataFromTerminatedThreads.begin();
           i != dataFromTerminatedThreads.end(); ++i) {
        data.push_back((T *)*i);
      }
    }
  }

  /** @brief Get and detach data from all threads
   *
   * Call cleanupDetachedData() when returned vector is not needed anymore.
   *
   * @return Vector with associated data. Content is preserved (including
   * lifetime of attached data pointers) until next
   * detachData()/cleanupDetachedData()/cleanup()/release() calls
   */
  std::vector<T *> &detachData() {
    CV_Assert(cleanupMode == false); // state is not valid
    std::vector<void *> dataVoid;
    {
      TLSDataContainer::detachData(dataVoid);
    }
    {
      AutoLock lock(mutex);
      detachedData.reserve(dataVoid.size() + dataFromTerminatedThreads.size());
      for (typename std::vector<T *>::const_iterator i =
               dataFromTerminatedThreads.begin();
           i != dataFromTerminatedThreads.end(); ++i) {
        detachedData.push_back((T *)*i);
      }
      dataFromTerminatedThreads.clear();
      for (typename std::vector<void *>::const_iterator i = dataVoid.begin();
           i != dataVoid.end(); ++i) {
        detachedData.push_back((T *)(void *)*i);
      }
    }
    dataVoid.clear();
    return detachedData;
  }

  /// Release associated thread data returned by detachData() call
  void cleanupDetachedData() {
    AutoLock lock(mutex);
    cleanupMode = true;
    _cleanupDetachedData();
    cleanupMode = false;
  }

  /// Release associated thread data
  void cleanup() {
    cleanupMode = true;
    TLSDataContainer::cleanup();

    AutoLock lock(mutex);
    _cleanupDetachedData();
    _cleanupTerminatedData();
    cleanupMode = false;
  }

  /// Release associated thread data and free TLS key
  void release() {
    cleanupMode = true;
    TLSDataContainer::release();
    {
      AutoLock lock(mutex);
      _cleanupDetachedData();
      _cleanupTerminatedData();
    }
  }

protected:
  // synchronized
  void _cleanupDetachedData() {
    for (typename std::vector<T *>::iterator i = detachedData.begin();
         i != detachedData.end(); ++i) {
      deleteDataInstance((T *)*i);
    }
    detachedData.clear();
  }

  // synchronized
  void _cleanupTerminatedData() {
    for (typename std::vector<T *>::iterator i =
             dataFromTerminatedThreads.begin();
         i != dataFromTerminatedThreads.end(); ++i) {
      deleteDataInstance((T *)*i);
    }
    dataFromTerminatedThreads.clear();
  }

protected:
  virtual void *createDataInstance() const CV_OVERRIDE {
    // Note: we can collect all allocated data here, but this would require
    // raced mutex locks
    return new T;
  }
  virtual void deleteDataInstance(void *pData) const CV_OVERRIDE {
    if (cleanupMode) {
      delete (T *)pData;
    } else {
      AutoLock lock(mutex);
      dataFromTerminatedThreads.push_back((T *)pData);
    }
  }
};

//! @}

} // namespace cv

#endif // OPENCV_UTILS_TLS_HPP
