#ifndef AMPL_SCOPEDARRAY_H
#define AMPL_SCOPEDARRAY_H

#include "ampl/ep/declarations.h"
#include "ampl/ep/error_information.h"
#include "ampl/format.h"

namespace ampl {
namespace internal {
/**
Represent a string literal with size
*/
struct PODStringRef {
  const char* ptr_;
  std::size_t size_;
};

inline PODStringRef StringRefToPOD(fmt::StringRef ref) {
  PODStringRef r = {ref.data(), ref.size()};
  return r;
}

extern "C" {
AMPLAPI void AMPL_DeleteArrayStrings(const char* const* ptr);
AMPLAPI const char** AMPL_CreateArrayStrings(std::size_t size,
                                             ErrorInformation* errorInfo);
/*
Get a copy of the source string, inside the library boundary.
Note that a terminating \0 is added at position [size]
of the copy.
*/
AMPLAPI const char* AMPL_CopyString(const char* source, std::size_t size,
                                    ErrorInformation* errorInfo);
AMPLAPI void AMPL_DeleteString(const char* s);

}  // extern "C"

template <typename TYPE>
struct InDLL;
template <>
struct InDLL<char const*> {
  static void deletearray(const char* const* ptr) {
    AMPL_DeleteArrayStrings(ptr);
  }
};
template <>
struct InDLL<const char> {
  static void deletearray(const char* ptr) { AMPL_DeleteString(ptr); }
};

template <typename T>
class ScopedArray {
  template <typename Y>
  struct ScopedArray_ref {
    Y* yp;
    ScopedArray_ref(Y* rhs) : yp(rhs) {}
  };
  T* ptr_;

 public:
  /**
  Copy constructors invalidate the passed scoped pointer, transferring
  the ownership to the copy
  */
  ScopedArray(ScopedArray& other) : ptr_(other.release()) {}

  template <class Y>
  ScopedArray(ScopedArray<Y>& other) : ptr_(other.release()) {}
  /**
  Assignment (passes ownership to the assigned pointer)
  */
  ScopedArray& operator=(ScopedArray& other) {
    reset(other.release());
    return *this;
  }
  /**
  Assignment (passes ownership to the assigned pointer)
  */
  template <class Y>
  ScopedArray& operator=(ScopedArray<Y>& other) {
    reset(other.release());
    return *this;
  }

  explicit ScopedArray(T* ptr) : ptr_(ptr) {}

  ScopedArray() : ptr_(NULL) {}

  ~ScopedArray() { InDLL<T>::deletearray(ptr_); }

  T& operator[](std::size_t index) const { return ptr_[index]; }
  T* get() const { return ptr_; }

  T* release() {
    T* tmp(ptr_);
    ptr_ = 0;
    return tmp;
  }
  void reset(T* ptr) {
    if (ptr_ != ptr) {
      InDLL<T>::deletearray(ptr_);
      ptr_ = ptr;
    }
  }
  /* special conversions with auxiliary type to enable copies and assignments
   */
  ScopedArray(ScopedArray_ref<T> other) : ptr_(other.yp) {}
  ScopedArray& operator=(ScopedArray_ref<T> other) {
    reset(other.yp);
    return *this;
  }

  template <class Y>
  operator ScopedArray_ref<Y>() {
    return ScopedArray_ref<Y>(release());
  }
  template <class Y>
  operator ScopedArray<Y>() throw() {
    return ScopedArray<Y>(release());
  }
};

inline std::string getStringFromDLL(const char* internalArray) {
  if (internalArray == NULL) return "";
  return std::string(internal::ScopedArray<const char>(internalArray).get());
}

}  // namespace internal
}  // namespace ampl
#endif  // AMPL_SCOPEDARRAY_H
