/*
 * Copyright (c) 2024, Oracle and/or its affiliates.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2.0,
 * as published by the Free Software Foundation.
 *
 * This program is designed to work with certain software (including
 * but not limited to OpenSSL) that is licensed under separate terms,
 * as designated in a particular file or component or in included license
 * documentation.  The authors of MySQL hereby grant you an additional
 * permission to link the program and your derivative works with the
 * separately licensed software that they have either included with
 * the program or referenced in the documentation.
 *
 * This program is distributed in the hope that it will be useful,  but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
 * the GNU General Public License, version 2.0, for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "mysqlshdk/scripting/polyglot/utils/polyglot_store.h"

#include <cassert>
#include <utility>

#include "mysqlshdk/scripting/polyglot/utils/polyglot_api_clean.h"

#include "mysqlshdk/libs/utils/logger.h"
#include "mysqlshdk/scripting/polyglot/utils/polyglot_utils.h"

namespace shcore {
namespace polyglot {

Store::Store(poly_thread thread, poly_handle object) : m_thread{thread} {
  if (nullptr != object) {
    throw_if_error(poly_create_reference, m_thread, object, &m_reference);
  }
}

Store &Store::operator=(Store &&other) noexcept {
  reset(false);

  m_thread = std::exchange(other.m_thread, nullptr);
  m_reference = std::exchange(other.m_reference, nullptr);

  return *this;
}

Store::Store(Store &&other) noexcept { *this = std::move(other); }

Store &Store::reset(bool throw_on_error) {
  if (m_reference) {
    if (const auto rc = poly_delete_reference(m_thread, m_reference);
        rc != poly_ok) {
      auto exception = Polyglot_error(m_thread, rc);
      if (throw_on_error) {
        throw exception;
      } else {
        log_error("polyglot error deleting stored reference: %s",
                  exception.format().c_str());
      }
    }
    m_reference = nullptr;
  }

  m_thread = nullptr;

  return *this;
}

Polyglot_storage::Polyglot_storage(poly_thread thread) : m_thread{thread} {}

poly_reference Polyglot_storage::store(poly_handle value) {
  std::lock_guard lock{m_mutex};

  assert(!m_cleared);

  auto stored = Store(m_thread, value);
  auto result = m_stored_values.emplace(stored.get(), std::move(stored));

  if (!result.second) {
    throw std::logic_error("Unable to save stored polyglot handle!");
  }

  return result.first->first;
}

void Polyglot_storage::erase(poly_reference value) {
  std::lock_guard lock{m_mutex};

  if (m_cleared) {
    return;
  }

  if (const auto it = m_stored_values.find(value);
      it == m_stored_values.end()) {
    throw std::logic_error("Attempt to delete an unknown reference in store!");
  } else {
    m_stored_values.erase(it);
  }
}

Polyglot_storage::~Polyglot_storage() { clear(); }

void Polyglot_storage::clear() {
  std::lock_guard lock{m_mutex};

  if (m_cleared) {
    return;
  }

  m_stored_values.clear();
  m_cleared = true;
}

}  // namespace polyglot
}  // namespace shcore
