##! Functions related to synchronous storage operations.

%%{
#include "zeek/storage/Backend.h"
#include "zeek/storage/Manager.h"
#include "zeek/storage/ReturnCode.h"

using namespace zeek;
using namespace zeek::storage;

static void wait_for_result(OperationResult& op_result, BackendPtr& backend, ResultCallback* cb) {
	if ( op_result.code == ReturnCode::IN_PROGRESS && ! backend->SupportsSync() ) {
		backend->Poll();
		op_result = cb->Result();
	}
}
%%}

module Storage::Sync;

function Storage::Sync::__open_backend%(btype: Storage::Backend, options: any, key_type: any, val_type: any%): Storage::OperationResult
	%{
	auto btype_val = IntrusivePtr<EnumVal>{NewRef{}, btype->AsEnumVal()};
	Tag tag{btype_val};

	auto b = storage_mgr->InstantiateBackend(tag);

	if ( ! b.has_value() ) {
		emit_builtin_error(b.error().c_str());
		return val_mgr->Bool(false);
	}

	auto bh = make_intrusive<storage::detail::BackendHandleVal>(b.value());

	auto cb = new OpenResultCallback(bh);
	auto kt = key_type->AsTypeVal()->GetType()->AsTypeType()->GetType();
	auto vt = val_type->AsTypeVal()->GetType()->AsTypeType()->GetType();
	auto options_val = IntrusivePtr<RecordVal>{NewRef{}, options->AsRecordVal()};
	auto op_result = storage_mgr->OpenBackend(b.value(), cb, options_val, kt, vt);

	// Potentially wait for a result if the backend only supports async.
	wait_for_result(op_result, b.value(), cb);

	delete cb;

	return op_result.BuildVal();
	%}

function Storage::Sync::__close_backend%(backend: opaque of Storage::BackendHandle%) : Storage::OperationResult
	%{
	OperationResult op_result;

	auto b = storage::detail::BackendHandleVal::CastFromAny(backend);
	if ( ! b )
		op_result = b.error();
	else {
		auto cb = new ResultCallback();
		op_result = storage_mgr->CloseBackend((*b)->backend, cb);

		// Potentially wait for a result if the backend only supports async.
		wait_for_result(op_result, (*b)->backend, cb);

		delete cb;
	}

	return op_result.BuildVal();
	%}

function Storage::Sync::__put%(backend: opaque of Storage::BackendHandle, key: any, value: any,
			 overwrite: bool, expire_time: interval%): Storage::OperationResult
	%{
	OperationResult op_result;

	auto b = storage::detail::BackendHandleVal::CastFromAny(backend);
	if ( ! b )
		op_result = b.error();
	else {
		if ( expire_time > 0.0 )
			expire_time += run_state::network_time;

		auto cb = new ResultCallback();
		auto key_v = IntrusivePtr<Val>{NewRef{}, key};
		auto val_v = IntrusivePtr<Val>{NewRef{}, value};
		op_result = (*b)->backend->Put(cb, key_v, val_v, overwrite, expire_time);

		// Potentially wait for a result if the backend only supports async.
		wait_for_result(op_result, (*b)->backend, cb);

		delete cb;
	}

	return op_result.BuildVal();
	%}

function Storage::Sync::__get%(backend: opaque of Storage::BackendHandle, key: any%): Storage::OperationResult
	%{
	OperationResult op_result;

	auto b = storage::detail::BackendHandleVal::CastFromAny(backend);
	if ( ! b )
		op_result = b.error();
	else {
		auto cb = new ResultCallback();
		auto key_v = IntrusivePtr<Val>{NewRef{}, key};
		op_result = (*b)->backend->Get(cb, key_v);

		// Potentially wait for a result if the backend only supports async.
		wait_for_result(op_result, (*b)->backend, cb);

		delete cb;
	}

	return op_result.BuildVal();
	%}

function Storage::Sync::__erase%(backend: opaque of Storage::BackendHandle, key: any%): Storage::OperationResult
	%{
	OperationResult op_result;

	auto b = storage::detail::BackendHandleVal::CastFromAny(backend);
	if ( ! b )
		op_result = b.error();
	else {
		auto cb = new ResultCallback();
		auto key_v = IntrusivePtr<Val>{NewRef{}, key};
		op_result = (*b)->backend->Erase(cb, key_v);

		// Potentially wait for a result if the backend only supports async.
		wait_for_result(op_result, (*b)->backend, cb);

		delete cb;
	}

	return op_result.BuildVal();
	%}
