From 90021e1d335efac1017562c1d5dee43e99580319 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Roman=20Smr=C5=BE?= <roman.smrz@seznam.cz>
Date: Wed, 22 Jul 2020 22:13:33 +0200
Subject: Local and shared state

---
 include/erebos/identity.h |  10 +++-
 include/erebos/state.h    |  55 ++++++++++++++++++++
 src/CMakeLists.txt        |   1 +
 src/identity.cpp          |  11 ++++
 src/state.cpp             | 127 ++++++++++++++++++++++++++++++++++++++++++++++
 src/state.h               |  32 ++++++++++++
 6 files changed, 235 insertions(+), 1 deletion(-)
 create mode 100644 include/erebos/state.h
 create mode 100644 src/state.cpp
 create mode 100644 src/state.h

diff --git a/include/erebos/identity.h b/include/erebos/identity.h
index 7ba3f29..66a4afb 100644
--- a/include/erebos/identity.h
+++ b/include/erebos/identity.h
@@ -7,8 +7,14 @@ namespace erebos {
 class Identity
 {
 public:
+	Identity(const Identity &) = default;
+	Identity(Identity &&) = default;
+	Identity & operator=(const Identity &) = default;
+	Identity & operator=(Identity &&) = default;
+
 	static std::optional<Identity> load(const Ref &);
 	static std::optional<Identity> load(const std::vector<Ref> &);
+	std::vector<Ref> store(const Storage & st) const;
 
 	std::optional<std::string> name() const;
 	std::optional<Identity> owner() const;
@@ -38,9 +44,11 @@ public:
 	static Builder create(const Storage &);
 	Builder modify() const;
 
+	static const UUID sharedTypeId;
+
 private:
 	struct Priv;
-	const std::shared_ptr<const Priv> p;
+	std::shared_ptr<const Priv> p;
 	Identity(const Priv * p);
 	Identity(std::shared_ptr<const Priv> && p);
 };
diff --git a/include/erebos/state.h b/include/erebos/state.h
new file mode 100644
index 0000000..543e03c
--- /dev/null
+++ b/include/erebos/state.h
@@ -0,0 +1,55 @@
+#pragma once
+
+#include <erebos/identity.h>
+#include <erebos/uuid.h>
+
+#include <optional>
+
+namespace erebos {
+
+using std::optional;
+using std::vector;
+
+class LocalState
+{
+public:
+	LocalState();
+	explicit LocalState(const Ref &);
+	static LocalState load(const Ref & ref) { return LocalState(ref); }
+	Ref store(const Storage &) const;
+
+	static const UUID headTypeId;
+
+	const optional<Identity> & identity() const;
+	LocalState identity(const Identity &) const;
+
+	template<class T> optional<T> shared() const;
+	template<class T> LocalState shared(const vector<Stored<T>> &) const;
+	template<class T> LocalState shared(const Stored<T> & x) const { return shared({ x }); };
+	template<class T> LocalState shared(const Storage & st, const T & x)
+	{ return updateShared(T::sharedTypeId, x.store(st)); }
+
+private:
+	vector<Ref> lookupShared(UUID) const;
+	LocalState updateShared(UUID, const vector<Ref> &) const;
+
+	struct Priv;
+	std::shared_ptr<Priv> p;
+};
+
+template<class T>
+optional<T> LocalState::shared() const
+{
+	return T::load(lookupShared(T::sharedTypeId));
+}
+
+template<class T>
+LocalState LocalState::shared(const vector<Stored<T>> & v) const
+{
+	vector<Ref> refs;
+	for (const auto x : v)
+		refs.push_back(x.ref());
+	return updateShared(T::sharedTypeId, refs);
+}
+
+}
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 64c14b5..b97293c 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -9,6 +9,7 @@ add_library(erebos
 	network
 	pubkey
 	service
+	state
 	storage
 	time
 	uuid
diff --git a/src/identity.cpp b/src/identity.cpp
index 2396b9f..7364a8b 100644
--- a/src/identity.cpp
+++ b/src/identity.cpp
@@ -11,6 +11,8 @@ using std::nullopt;
 using std::runtime_error;
 using std::set;
 
+const UUID Identity::sharedTypeId { "0c6c1fe0-f2d7-4891-926b-c332449f7871" };
+
 Identity::Identity(const Priv * p): p(p) {}
 Identity::Identity(shared_ptr<const Priv> && p): p(std::move(p)) {}
 
@@ -32,6 +34,15 @@ optional<Identity> Identity::load(const vector<Ref> & refs)
 	return nullopt;
 }
 
+vector<Ref> Identity::store(const Storage & st) const
+{
+	vector<Ref> res;
+	res.reserve(p->data.size());
+	for (const auto & x : p->data)
+		res.push_back(x.store(st));
+	return res;
+}
+
 optional<string> Identity::name() const
 {
 	return p->name.get();
diff --git a/src/state.cpp b/src/state.cpp
new file mode 100644
index 0000000..8790dfc
--- /dev/null
+++ b/src/state.cpp
@@ -0,0 +1,127 @@
+#include "state.h"
+
+#include "identity.h"
+
+using namespace erebos;
+
+using std::make_shared;
+
+const UUID LocalState::headTypeId { "1d7491a9-7bcb-4eaa-8f13-c8c4c4087e4e" };
+
+LocalState::LocalState():
+	p(make_shared<Priv>())
+{}
+
+LocalState::LocalState(const Ref & ref):
+	LocalState()
+{
+	auto rec = ref->asRecord();
+	if (!rec)
+		return;
+
+	if (auto x = rec->item("id").asRef())
+		p->identity = Identity::load(*x);
+
+	for (auto i : rec->items("shared"))
+		if (const auto & x = i.as<SharedState>())
+			p->shared.push_back(*x);
+}
+
+Ref LocalState::store(const Storage & st) const
+{
+	vector<Record::Item> items;
+
+	if (p->identity)
+		items.emplace_back("id", *p->identity->ref());
+	for (const auto & x : p->shared)
+		items.emplace_back("shared", x);
+
+	return st.storeObject(Record(std::move(items)));
+}
+
+const optional<Identity> & LocalState::identity() const
+{
+	return p->identity;
+}
+
+LocalState LocalState::identity(const Identity & id) const
+{
+	LocalState ret;
+	ret.p->identity = id;
+	ret.p->shared = p->shared;
+	return ret;
+}
+
+vector<Ref> LocalState::lookupShared(UUID type) const
+{
+	vector<Stored<SharedState>> found;
+	vector<Stored<SharedState>> process = p->shared;
+
+	while (!process.empty()) {
+		auto cur = std::move(process.back());
+		process.pop_back();
+
+		if (cur->type == type) {
+			found.push_back(std::move(cur));
+			continue;
+		}
+
+		for (const auto & x : cur->prev)
+			process.push_back(x);
+	}
+
+	filterAncestors(found);
+	vector<Ref> res;
+	for (const auto & s : found)
+		for (const auto & v : s->value)
+			res.push_back(v);
+	return res;
+}
+
+LocalState LocalState::updateShared(UUID type, const vector<Ref> & xs) const
+{
+	const Storage * st;
+	if (xs.size() > 0)
+		st = &xs[0].storage();
+	else if (p->shared.size() > 0)
+		st = &p->shared[0].ref().storage();
+	else
+		return *this;
+
+	LocalState ret;
+	ret.p->identity = p->identity;
+	ret.p->shared.push_back(SharedState(p->shared, type, xs).store(*st));
+	return ret;
+}
+
+
+SharedState::SharedState(const Ref & ref)
+{
+	auto rec = ref->asRecord();
+	if (!rec)
+		return;
+
+	for (auto i : rec->items("PREV"))
+		if (const auto & x = i.as<SharedState>())
+			prev.push_back(*x);
+
+	if (auto x = rec->item("type").asUUID())
+		type = *x;
+
+	for (auto i : rec->items("value"))
+		if (const auto & x = i.asRef())
+			value.push_back(*x);
+}
+
+Ref SharedState::store(const Storage & st) const
+{
+	vector<Record::Item> items;
+
+	for (const auto & x : prev)
+		items.emplace_back("PREV", x);
+	items.emplace_back("type", type);
+	for (const auto & x : value)
+		items.emplace_back("value", x);
+
+	return st.storeObject(Record(std::move(items)));
+}
diff --git a/src/state.h b/src/state.h
new file mode 100644
index 0000000..d2c89fc
--- /dev/null
+++ b/src/state.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <erebos/state.h>
+
+#include "pubkey.h"
+
+using std::optional;
+using std::vector;
+
+namespace erebos {
+
+struct LocalState::Priv
+{
+	optional<Identity> identity;
+	vector<Stored<struct SharedState>> shared;
+};
+
+struct SharedState
+{
+	explicit SharedState(vector<Stored<SharedState>> prev,
+			UUID type, vector<Ref> value):
+		prev(prev), type(type), value(value) {}
+	explicit SharedState(const Ref &);
+	static SharedState load(const Ref & ref) { return SharedState(ref); }
+	Ref store(const Storage &) const;
+
+	vector<Stored<SharedState>> prev;
+	UUID type;
+	vector<Ref> value;
+};
+
+}
-- 
cgit v1.2.3