/*
 * ObjectBox Generator - a build time tool for ObjectBox
 * Copyright (C) 2018-2024 ObjectBox Ltd. All rights reserved.
 * https://objectbox.io
 *
 * This file is part of ObjectBox Generator.
 *
 * ObjectBox Generator is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * ObjectBox Generator 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with ObjectBox Generator.  If not, see <http://www.gnu.org/licenses/>.
 */

package templates

import (
	"text/template"
)

// CppBindingTemplate is used to generated the binding code
var CppBindingTemplate = template.Must(template.New("binding-cpp").Funcs(funcMap).Parse(
	`// Code generated by ObjectBox; DO NOT EDIT.

{{define "field-value"}}{{if .Optional}}*{{end}}object.{{.CppName}}{{end -}}
{{define "field-value-assign-pre"}}{{if IsOptionalPtr .Optional}}.reset(new {{.CppType}}({{else}} = {{end}}{{end -}}
{{define "field-value-assign-post"}}{{if IsOptionalPtr .Optional}})){{end}}{{end -}}

#include "{{.HeaderFile}}"
{{if .NaNAsNull}}
#include <cmath>
{{end -}}
{{range $entity := .Model.EntitiesWithMeta}}
	{{- range $property := $entity.Properties}}
const 
		{{- if $property.RelationTarget}} obx::RelationProperty<{{$entity.Meta.CppNamespacePrefix}}{{$entity.Meta.CppName}}, {{$property.Meta.CppNameRelationTarget}}>
		{{- else}} obx::Property<{{$entity.Meta.CppNamespacePrefix}}{{$entity.Meta.CppName}}, OBXPropertyType_{{PropTypeName $property.Type}}>
		{{- end}} {{$entity.Meta.CppNamespacePrefix}}{{$entity.Meta.CppName}}_::{{$property.Meta.CppName}}({{$property.Id.GetId}});
	{{- end}}
	{{- range $relation := $entity.Relations}}
const obx::RelationStandalone<{{$entity.Meta.CppNamespacePrefix}}{{$entity.Meta.CppName}}, {{$relation.Target.Meta.CppNamespacePrefix}}{{$relation.Target.Meta.CppName}}> {{$entity.Meta.CppNamespacePrefix}}{{$entity.Meta.CppName}}_::{{$relation.Meta.CppName}}({{$relation.Id.GetId}});
	{{- end}}

void {{$entity.Meta.CppNamespacePrefix}}{{$entity.Meta.CppName}}::_OBX_MetaInfo::toFlatBuffer(flatbuffers::FlatBufferBuilder& fbb, const {{$entity.Meta.CppNamespacePrefix}}{{$entity.Meta.CppName}}& object) {
	fbb.Clear();
	{{- range $property := $entity.Properties}}{{$factory := $property.Meta.FbOffsetFactory}}{{if $factory}}
	auto offset{{$property.Meta.CppName}} =
		{{- if $property.Meta.Optional}} !object.{{$property.Meta.CppName}} ? 0 : {{end -}}
		{{- if and $.EmptyStringAsNull (eq "std::string" $property.Meta.CppType) }} ({{template "field-value" $property.Meta}}).empty() ? 0 : 
		{{- end }} fbb.{{$factory}}({{template "field-value" $property.Meta}});
	{{- end}}{{end}}
	flatbuffers::uoffset_t fbStart = fbb.StartTable();
	{{range $property := $entity.Properties}}
	{{- if $property.Meta.Optional}}if (object.{{$property.Meta.CppName}}) {{end}}
	{{- if $property.Meta.FbOffsetFactory}}fbb.AddOffset({{$property.FbvTableOffset}}, offset{{$property.Meta.CppName}});
	{{- else -}}
		{{- if and $.NaNAsNull $property.Meta.FbIsFloatingPoint -}} if (!std::isnan({{template "field-value" $property.Meta}})) {{end -}} fbb.AddElement({{$property.FbvTableOffset}}, {{template "field-value" $property.Meta}}{{if eq "bool" $property.Meta.CppType}} ? 1 : 0{{end}});
	{{- end}}
	{{end -}}
	flatbuffers::Offset<flatbuffers::Table> offset;
	offset.o = fbb.EndTable(fbStart);
	fbb.Finish(offset);
}

{{$entity.Meta.CppNamespacePrefix}}{{$entity.Meta.CppName}} {{$entity.Meta.CppNamespacePrefix}}{{$entity.Meta.CppName}}::_OBX_MetaInfo::fromFlatBuffer(const void* data, size_t size) {
	{{$entity.Meta.CppNamespacePrefix}}{{$entity.Meta.CppName}} object;
	fromFlatBuffer(data, size, object);
	return object;
}

std::unique_ptr<{{$entity.Meta.CppNamespacePrefix}}{{$entity.Meta.CppName}}> {{$entity.Meta.CppNamespacePrefix}}{{$entity.Meta.CppName}}::_OBX_MetaInfo::newFromFlatBuffer(const void* data, size_t size) {
	{{- if ge $.LangVersion 14 }}
	auto object = std::make_unique<{{$entity.Meta.CppNamespacePrefix}}{{$entity.Meta.CppName}}>();
	{{- else}}
	auto object = std::unique_ptr<{{$entity.Meta.CppNamespacePrefix}}{{$entity.Meta.CppName}}>(new {{$entity.Meta.CppNamespacePrefix}}{{$entity.Meta.CppName}}());
	{{- end}}
	fromFlatBuffer(data, size, *object);
	return object;
}

void {{$entity.Meta.CppNamespacePrefix}}{{$entity.Meta.CppName}}::_OBX_MetaInfo::fromFlatBuffer(const void* data, size_t, {{$entity.Meta.CppNamespacePrefix}}{{$entity.Meta.CppName}}& outObject) {
	const auto* table = flatbuffers::GetRoot<flatbuffers::Table>(data);
	assert(table);
	{{- range $property := $entity.Properties}}
		{{- if eq "std::string" $property.Meta.CppType}}
	{
		auto* ptr = table->GetPointer<const flatbuffers::String*>({{$property.FbvTableOffset}});
		if (ptr) {
			outObject.{{$property.Meta.CppName}}
				{{- if $property.Meta.Optional}}
					{{- if IsOptionalPtr $.Optional -}}
						.reset(new std::string(ptr->c_str(), ptr->size()));
					{{- else -}}
						.emplace(ptr->c_str(), ptr->size());
					{{- end}}
				{{- else -}}
					.assign(ptr->c_str(), ptr->size());
				{{- end}}
		} else {
			outObject.{{$property.Meta.CppName}}
			{{- if $property.Meta.Optional -}}
				.reset();
			{{- else -}}
				.clear();
			{{- end}}
		}
	}
		{{- else if eq "std::vector<std::string>" $property.Meta.CppType}}
	{
		auto* ptr = table->GetPointer<const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>*>({{$property.FbvTableOffset}});
		if (ptr) {
			{{- if $property.Meta.Optional}}
			outObject.{{$property.Meta.CppName}}{{if IsOptionalPtr $property.Meta.Optional}}.reset(new {{$property.Meta.CppType}}({{else}} = {{$property.Meta.CppType}}(){{end}}{{template "field-value-assign-post" $property.Meta}};
			{{- end}}
			outObject.{{$property.Meta.CppName}}{{$property.Meta.CppValOp}}reserve(ptr->size());
			for (flatbuffers::uoffset_t i = 0; i < ptr->size(); i++) {
				auto* itemPtr = ptr->Get(i);
				if (itemPtr) outObject.{{$property.Meta.CppName}}{{$property.Meta.CppValOp}}emplace_back(itemPtr->c_str());
			}
		} else {
			outObject.{{$property.Meta.CppName}}
			{{- if $property.Meta.Optional -}}
				.reset();
			{{- else -}}
				.clear();
			{{- end}}
		}
	}
		{{- else if $property.Meta.FbIsVector}}
	{
		auto* ptr = table->GetPointer<const {{$property.Meta.FbOffsetType}}*>({{$property.FbvTableOffset}});
		if (ptr) { 
			outObject.{{$property.Meta.CppName}}
			{{- if IsOptionalPtr $property.Meta.Optional}}{{template "field-value-assign-pre" $property.Meta}}ptr->begin(), ptr->end(){{template "field-value-assign-post" $property.Meta}}
			{{- else if $property.Meta.Optional}} = {{$property.Meta.CppType}}(ptr->begin(), ptr->end())
			{{- else}}.assign(ptr->begin(), ptr->end())
			{{- end}};
		} else {
			outObject.{{$property.Meta.CppName}}
			{{- if $property.Meta.Optional -}}
				.reset();
			{{- else -}}
				.clear();
			{{- end}}
		}
	}
		{{- else }}
	{{			if $property.Meta.Optional -}}
	if (table->CheckField({{$property.FbvTableOffset}})) {{end -}} 
	outObject.{{$property.Meta.CppName}}
			{{- template "field-value-assign-pre" $property.Meta -}}
		table->GetField<{{$property.Meta.CppFbType}}>({{- $property.FbvTableOffset}}, {{$property.Meta.FbDefaultValue}}){{if eq "bool" $property.Meta.CppType}} != 0{{end}}
			{{- template "field-value-assign-post" $property.Meta}};
			{{- if $property.Meta.Optional}} else outObject.{{$property.Meta.CppName}}.reset();{{- end}}
		{{- end }}
	{{- end}}
}
{{end}}
`))
