/**
 * An implementation of the intrusive AVL tree to store a sequence of elements.
 * The inorder traversal of the tree coincides with the sequence.
 */

#pragma once

#include <iterator>
#include <string>
#include <iostream>
#include <algorithm>
#include <utility>
#include <functional>
#include <type_traits>
#include <boost/optional.hpp>

namespace Snu {
namespace Cnrc {
namespace AVLSequence {

class AVLSequenceModule {

public:

	template<class T, class SizeType>
	class AVLSequenceNodeMixin;

	/**
	 * This trait defines an interface for the type `T` to be stored in an intrusive AVL tree.
	 * If `T` extends AVLSequenceNodeMixin<T>, the default implementation of the trait will just work.
	 * If not, a specialization of this template for `T` must be provided.
	 */
	template<class T>
	class AVLSequenceNodeTrait {

	private:

		using NodeMixin = AVLSequenceNodeMixin<T, typename T::Size>;

		static_assert(
			std::is_base_of<NodeMixin, T>::value,
			"The default implementation of AVLSequenceNodeTrait<T> requires T extending AVLSequenceNodeMixin. "
			"Or you can specialize AVLSequenceNodeTrait for T"
		);

	public:

		using Node = T;
		using Size = typename Node::Size;
		using Height = typename Node::Height;

	public:

		static T* parent(const T* const n) {
			return static_cast<const NodeMixin*>(n)->parent;
		}

		static void parent(T* const n, T* const p) {
			static_cast<NodeMixin*>(n)->parent = p;
		}

		static T* leftChild(const T* const n) {
			return static_cast<const NodeMixin*>(n)->leftChild;
		}

		static void leftChild(T* const n, T* const l) {
			static_cast<NodeMixin*>(n)->leftChild = l;
		}

		static T* rightChild(const T* n) {
			return static_cast<const NodeMixin*>(n)->rightChild;
		}

		static void rightChild(T* const n, T* const r) {
			static_cast<NodeMixin*>(n)->rightChild = r;
		}

		static Height height(const T* const n) {
			return static_cast<const NodeMixin*>(n)->height;
		}

		static void height(T* const n, Height h) {
			static_cast<NodeMixin*>(n)->height = h;
		}

		static Size volume(const T* const n) {
			return static_cast<const NodeMixin*>(n)->volume;
		}

		static void volume(T* const n, Size v) {
			static_cast<NodeMixin*>(n)->volume = v;
		}

		static void augment(T* const) {
		}

	};

	/**
	 * This class works as a mixin by CRTP to store a sequence of type `T` objects in
	 * an AVL tree with the help of `AVLSequenceNodeTrait<T>`.
	 */
	template<class T, class SizeType>
	class AVLSequenceNodeMixin {

	public:

		using Size = SizeType;
		using Height = unsigned int;

	public:

		AVLSequenceNodeMixin()
		: parent(nullptr), leftChild(nullptr), rightChild(nullptr)
		, height(1), volume(1) {
		}

		AVLSequenceNodeMixin(const AVLSequenceNodeMixin&) = delete;

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

		AVLSequenceNodeMixin& operator=(const AVLSequenceNodeMixin&) = delete;

		AVLSequenceNodeMixin& operator=(AVLSequenceNodeMixin&& other) {
			parent = other.parent;
			leftChild = other.leftChild;
			rightChild = other.rightChild;
			height = other.height;
			volume = other.volume;
			if(parent != nullptr) {
				if(parent->leftChild == &other) {
					parent->leftChild = static_cast<T*>(this);
				} else {
					parent->rightChild = static_cast<T*>(this);
				}
			}
			if(leftChild != nullptr) {
				leftChild->parent = static_cast<T*>(this);
			}
			if(rightChild != nullptr) {
				rightChild->parent = static_cast<T*>(this);
			}
			other.parent = nullptr;
			other.leftChild = nullptr;
			other.rightChild = nullptr;
			other.height = 1;
			other.volume = 1;
			return *this;
		}

	private:

		T* parent;
		T* leftChild;
		T* rightChild;
		Height height;
		Size volume;

		friend class AVLSequenceNodeTrait<T>;

	};

	/**
	 * This provides operations to store a sequence of instances of `Node`
	 * in an intrusive AVL tree with the aid of `AVLSequenceNodeTrait<Node>`.
	 * `Node` must implement the interface defined by the template class `AVLSequenceNodeTrait<Node>`.
	 * Or you can provide an equivalent trait implementation by specifying the second template paramater.
	 */
	template<class Node, class NodeTrait = AVLSequenceNodeTrait<Node>>
	class AVLSequenceAlgorithm {

	public:

		using Size = typename NodeTrait::Size;
		using Height = typename NodeTrait::Height;

	private:

		template<class T>
		using optional = boost::optional<std::reference_wrapper<T>>;

	private:

		using T = NodeTrait;

	private:

		template<class CNode>
		class IteratorImpl : public std::iterator<std::bidirectional_iterator_tag, CNode> {

		private:

			using This = IteratorImpl<CNode>;

		public:

			IteratorImpl()
			: p(nullptr), q(nullptr) {
			}

			IteratorImpl(CNode* p, CNode* q)
			: p(p), q(q) {
			}

		public:

			This& operator++() {
				if(! p) {
					p = q;
					q = nullptr;
				} else if(auto r = next(p)) {
					p = &(r->get());
				} else {
					q = p;
					p = nullptr;
				}
				return *this;
			}

			This operator++(int) {
				This tmp(*this);
				operator++();
				return tmp;
			}

			This& operator--() {
				if(! p) {
					p = q;
					q = nullptr;
				} else if(auto r = previous(p)) {
					p = &(r->get());
				} else {
					q = p;
					p = nullptr;
				}
				return *this;
			}

			This operator--(int) {
				This tmp(*this);
				operator--();
				return tmp;
			}

			bool operator==(const This& other) const {
				return p == other.p && q == other.q;
			}

			bool operator!=(const This& other) const {
				return p != other.p || q != other.q;
			}

			CNode& operator*() const {
				return *p;
			}

			CNode* operator->() const {
				return p;
			}

		private:

			CNode* p;
			CNode* q;
		};

	public:

		class ContainerView {

		public:

			using iterator = IteratorImpl<Node>;
			using const_iterator = IteratorImpl<const Node>;
			using reverse_iterator = std::reverse_iterator<iterator>;
			using const_reverse_iterator = std::reverse_iterator<const_iterator>;

		public:

			explicit ContainerView(Node& root)
			: root(&root) {
			}

		public:

			iterator begin() {
				return iterator(findHead(root), nullptr);
			}

			iterator end() {
				return iterator(nullptr, findTail(root));
			}

			const_iterator begin() const {
				return const_iterator(findHead(root), nullptr);
			}

			const_iterator end() const {
				return const_iterator(nullptr, findTail(root));
			}

			reverse_iterator rbegin() {
				return std::reverse_iterator<iterator>(end());
			}

			reverse_iterator rend() {
				return std::reverse_iterator<iterator>(begin());
			}

			const_reverse_iterator rbegin() const {
				return std::reverse_iterator<const_iterator>(end());
			}

			const_reverse_iterator rend() const {
				return std::reverse_iterator<const_iterator>(begin());
			}

			const_iterator cbegin() const {
				return const_iterator(findHead(root), nullptr);
			}

			const_iterator cend() const {
				return const_iterator(nullptr, findTail(root));
			}

			const_reverse_iterator crbegin() const {
				return std::reverse_iterator<const_iterator>(end());
			}

			const_reverse_iterator crend() const {
				return std::reverse_iterator<const_iterator>(begin());
			}

		private:

			Node* const root;
		};

		class ConstContainerView {

		public:

			using const_iterator = IteratorImpl<const Node>;
			using const_reverse_iterator = std::reverse_iterator<const_iterator>;

		public:

			explicit ConstContainerView(const Node& root)
			: root(&root) {
			}

		public:

			const_iterator begin() const {
				return const_iterator(findHead(root), nullptr);
			}

			const_iterator end() const {
				return const_iterator(nullptr, findTail(root));
			}

			const_reverse_iterator rbegin() const {
				return std::reverse_iterator<const_iterator>(end());
			}

			const_reverse_iterator rend() const {
				return std::reverse_iterator<const_iterator>(begin());
			}

			const_iterator cbegin() const {
				return const_iterator(findHead(root), nullptr);
			}

			const_iterator cend() const {
				return const_iterator(nullptr, findTail(root));
			}

			const_reverse_iterator crbegin() const {
				return std::reverse_iterator<const_iterator>(end());
			}

			const_reverse_iterator crend() const {
				return std::reverse_iterator<const_iterator>(begin());
			}

		private:

			const Node* const root;
		};

	public:

		/**
		 * Returns ContainerView of the sequence whose root is `p`.
		 * ContainerView has an interface for STL compatible iterators.
		 */
		static ContainerView containerView(Node& p) {
			return ContainerView(p);
		}

		/**
		 * Returns ContainerView of the sequence whose root is `p`.
		 * ContainerView has an interface for STL compatible iterators.
		 */
		static ConstContainerView containerView(const Node& p) {
			return ConstContainerView(p);
		}

		/**
		 * Inserts node `n` before `p`.
		 */
		static void insertNodeBefore(Node& p, Node& n) {
			insertNodeBefore(&p, &n);
		}

		/**
		 * Inserts node `n` after `p`.
		 */
		static void insertNodeAfter(Node& p, Node& n) {
			insertNodeAfter(&p, &n);
		}

		/**
		 * Isolate `p` from its tree.
		 * `p` becomes a tree of its own.
		 */
		static void removeNode(Node& p) {
			removeNode(&p);
		}

		/**
		 * Joins the sequence whose rightmost element is `p` with
		 * the sequence whose leftmost element is `q`.
		 */
		static void join(Node& p, Node& q) {
			join(&p, &q);
		}

		/**
		 * Splits `p` and its predecessor.
		 */
		static void splitBefore(Node& p) {
			splitBefore(&p);
		}

		/**
		 * Splits `p` and its successor.
		 */
		static void splitAfter(Node& p) {
			splitAfter(&p);
		}

		/**
		 * Returns the size of the subtree rooted at `p`.
		 */
		static Size size(const Node& p) {
			return T::volume(&p);
		}

		/**
		 * Returns the root of the tree containing `p`.
		 */
		static Node& findRoot(Node& p) {
			return *findRoot(&p);
		}

		/**
		 * Returns the root of the tree containing `p`.
		 */
		static const Node& findRoot(const Node& p) {
			return *findRoot(&p);
		}

		/**
		 * Finds the leftmost node of the subtree rooted at `p`.
		 * Call this with the root to get the first element of the whole sequence.
		 */
		static Node& findHead(Node& p) {
			return *findHead(&p);
		}

		/**
		 * Finds the leftmost node of the subtree rooted at `p`.
		 * Call this with the root to get the first element of the whole sequence.
		 */
		static const Node& findHead(const Node& p) {
			return *findHead(&p);
		}

		/**
		 * Finds the rightmost node of the subtree rooted at `p`.
		 * Call this with the root to get the last element of the whole sequence.
		 */
		static Node& findTail(Node& p) {
			return *findTail(&p);
		}

		/**
		 * Finds the rightmost node of the subtree rooted at `p`.
		 * Call this with the root to get the last element of the whole sequence.
		 */
		static const Node& findTail(const Node& p) {
			return *findTail(&p);
		}

		/**
		 * Returns the node next to `p` if `p` is not the last element.
		 */
		static optional<Node> next(Node& p) {
			return next(&p);
		}

		/**
		 * Returns the node next to `p` if `p` is not the last element.
		 */
		static optional<const Node> next(const Node& p) {
			return next(&p);
		}

		/**
		 * Returns the node previous to `p` if `p` is not the first element.
		 */
		static optional<Node> previous(Node& p) {
			return previous(&p);
		}

		/**
		 * Returns the node previous to `p` if `p` is not the first element.
		 */
		static optional<const Node> previous(const Node& p) {
			return previous(&p);
		}

	private:

		static void transplant(Node* p, Node* q) {
			if(T::parent(p)) {
				if(p == T::leftChild(T::parent(p))) {
					T::leftChild(T::parent(p), q);
				} else {
					T::rightChild(T::parent(p), q);
				}
			} //else set q as root (p was old root)
			if(q) {
				T::parent(q, T::parent(p));
			}
		}

		static void setLeft(Node* const p, Node* const q) {
			if(p) {
				T::leftChild(p, q);
			}
			if(q) {
				T::parent(q, p);
			}
		}

		static void setRight(Node* const p, Node* const q) {
			if(p) {
				T::rightChild(p, q);
			}
			if(q) {
				T::parent(q, p);
			}
		}

		static Height calcHeight(const Node* const p) {
			return
				std::max(
					T::leftChild(p)  ? T::height(T::leftChild(p))  : 0,
					T::rightChild(p) ? T::height(T::rightChild(p)) : 0
				) + 1;
		}

		static Size calcVolume(const Node* const p) {
			return
				(T::leftChild(p)  ? T::volume(T::leftChild(p))  : 0) +
				(T::rightChild(p) ? T::volume(T::rightChild(p)) : 0) + 1;
		}

		static void augment(Node* const p) {
			T::height(p, calcHeight(p));
			T::volume(p, calcVolume(p));
			T::augment(p);
		}

		static void augmentUp(Node* const p) {
			if(p) {
				augment(p);
				augmentUp(T::parent(p));//expects TCO
			}
		}

		static void rotateRight(Node* const p) {
			Node* const q = T::leftChild(p);
			T::leftChild(p, T::rightChild(q));
			if(T::leftChild(p)) {
				T::parent(T::leftChild(p), p);
			}
			T::parent(q, T::parent(p));
			if(Node* qp = T::parent(q)) {
				if(T::rightChild(qp) == p) {
					T::rightChild(qp, q);
				} else {
					T::leftChild(qp, q);
				}
			}// else set q as the new root (p was the old root)
			T::rightChild(q, p);
			T::parent(p, q);
			augment(p);
			augment(q);
		}

		static void rotateLeft(Node* const p) {
			Node* const q = T::rightChild(p);
			T::rightChild(p, T::leftChild(q));
			if(T::rightChild(p)) {
				T::parent(T::rightChild(p), p);
			}
			T::parent(q, T::parent(p));
			if(Node* qp = T::parent(q)) {
				if(T::leftChild(qp) == p) {
					T::leftChild(qp, q);
				} else {
					T::rightChild(qp, q);
				}
			}// else set q as the new root (p was the old root)
			T::leftChild(q, p);
			T::parent(p, q);
			augment(p);
			augment(q);
		}

		static int slope(const Node* const p) {
			return
				(T::leftChild(p)  ? T::height(T::leftChild(p))  : 0) -
				(T::rightChild(p) ? T::height(T::rightChild(p)) : 0);
		}

		static void balance(Node* const p) {
			if(! p) {
				return;
			}
			augment(p);
			const int bf = slope(p);
			if(bf > 1) {
				if(slope(T::leftChild(p)) == -1) {
					rotateLeft(T::leftChild(p));
				}
				rotateRight(p);
			} else if(bf < -1) {
				if(slope(T::rightChild(p)) == 1) {
					rotateRight(T::rightChild(p));
				}
				rotateLeft(p);
			}
			balance(T::parent(p));//expects TCO
		}

		template<class CNode>
		static CNode* findRoot(CNode* p) {
			while(T::parent(p)) {
				p = T::parent(p);
			}
			return p;
		}

		template<class CNode>
		static CNode* findHead(CNode* p) {
			while(T::leftChild(p)) {
				p = T::leftChild(p);
			}
			return p;
		}

		template<class CNode>
		static CNode* findTail(CNode* p) {
			while(T::rightChild(p)) {
				p = T::rightChild(p);
			}
			return p;
		}

		static void insertNodeBefore(Node* const p, Node* const q) {
			T::leftChild(q, nullptr);
			T::rightChild(q, nullptr);
			T::height(q, 1);
			T::volume(q, 1);
			if(T::leftChild(p)) {
				Node* const r = findTail(T::leftChild(p));
				setRight(r, q);
				balance(r);
			} else {
				setLeft(p, q);
				balance(p);
			}
		}

		static void insertNodeAfter(Node* const p, Node* const q) {
			T::leftChild(q, nullptr);
			T::rightChild(q, nullptr);
			T::height(q, 1);
			T::volume(q, 1);
			if(T::rightChild(p)) {
				Node* const r = findHead(T::rightChild(p));
				setLeft(r, q);
				balance(r);
			} else {
				setRight(p, q);
				balance(p);
			}
		}

		static void removeNode(Node* const p) {
			if(! T::leftChild(p)) {
				Node* const b = T::parent(p);
				transplant(p, T::rightChild(p));
				balance(b);
			} else if(! T::rightChild(p)) {
				Node* const b = T::parent(p);
				transplant(p, T::leftChild(p));
				balance(b);
			} else {
				Node* const q = findHead(T::rightChild(p));
				Node* b = q;
				if(T::parent(q) != p) {
					b = T::parent(q);
					setLeft(T::parent(q), T::rightChild(q));
					setRight(q, T::rightChild(p));
				}
				transplant(p, q);
				setLeft(q, T::leftChild(p));
				balance(b);
			}
			T::parent(p, nullptr);
			T::leftChild(p, nullptr);
			T::rightChild(p, nullptr);
			T::height(p, 1);
			T::volume(p, 1);
		}

		static void embed(Node* const p, Node* const q) {
			const Height h = T::height(p);
			Node* const n = findTail(p);
			Node* b = n;
			if(T::parent(n)) {
				b = T::parent(n);
				transplant(n, T::leftChild(n));
				augmentUp(T::parent(n));
			}
			Node* m = q;
			while(h < T::height(m) && T::leftChild(m)) {
				m = T::leftChild(m);
			}
			setLeft(T::parent(m), n);
			setRight(n, m);
			if(n != p) {
				setLeft(n, p);
			}
			balance(b);
		}

		static void embrace(Node* const p, Node* const q) {
			const Height h = T::height(q);
			Node* const n = findHead(q);
			Node* b = n;
			if(T::parent(n)) {
				b = T::parent(n);
				transplant(n, T::rightChild(n));
				augmentUp(b);
			}
			Node* m = p;
			while(h < T::height(m) && T::rightChild(m)) {
				m = T::rightChild(m);
			}
			setRight(T::parent(m), n);
			setLeft(n, m);
			if(n != q) {
				setRight(n, q);
			}
			balance(b);
		}

		static void join(Node* const p, Node* const q) {
			Node* const rp = findRoot(p);
			Node* const rq = findRoot(q);
			if(T::height(rp) < T::height(rq)) {
				embed(rp, rq);
			} else {
				embrace(rp, rq);
			}
		}

		static void makeRoot(Node* const p) {
			if(T::parent(p)) {
				if(T::leftChild(T::parent(p)) == p) {
					rotateRight(T::parent(p));
				} else {
					rotateLeft(T::parent(p));
				}
				makeRoot(p);//expects TCO
			}
		}

		static void augmentHeightUp(Node* const p) {
			if(p) {
				Height h = calcHeight(p);
				if(T::height(p) != h) {
					T::height(p, h);
					augmentHeightUp(T::parent(p));//expects TCO
				}
			}
		}

		static void balanceDown(Node* const p) {
			if(! p) {
				return;
			}
			augment(p);
			const int bf = slope(p);
			if(bf > 1) {
				if(slope(T::leftChild(p)) == -1) {
					rotateLeft(T::leftChild(p));
				}
				rotateRight(p);
				balanceDown(p);//expects TCO
			} else if(bf < -1) {
				if(slope(T::rightChild(p)) == 1) {
					rotateRight(T::rightChild(p));
				}
				rotateLeft(p);
				balanceDown(p);//expects TCO
			} else {
				balanceDown(T::parent(p));//expects TCO
			}
		}

		static void splitBefore(Node* const p) {
			makeRoot(p);
			Node* const q = T::leftChild(p);
			if(q) {
				T::leftChild(p, nullptr);
				T::parent(q, nullptr);
				balanceDown(findTail(q));
			}
			balanceDown(p);
		}

		static void splitAfter(Node* const p) {
			makeRoot(p);
			Node* const q = T::rightChild(p);
			if(q) {
				T::rightChild(p, nullptr);
				T::parent(q, nullptr);
				balanceDown(findHead(q));
			}
			balanceDown(p);
		}

		static Size size(const Node* const p) {
			return p ? T::volume(p) : 0;
		}

		template<class CNode>
		static Node* aboveRightTop(CNode* p) {
			if(! T::parent(p)) {
				return nullptr;
			} else if(p == T::leftChild(T::parent(p))) {
				return T::parent(p);
			} else {
				return aboveRightTop(T::parent(p));//expects TCO
			}
		}

		template<class CNode>
		static optional<CNode> next(CNode* p) {
			if(T::rightChild(p)) {
				p = findHead(T::rightChild(p));
			} else {
				p = aboveRightTop(p);
			}
			return p ?
				optional<CNode>(*p) :
				optional<CNode>();
		}

		template<class CNode>
		static Node* aboveLeftTop(CNode* p) {
			if(! T::parent(p)) {
				return nullptr;
			} else if(p == T::rightChild(T::parent(p))) {
				return T::parent(p);
			} else {
				return aboveLeftTop(T::parent(p));//expects TCO
			}
		}

		template<class CNode>
		static optional<CNode> previous(CNode* p) {
			if(T::leftChild(p)) {
				p = findTail(T::leftChild(p));
			} else {
				p = aboveLeftTop(p);
			}
			return p ?
				optional<CNode>(*p) :
				optional<CNode>();
		}

	#ifdef CNRC_DEBUG_AVL_SEQUENCE // helpers for debugging

	public:

		struct Debug {
			/**
			 * Checks AVL invariants of the tree rooted at 'p'.
			 * Compile with -DDEBUG_CNRC flag to use this.
			 */
			template<class ShowNode>
			static bool checkSanity(
					const Node& p,
					ShowNode showNode
			) {
				return checkSanity(&p, T::parent(&p), showNode);
			}

			template<class ShowNode>
			static bool checkSanity(
				const Node* const p,
				const Node* const parent,
				ShowNode showNode
			) {
				if(! p) {
					return true;
				}
				if(T::parent(p) != parent) {
					std::cerr <<
						"AVLSequence sanity check: " <<
						"invalid parent pointer " <<
						"at node " << showNode(*p) << std::endl;
					return false;
				}
				if(T::height(p) != calcHeight(p)) {
					std::cerr <<
						"AVLSequence sanity check: " <<
						"invalid height value " <<
						"at node " << showNode(*p) << std::endl;
					return false;
				}
				const int b = slope(p);
				if(b < -1 || b > 1) {
					std::cerr <<
						"AVLSequence sanity check: " <<
						"violation of the balance condition " <<
						"at node " << showNode(*p) << std::endl;
					return false;
				}
				if(T::volume(p) != calcVolume(p)) {
					std::cerr <<
						"AVLSequence sanity check: " <<
						"invalid branch size value " <<
						"at node: " << showNode(*p) << std::endl;
					return false;
				}
				return
					checkSanity(T::leftChild(p), p, showNode) &&
					checkSanity(T::rightChild(p), p, showNode);
			}

			/**
			 * Prints the structure of the tree rootead at 'p'.
			 * Compile with -DDEBUG_CNRC flag to use this.
			 */
			template<class ShowNode>
			static void printTree(
				const Node& p,
				ShowNode showNode
			) {
				printTree(&p, "[T]", 0, showNode);
			}

			template<class ShowNode>
			static void printTree(
				const Node* const p,
				const std::string head,
				const unsigned int indent,
				ShowNode showNode
			) {
				if(! p) {
					return;
				}
				for(unsigned int i = 0; i < indent; ++i) {
					std::cerr << "\t";
				}
				std::cerr <<
					head << showNode(*p) << ", " <<
					"height: " << T::height(p) << ", " <<
					"branch size: " << T::volume(p) << ", " <<
					"addr: " << p << ", " <<
					"parent addr: " << T::parent(p) << std::endl;
				printTree(T::leftChild(p), "[L]", indent + 1, showNode);
				printTree(T::rightChild(p), "[R]", indent + 1, showNode);
			}
		};

	#endif //end CNRC_DEBUG_AVL_SEQUENCE

	};
};

/**
 * Public interface exported from AVLSequenceModule.
 */

template<class NodeTrait>
using AVLSequenceAlgorithm = AVLSequenceModule::AVLSequenceAlgorithm<NodeTrait>;

template<class T>
using AVLSequenceNodeTrait = AVLSequenceModule::AVLSequenceNodeTrait<T>;

template<class T, class SizeType = unsigned int>
using AVLSequenceNodeMixin = AVLSequenceModule::AVLSequenceNodeMixin<T, SizeType>;

} //end namespace AVLSequence
} //end namespace Cnrc
} //end namespace Snu


/*
 * SettingsManager.cpp
 *
 *  Created on: 22.08.2017
 *      Author: thies
 */

#include <deal.II/base/exceptions.h>
#include <deal.II/base/function.h>
#include <deal.II/base/utilities.h>

#include <base/DiscretizedFunction.h>
#include <base/Tuple.h>
#include <forward/WaveEquationBase.h>
#include <inversion/InversionProgress.h>
#include <inversion/NonlinearLandweber.h>
#include <inversion/REGINN.h>
#include <measurements/ConvolutionMeasure.h>
#include <measurements/CubeBoundaryDistribution.h>
#include <measurements/GridDistribution.h>
#include <measurements/SensorDistribution.h>

#include <SettingsManager.h>

#include <tgmath.h>
#include <cstdio>
#include <utility>

namespace wavepi {
using namespace dealii;
using namespace wavepi::forward;
using namespace wavepi::inversion;
using namespace wavepi::measurements;

const std::string SettingsManager::KEY_GENERAL            = "general";
const std::string SettingsManager::KEY_GENERAL_DIMENSION  = "dimension";
const std::string SettingsManager::KEY_GENERAL_FE_DEGREE  = "finite element degree";
const std::string SettingsManager::KEY_GENERAL_QUAD_ORDER = "quadrature order";

const std::string SettingsManager::KEY_LOG                = "log";
const std::string SettingsManager::KEY_LOG_FILE           = "file";
const std::string SettingsManager::KEY_LOG_FILE_DEPTH     = "file depth";
const std::string SettingsManager::KEY_LOG_FILE_DEPTH_MPI = "file depth mpi";
const std::string SettingsManager::KEY_LOG_CONSOLE_DEPTH  = "console depth";

const std::string SettingsManager::KEY_MESH                    = "mesh";
const std::string SettingsManager::KEY_MESH_END_TIME           = "end time";
const std::string SettingsManager::KEY_MESH_INITIAL_REFINES    = "initial refines";
const std::string SettingsManager::KEY_MESH_INITIAL_TIME_STEPS = "initial time steps";
const std::string SettingsManager::KEY_MESH_SHAPE              = "shape";
const std::string SettingsManager::KEY_MESH_SHAPE_GENERATOR    = "generator name";
const std::string SettingsManager::KEY_MESH_SHAPE_OPTIONS      = "options";

const std::string SettingsManager::KEY_PROBLEM                      = "problem";
const std::string SettingsManager::KEY_PROBLEM_TYPE                 = "type";
const std::string SettingsManager::KEY_PROBLEM_TRANSFORM            = "transform";
const std::string SettingsManager::KEY_PROBLEM_NORM_DOMAIN          = "norm of domain";
const std::string SettingsManager::KEY_PROBLEM_NORM_CODOMAIN        = "norm of codomain";
const std::string SettingsManager::KEY_PROBLEM_NORM_DOMAIN_P_ENABLE = "domain lp wrapping";
const std::string SettingsManager::KEY_PROBLEM_NORM_DOMAIN_P        = "domain p";

const std::string SettingsManager::KEY_PROBLEM_NORM_H1L2ALPHA         = "H1L2 alpha";
const std::string SettingsManager::KEY_PROBLEM_NORM_H2L2ALPHA         = "H2L2 alpha";
const std::string SettingsManager::KEY_PROBLEM_NORM_H2L2BETA          = "H2L2 beta";
const std::string SettingsManager::KEY_PROBLEM_NORM_H1H1ALPHA         = "H1H1 alpha";
const std::string SettingsManager::KEY_PROBLEM_NORM_H1H1GAMMA         = "H1H1 gamma";
const std::string SettingsManager::KEY_PROBLEM_NORM_H2L2PLUSL2H1ALPHA = "H2L2+L2H1 alpha";
const std::string SettingsManager::KEY_PROBLEM_NORM_H2L2PLUSL2H1BETA  = "H2L2+L2H1 beta";
const std::string SettingsManager::KEY_PROBLEM_NORM_H2L2PLUSL2H1GAMMA = "H2L2+L2H1 gamma";

const std::string SettingsManager::KEY_PROBLEM_EPSILON           = "epsilon";
const std::string SettingsManager::KEY_PROBLEM_CONSTANTS         = "constants";
const std::string SettingsManager::KEY_PROBLEM_GUESS             = "initial guess";
const std::string SettingsManager::KEY_PROBLEM_PARAM_RHO         = "parameter rho";
const std::string SettingsManager::KEY_PROBLEM_PARAM_RHO_DYNAMIC = "parameter rho dynamic";
const std::string SettingsManager::KEY_PROBLEM_PARAM_Q           = "parameter q";
const std::string SettingsManager::KEY_PROBLEM_PARAM_C           = "parameter c";
const std::string SettingsManager::KEY_PROBLEM_PARAM_NU          = "parameter nu";
const std::string SettingsManager::KEY_PROBLEM_PARAM_BACKGROUND  = "background parameter";
const std::string SettingsManager::KEY_PROBLEM_SHAPE_SCALE       = "shape scaling";

const std::string SettingsManager::KEY_PROBLEM_DATA                       = "data";
const std::string SettingsManager::KEY_PROBLEM_DATA_ADDITIONAL_REFINES    = "additional refines";
const std::string SettingsManager::KEY_PROBLEM_DATA_ADDITIONAL_DEGREE     = "additional fe degrees";
const std::string SettingsManager::KEY_PROBLEM_DATA_COUNT                 = "number of right hand sides";
const std::string SettingsManager::KEY_PROBLEM_DATA_RHS                   = "right hand sides";
const std::string SettingsManager::KEY_PROBLEM_DATA_CONFIG                = "configurations";
const std::string SettingsManager::KEY_PROBLEM_DATA_I                     = "config ";  // + number
const std::string SettingsManager::KEY_PROBLEM_DATA_I_MEASURE             = "measure";
const std::string SettingsManager::KEY_PROBLEM_DATA_I_MASK                = "mask";
const std::string SettingsManager::KEY_PROBLEM_DATA_I_SENSOR_DISTRIBUTION = "sensor distribution";

const std::string SettingsManager::KEY_INVERSION        = "inversion";
const std::string SettingsManager::KEY_INVERSION_TAU    = "tau";
const std::string SettingsManager::KEY_INVERSION_METHOD = "method";

void SettingsManager::declare_parameters(std::shared_ptr<ParameterHandler> prm) {
  OutputProgressListener<2, Tuple<DiscretizedFunction<2>>>::declare_parameters(*prm);
  WatchdogProgressListener<Tuple<DiscretizedFunction<2>>, Tuple<DiscretizedFunction<2>>>::declare_parameters(*prm);
  StatOutputProgressListener<Tuple<DiscretizedFunction<2>>, Tuple<DiscretizedFunction<2>>>::declare_parameters(*prm);
  AbstractEquation<2>::declare_parameters(*prm);
  BoundCheckProgressListener<2, Tuple<DiscretizedFunction<2>>, DiscretizedFunction<2>>::declare_parameters(*prm);
  LogTransform<2>::declare_parameters(*prm);
  ArtanhTransform<2>::declare_parameters(*prm);

  prm->enter_subsection(KEY_GENERAL);
  {
    prm->declare_entry(KEY_GENERAL_DIMENSION, "2", Patterns::Integer(1, 3), "problem dimension");
    prm->declare_entry(KEY_GENERAL_FE_DEGREE, "1", Patterns::Integer(1, 4),
                       "polynomial degree of finite elements. Note that bound "
                       "checking is currently only implemented for linear "
                       "elements.");
    prm->declare_entry(KEY_GENERAL_QUAD_ORDER, "3", Patterns::Integer(1, 20),
                       "order of quadrature (QGauss, exact in polynomials of "
                       "degree ≤ 2n-1, use at least finite element degree + "
                       "1) ");
  }
  prm->leave_subsection();

  prm->enter_subsection(KEY_LOG);
  {
    prm->declare_entry(KEY_LOG_FILE, "wavepi.log", Patterns::FileName(Patterns::FileName::output), "external log file");
    prm->declare_entry(KEY_LOG_FILE_DEPTH, "3", Patterns::Integer(0), "depth for the log file (root process)");
    prm->declare_entry(KEY_LOG_FILE_DEPTH_MPI, "3", Patterns::Integer(0), "depth for the log file (other processes)");
    prm->declare_entry(KEY_LOG_CONSOLE_DEPTH, "2", Patterns::Integer(0), "depth for stdout (root process)");
  }
  prm->leave_subsection();

  prm->enter_subsection(KEY_MESH);
  {
    prm->declare_entry(KEY_MESH_END_TIME, "6.28318530718", Patterns::Double(0), "time horizon T");
    prm->declare_entry(KEY_MESH_INITIAL_REFINES, "6", Patterns::Integer(0), "refines of the (initial) spatial grid");
    prm->declare_entry(KEY_MESH_INITIAL_TIME_STEPS, "256", Patterns::Integer(2), "(initial) number of time steps");

    prm->enter_subsection(KEY_MESH_SHAPE);
    {
      prm->declare_entry(KEY_MESH_SHAPE_GENERATOR, "hyper_cube",
                         Patterns::Selection("hyper_cube|hyper_L|hyper_ball|cheese"),
                         "generator for the triangulation");
      prm->declare_entry(KEY_MESH_SHAPE_OPTIONS, "left=-1.0, right=1.0", Patterns::Anything(),
                         "options for the generator, in the form `var1=value1, var2=value2, "
                         "...`.\n Available options: left, right for hyper_cube and hyper_L, "
                         "center_{x,y,z} and radius for hyper_cube, scale for cheese.");
    }
    prm->leave_subsection();
  }
  prm->leave_subsection();

  prm->enter_subsection(KEY_PROBLEM);
  {
    prm->declare_entry(KEY_PROBLEM_TYPE, "rho", Patterns::Selection("c|c_constant|nu|rho|rho_constant|q"),
                       "parameter that is reconstructed");

    prm->declare_entry(KEY_PROBLEM_TRANSFORM, "Identity", Patterns::Selection("Identity|Log|Artanh"),
                       "transformation to apply to the parameter (e.g. to get rid of constraints)");

    prm->declare_entry(KEY_PROBLEM_NORM_DOMAIN, "L2L2",
                       Patterns::Selection("L2L2|H1L2|H2L2|H1H1|H2L2PlusL2H1|Coefficients"),
                       "norm to use for parameters (incl. the reconstruction)");
    prm->declare_entry(KEY_PROBLEM_NORM_CODOMAIN, "L2L2",
                       Patterns::Selection("L2L2|H1L2|H2L2|H1H1|H2L2PlusL2H1|Coefficients"),
                       "Set the norm to use for fields. Be aware that this has "
                       "to match the norm that the measurements expect its "
                       "inputs to have.");

    prm->declare_entry(KEY_PROBLEM_NORM_DOMAIN_P, "2.0", Patterns::Double(1),
                       "index p for the l^p-norm. See also " + KEY_PROBLEM_NORM_DOMAIN_P_ENABLE + ".");
    prm->declare_entry(KEY_PROBLEM_NORM_DOMAIN_P_ENABLE, "false", Patterns::Bool(),
                       "Wrap the norm of the parameter space inside a l^p-norm");

    prm->declare_entry(KEY_PROBLEM_NORM_H1L2ALPHA, "0.5", Patterns::Double(0),
                       "Factor α in front of derivative term of H¹([0,T], L²(Ω)) dot product");

    prm->declare_entry(KEY_PROBLEM_NORM_H2L2ALPHA, "0.5", Patterns::Double(0),
                       "Factor α in front of first derivative term of "
                       "H²([0,T], L²(Ω)) dot product");
    prm->declare_entry(KEY_PROBLEM_NORM_H2L2BETA, "0.25", Patterns::Double(0),
                       "Factor β in front of second derivative term of "
                       "H²([0,T], L²(Ω)) dot product");

    prm->declare_entry(KEY_PROBLEM_NORM_H1H1ALPHA, "0.5", Patterns::Double(0),
                       "Factor α in front of time derivative term of H¹([0,T], H¹(Ω)) dot product");
    prm->declare_entry(KEY_PROBLEM_NORM_H1H1GAMMA, "0.5", Patterns::Double(0),
                       "Factor ɣ in front of gradient term of H¹([0,T], H¹(Ω)) dot product");

    prm->declare_entry(KEY_PROBLEM_NORM_H2L2PLUSL2H1ALPHA, "0.5", Patterns::Double(0),
                       "Factor α in front of time derivative term of H²([0,T], L²(Ω)) ∩ L²([0,T], H¹(Ω)) dot product");
    prm->declare_entry(KEY_PROBLEM_NORM_H2L2PLUSL2H1BETA, "0.25", Patterns::Double(0),
                       "Factor β in front of second time derivative term of "
                       "H²([0,T], L²(Ω)) ∩ L²([0,T], H¹(Ω)) dot product");
    prm->declare_entry(KEY_PROBLEM_NORM_H2L2PLUSL2H1GAMMA, "0.5", Patterns::Double(0),
                       "Factor ɣ in front of gradient term of H²([0,T], L²(Ω)) ∩ L²([0,T], H¹(Ω)) dot product");

    prm->declare_entry(KEY_PROBLEM_EPSILON, "1e-2", Patterns::Double(0, 1), "relative noise level ε");

    prm->declare_entry(KEY_PROBLEM_CONSTANTS, "", Patterns::Anything(),
                       "constants for the function declarations,\nin the form "
                       "`var1=value1, var2=value2, ...`.");

    prm->declare_entry(KEY_PROBLEM_GUESS, "0.0", Patterns::Anything(), "initial guess");

    prm->declare_entry(KEY_PROBLEM_PARAM_RHO, "1.0", Patterns::Anything(), "parameter ρ");
    prm->declare_entry(KEY_PROBLEM_PARAM_RHO_DYNAMIC, "true", Patterns::Bool(),
                       "parameter ρ time-dependent? (performance)");
    prm->declare_entry(KEY_PROBLEM_PARAM_Q, "0.0", Patterns::Anything(), "parameter q");
    prm->declare_entry(KEY_PROBLEM_PARAM_C, "2.0", Patterns::Anything(), "parameter c");
    prm->declare_entry(KEY_PROBLEM_PARAM_NU, "0.0", Patterns::Anything(), "parameter ν");
    prm->declare_entry(KEY_PROBLEM_PARAM_BACKGROUND, "0.0", Patterns::Anything(),
                       "background parameter (added to all arguments of the forward operator, including exact data)");
    prm->declare_entry(KEY_PROBLEM_SHAPE_SCALE, "1.0", Patterns::Double(0),
                       "scaling parameter applied to all shapes that are used");

    prm->enter_subsection(KEY_PROBLEM_DATA);
    {
      prm->declare_entry(
          KEY_PROBLEM_DATA_ADDITIONAL_REFINES, "0", Patterns::Integer(0),
          "Additional global refines to perform in space and time for data synthesis (to avoid inverse crime)");
      prm->declare_entry(KEY_PROBLEM_DATA_ADDITIONAL_DEGREE, "0", Patterns::Integer(0),
                         "Additional finite element degrees to use for data synthesis (to avoid inverse crime)");

      prm->declare_entry(KEY_PROBLEM_DATA_COUNT, "1", Patterns::Integer(1), "Number of right hand sides");

      prm->declare_entry(KEY_PROBLEM_DATA_CONFIG, "0", Patterns::Anything(),
                         "configuration to use for which right hand side, "
                         "separated by semicolons");
      prm->declare_entry(KEY_PROBLEM_DATA_RHS, "if(t<3.14, if(norm{x|y|z} < 0.1, sin(2*t), 0.0), 0.0)",
                         Patterns::Anything(), "right hand sides, separated by semicolons");

      for (size_t i = 0; i < num_configurations; i++) {
        prm->enter_subsection(KEY_PROBLEM_DATA_I + Utilities::int_to_string(i, 1));

        ConvolutionMeasure<2>::declare_parameters(*prm);
        GridDistribution<2>::declare_parameters(*prm);
        CubeBoundaryDistribution<2>::declare_parameters(*prm);

        prm->declare_entry(KEY_PROBLEM_DATA_I_MEASURE, "Field",
                           Patterns::Selection("Field|MaskedField|Convolution|Delta"), "type of measurements");

        prm->declare_entry(KEY_PROBLEM_DATA_I_SENSOR_DISTRIBUTION, "Grid", Patterns::Selection("Grid|CubeBoundary"),
                           "in case of simulated sensors, their location on the mesh");

        prm->declare_entry(KEY_PROBLEM_DATA_I_MASK, "1", Patterns::Anything(),
                           "in case of MaskedField, the mask function");

        prm->leave_subsection();
      }
    }
    prm->leave_subsection();
  }
  prm->leave_subsection();

  prm->enter_subsection(KEY_INVERSION);
  {
    prm->declare_entry(KEY_INVERSION_METHOD, "REGINN", Patterns::Selection("REGINN|NonlinearLandweber"),
                       "solver for the inverse problem");
    prm->declare_entry(KEY_INVERSION_TAU, "2", Patterns::Double(0), "parameter τ for discrepancy principle");

    REGINN<DiscretizedFunction<2>, DiscretizedFunction<2>, Function<2>>::declare_parameters(*prm);
    NonlinearLandweber<DiscretizedFunction<2>, DiscretizedFunction<2>, Function<2>>::declare_parameters(*prm);
  }
  prm->leave_subsection();
}

void SettingsManager::get_parameters(std::shared_ptr<ParameterHandler> prm) {
  this->prm = prm;
  AssertThrow(prm, ExcInternalError());

  prm->enter_subsection(KEY_LOG);
  {
    log_file           = prm->get(KEY_LOG_FILE);
    log_file_depth     = prm->get_integer(KEY_LOG_FILE_DEPTH);
    log_file_depth_mpi = prm->get_integer(KEY_LOG_FILE_DEPTH_MPI);
    log_console_depth  = prm->get_integer(KEY_LOG_CONSOLE_DEPTH);
  }
  prm->leave_subsection();

  prm->enter_subsection(KEY_GENERAL);
  {
    fe_degree  = prm->get_integer(KEY_GENERAL_FE_DEGREE);
    quad_order = prm->get_integer(KEY_GENERAL_QUAD_ORDER);
    dimension  = prm->get_integer(KEY_GENERAL_DIMENSION);
  }
  prm->leave_subsection();

  prm->enter_subsection(KEY_MESH);
  {
    end_time           = prm->get_double(KEY_MESH_END_TIME);
    initial_refines    = prm->get_integer(KEY_MESH_INITIAL_REFINES);
    initial_time_steps = prm->get_integer(KEY_MESH_INITIAL_TIME_STEPS);

    dt = end_time / (initial_time_steps - 1);
    times.clear();
    times.resize(initial_time_steps);

    for (size_t i = 0; i < initial_time_steps; i++)
      times[i] = i * dt;

    prm->enter_subsection(KEY_MESH_SHAPE);
    {
      std::string generator = prm->get(KEY_MESH_SHAPE_GENERATOR);

      std::string options_list                = prm->get(KEY_MESH_SHAPE_OPTIONS);
      std::vector<std::string> options_listed = Utilities::split_string_list(options_list, ',');

      shape_options.clear();

      for (size_t i = 0; i < options_listed.size(); ++i) {
        std::vector<std::string> this_c = Utilities::split_string_list(options_listed[i], '=');
        AssertThrow(this_c.size() == 2, ExcMessage("Could not parse generator options"));
        double tmp;
        AssertThrow(std::sscanf(this_c[1].c_str(), "%lf", &tmp), ExcMessage("Could not parse generator options"));
        shape_options[this_c[0]] = tmp;
      }

      if (generator == "hyper_cube") {
        if (!shape_options.count("left")) shape_options.emplace("left", -5.0);
        if (!shape_options.count("right")) shape_options.emplace("right", 5.0);

        shape = MeshShape::hyper_cube;
      } else if (generator == "hyper_L") {
        if (!shape_options.count("left")) shape_options.emplace("left", -5.0);
        if (!shape_options.count("right")) shape_options.emplace("right", 5.0);

        shape = MeshShape::hyper_l;
      } else if (generator == "hyper_ball") {
        if (!shape_options.count("center_x")) shape_options.emplace("center_x", 0.0);
        if (!shape_options.count("center_y")) shape_options.emplace("center_y", 0.0);
        if (!shape_options.count("center_z")) shape_options.emplace("center_z", 0.0);
        if (!shape_options.count("radius")) shape_options.emplace("radius", 1.0);

        shape = MeshShape::hyper_ball;
      } else if (generator == "cheese") {
        if (!shape_options.count("scale")) shape_options.emplace("scale", 1.0);

        AssertThrow(dimension > 1, ExcMessage("cheese only makes sense for dim > 1."));
        shape = MeshShape::cheese;
      } else {
        AssertThrow(false, ExcMessage("Unknown mesh generator:" + generator));
      }
    }
    prm->leave_subsection();
  }
  prm->leave_subsection();

  prm->enter_subsection(KEY_PROBLEM);
  {
    epsilon = prm->get_double(KEY_PROBLEM_EPSILON);

    std::string norm_domain_s = prm->get(KEY_PROBLEM_NORM_DOMAIN);

    if (norm_domain_s == "L2L2")
      norm_domain = NormType::l2l2;
    else if (norm_domain_s == "H1L2")
      norm_domain = NormType::h1l2;
    else if (norm_domain_s == "H2L2")
      norm_domain = NormType::h2l2;
    else if (norm_domain_s == "H1H1")
      norm_domain = NormType::h1h1;
    else if (norm_domain_s == "H2L2PlusL2H1")
      norm_domain = NormType::h2l2plusl2h1;
    else if (norm_domain_s == "Coefficients")
      norm_domain = NormType::vector;
    else
      AssertThrow(false, ExcMessage("Cannot parse norm of domain"));

    std::string norm_codomain_s = prm->get(KEY_PROBLEM_NORM_CODOMAIN);

    if (norm_codomain_s == "L2L2")
      norm_codomain = NormType::l2l2;
    else if (norm_codomain_s == "H1L2")
      norm_codomain = NormType::h1l2;
    else if (norm_codomain_s == "H2L2")
      norm_codomain = NormType::h2l2;
    else if (norm_codomain_s == "H1H1")
      norm_codomain = NormType::h1h1;
    else if (norm_codomain_s == "H2L2PlusL2H1")
      norm_codomain = NormType::h2l2plusl2h1;
    else if (norm_codomain_s == "Coefficients")
      norm_codomain = NormType::vector;
    else
      AssertThrow(false, ExcMessage("Cannot parse norm of codomain"));

    norm_domain_enable_wrapping = prm->get_bool(KEY_PROBLEM_NORM_DOMAIN_P_ENABLE);
    norm_domain_p               = prm->get_double(KEY_PROBLEM_NORM_DOMAIN_P);

    norm_h1l2_alpha = prm->get_double(KEY_PROBLEM_NORM_H1L2ALPHA);

    norm_h2l2_alpha = prm->get_double(KEY_PROBLEM_NORM_H2L2ALPHA);
    norm_h2l2_beta  = prm->get_double(KEY_PROBLEM_NORM_H2L2BETA);

    norm_h1h1_alpha = prm->get_double(KEY_PROBLEM_NORM_H1H1ALPHA);
    norm_h1h1_gamma = prm->get_double(KEY_PROBLEM_NORM_H1H1GAMMA);

    norm_h2l2plusl2h1_alpha = prm->get_double(KEY_PROBLEM_NORM_H2L2PLUSL2H1ALPHA);
    norm_h2l2plusl2h1_beta  = prm->get_double(KEY_PROBLEM_NORM_H2L2PLUSL2H1BETA);
    norm_h2l2plusl2h1_gamma = prm->get_double(KEY_PROBLEM_NORM_H2L2PLUSL2H1GAMMA);

    std::string transform_s = prm->get(KEY_PROBLEM_TRANSFORM);

    if (transform_s == "Identity")
      transform = TransformType::identity;
    else if (transform_s == "Log")
      transform = TransformType::log;
    else if (transform_s == "Artanh")
      transform = TransformType::artanh;
    else
      AssertThrow(false, ExcMessage("Cannot parse transform type"));

    std::string problem = prm->get(KEY_PROBLEM_TYPE);

    if (problem == "rho")
      problem_type = ProblemType::rho;
    else if (problem == "q")
      problem_type = ProblemType::q;
    else if (problem == "nu")
      problem_type = ProblemType::nu;
    else if (problem == "c")
      problem_type = ProblemType::c;
    else if (problem == "rho_constant")
      problem_type = ProblemType::rho_constant;
    else if (problem == "c_constant")
      problem_type = ProblemType::c_constant;
    else
      AssertThrow(false, ExcMessage("unknown problem type"));

    std::string constants_list            = prm->get(KEY_PROBLEM_CONSTANTS);
    std::vector<std::string> const_listed = Utilities::split_string_list(constants_list, ',');

    constants_for_exprs.clear();

    for (size_t i = 0; i < const_listed.size(); ++i) {
      std::vector<std::string> this_c = Utilities::split_string_list(const_listed[i], '=');
      AssertThrow(this_c.size() == 2, ExcMessage("Invalid format"));
      double tmp;
      AssertThrow(std::sscanf(this_c[1].c_str(), "%lf", &tmp), ExcMessage("Double number?"));
      constants_for_exprs[this_c[0]] = tmp;
    }

    expr_initial_guess    = prm->get(KEY_PROBLEM_GUESS);
    expr_param_rho        = prm->get(KEY_PROBLEM_PARAM_RHO);
    expr_param_nu         = prm->get(KEY_PROBLEM_PARAM_NU);
    expr_param_c          = prm->get(KEY_PROBLEM_PARAM_C);
    expr_param_q          = prm->get(KEY_PROBLEM_PARAM_Q);
    expr_param_background = prm->get(KEY_PROBLEM_PARAM_BACKGROUND);

    shape_scale = prm->get_double(KEY_PROBLEM_SHAPE_SCALE);
    rho_dynamic = prm->get_bool(KEY_PROBLEM_PARAM_RHO_DYNAMIC);

    prm->enter_subsection(KEY_PROBLEM_DATA);
    {
      num_rhs   = prm->get_integer(KEY_PROBLEM_DATA_COUNT);
      exprs_rhs = Utilities::split_string_list(prm->get(KEY_PROBLEM_DATA_RHS), ';');

      synthesis_additional_refines    = prm->get_integer(KEY_PROBLEM_DATA_ADDITIONAL_REFINES);
      synthesis_additional_fe_degrees = prm->get_integer(KEY_PROBLEM_DATA_ADDITIONAL_DEGREE);

      configs.clear();
      measures.clear();
      sensor_distributions.clear();
      expr_masks.clear();

      try {
        for (auto is : Utilities::split_string_list(prm->get(KEY_PROBLEM_DATA_CONFIG), ';')) {
          size_t ci = std::stoi(is);
          AssertThrow(ci < num_configurations, ExcIndexRange(ci, 0, num_configurations));
          configs.push_back(ci);
        }
      } catch (std::invalid_argument &e) {
        AssertThrow(false, ExcMessage("could not parse configurations: " + std::string(e.what())));
      }

      AssertThrow(num_rhs == exprs_rhs.size(), ExcMessage("number of right hand sides != number of expressions given"));
      AssertThrow(num_rhs == configs.size(),
                  ExcMessage("number of right hand sides != number of configurations given"));

      std::vector<MeasureType> measure_types;

      for (size_t i = 0; i < num_configurations; i++) {
        prm->enter_subsection(KEY_PROBLEM_DATA_I + Utilities::int_to_string(i, 1));

        auto measure_desc = prm->get(KEY_PROBLEM_DATA_I_MEASURE);
        MeasureType my_measure_type;

        if (measure_desc == "Field") {
          measures.push_back(Measure::field);
          my_measure_type = MeasureType::discretized_function;
        } else if (measure_desc == "MaskedField") {
          measures.push_back(Measure::masked_field);
          my_measure_type = MeasureType::discretized_function;
        } else if (measure_desc == "Convolution") {
          measures.push_back(Measure::convolution);
          my_measure_type = MeasureType::vector;
        } else if (measure_desc == "Delta") {
          measures.push_back(Measure::delta);
          my_measure_type = MeasureType::vector;
        } else {
          AssertThrow(false, ExcMessage("Unknown Measure: " + measure_desc));
        }

        measure_types.push_back(my_measure_type);

        auto distrib_desc = prm->get(KEY_PROBLEM_DATA_I_SENSOR_DISTRIBUTION);

        if (distrib_desc == "Grid") {
          sensor_distributions.push_back(SettingsManager::SensorDistribution::grid);
        } else if (distrib_desc == "CubeBoundary") {
          sensor_distributions.push_back(SettingsManager::SensorDistribution::cube_boundary);
        } else {
          AssertThrow(false, ExcMessage("Unknown sensor distribution: " + distrib_desc));
        }

        expr_masks.push_back(prm->get(KEY_PROBLEM_DATA_I_MASK));
        prm->leave_subsection();
      }

      for (size_t i = 0; i < num_rhs; i++) {
        auto my_measure_type = measure_types[configs[i]];

        if (!i) measure_type = my_measure_type;

        AssertThrow(measure_type == my_measure_type, ExcMessage("the resulting data types must be the same for "
                                                                "all used measurement configurations!"))
      }
    }
    prm->leave_subsection();
  }
  prm->leave_subsection();

  prm->enter_subsection(KEY_INVERSION);
  {
    tau = prm->get_double(KEY_INVERSION_TAU);

    std::string smethod = prm->get(KEY_INVERSION_METHOD);

    if (smethod == "REGINN")
      method = NonlinearMethod::reginn;
    else if (smethod == "NonlinearLandweber")
      method = NonlinearMethod::nonlinear_landweber;
    else
      AssertThrow(false, ExcMessage("Unknown Method: " + smethod));
  }

  prm->leave_subsection();
}

void SettingsManager::log_parameters() {
#ifdef WAVEPI_MPI
  size_t mpi_rank = Utilities::MPI::this_mpi_process(MPI_COMM_WORLD);
#else
  size_t mpi_rank = 0;
#endif

  unsigned int prev_console = mpi_rank > 0 ? 0 : deallog.depth_console(100);
  unsigned int prev_file    = deallog.depth_file(100);

  prm->log_parameters(deallog);

  deallog.depth_console(prev_console);
  deallog.depth_file(prev_file);
}

}  // namespace wavepi


// SPDX-License-Identifier: NASA-1.3
/*******************************************************************************
 *
 * Implementation of Patricia trees based on the algorithms described in
 * C. Okasaki and A. Gill's paper: "Fast Mergeable Integer Maps",
 * Workshop on ML, September 1998, pages 77-86.
 *
 * Author: Arnaud J. Venet (arnaud.j.venet@nasa.gov)
 *
 * Notices:
 *
 * Copyright (c) 2011 United States Government as represented by the
 * Administrator of the National Aeronautics and Space Administration.
 * All Rights Reserved.
 *
 * Disclaimers:
 *
 * No Warranty: THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF
 * ANY KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED
 * TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO SPECIFICATIONS,
 * ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
 * OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL BE
 * ERROR FREE, OR ANY WARRANTY THAT DOCUMENTATION, IF PROVIDED, WILL CONFORM TO
 * THE SUBJECT SOFTWARE. THIS AGREEMENT DOES NOT, IN ANY MANNER, CONSTITUTE AN
 * ENDORSEMENT BY GOVERNMENT AGENCY OR ANY PRIOR RECIPIENT OF ANY RESULTS,
 * RESULTING DESIGNS, HARDWARE, SOFTWARE PRODUCTS OR ANY OTHER APPLICATIONS
 * RESULTING FROM USE OF THE SUBJECT SOFTWARE.  FURTHER, GOVERNMENT AGENCY
 * DISCLAIMS ALL WARRANTIES AND LIABILITIES REGARDING THIRD-PARTY SOFTWARE,
 * IF PRESENT IN THE ORIGINAL SOFTWARE, AND DISTRIBUTES IT "AS IS."
 *
 * Waiver and Indemnity:  RECIPIENT AGREES TO WAIVE ANY AND ALL CLAIMS AGAINST
 * THE UNITED STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS, AS WELL
 * AS ANY PRIOR RECIPIENT.  IF RECIPIENT'S USE OF THE SUBJECT SOFTWARE RESULTS
 * IN ANY LIABILITIES, DEMANDS, DAMAGES, EXPENSES OR LOSSES ARISING FROM SUCH
 * USE, INCLUDING ANY DAMAGES FROM PRODUCTS BASED ON, OR RESULTING FROM,
 * RECIPIENT'S USE OF THE SUBJECT SOFTWARE, RECIPIENT SHALL INDEMNIFY AND HOLD
 * HARMLESS THE UNITED STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS,
 * AS WELL AS ANY PRIOR RECIPIENT, TO THE EXTENT PERMITTED BY LAW.
 * RECIPIENT'S SOLE REMEDY FOR ANY SUCH MATTER SHALL BE THE IMMEDIATE,
 * UNILATERAL TERMINATION OF THIS AGREEMENT.
 *
 ******************************************************************************/

#pragma once

#include <algorithm>
#include <memory>
#include <optional>
#include <vector>

#include <boost/iterator/iterator_facade.hpp>

namespace crab {

// Numerical type for indexed objects
using index_t = uint64_t;

inline index_t index(index_t i) { return i; }

template <typename T>
inline index_t index(const T& x) { return x.index(); }

namespace patricia_trees_impl {

inline index_t highest_bit(index_t x, index_t m) {
    index_t x_ = x & ~(m - 1);
    index_t m_ = m;

    while (x_ != m_) {
        x_ = x_ & ~m_;
        m_ = 2 * m_;
    }
    return m_;
}

inline index_t compute_branching_bit(index_t p0, index_t m0, index_t p1, index_t m1) {
    return highest_bit(p0 ^ p1, std::max((index_t)1, 2 * std::max(m0, m1)));
}

inline index_t mask(index_t k, index_t m) { return (k | (m - 1)) & ~m; }

inline bool zero_bit(index_t k, index_t m) { return (k & m) == 0; }

inline bool match_prefix(index_t k, index_t p, index_t m) { return mask(k, m) == p; }

template <typename Key, typename Value>
class tree {
  public:
    using tree_t = tree<Key, Value>;
    using tree_ptr = std::shared_ptr<tree_t>;

    class partial_order_t {
      public:
        virtual bool leq(Value, Value) = 0;
        // True if the default value is the top element for the partial order (false if it is bottom)
        virtual bool default_is_top() = 0;

        virtual ~partial_order_t() = default;

    }; // class partial_order

    class binary_action_t {
      public:
        // if first element of the pair is true then bottom and ignore second element
        // else if second element of the pair is empty then top
        // else the value stored in the second element of the pair.
        // The operation is idempotent: apply(x, x) = x
        virtual std::pair<bool, std::optional<Value>> apply(const Value&, const Value&) = 0;

        // True if the default value is absorbing (false if it is neutral)
        virtual bool default_is_absorbing() = 0;

        virtual ~binary_action_t() = default;

    }; // class binary_action

    using binding_t = std::pair<const Key&, const Value&>;

  public:
    static tree_ptr make_node(index_t, index_t, tree_ptr, tree_ptr);
    static tree_ptr make_leaf(const Key&, const Value&);
    static std::pair<bool, tree_ptr> merge(tree_ptr, tree_ptr, binary_action_t&, bool);
    static tree_ptr join(tree_ptr t0, tree_ptr t1);
    static std::pair<bool, tree_ptr> insert(tree_ptr, const Key&, const Value&, binary_action_t&, bool);
    static tree_ptr remove(tree_ptr, const Key&);
    static bool compare(tree_ptr, tree_ptr, partial_order_t&, bool);

  public:
    [[nodiscard]] virtual std::size_t size() const = 0;
    [[nodiscard]] virtual bool is_leaf() const = 0;
    virtual binding_t binding() const = 0;
    virtual tree_ptr left_branch() const = 0;
    virtual tree_ptr right_branch() const = 0;
    [[nodiscard]] virtual index_t prefix() const = 0;
    [[nodiscard]] virtual index_t branching_bit() const = 0;
    virtual std::optional<Value> lookup(const Key&) const = 0;

  public:
    [[nodiscard]] bool is_node() const { return !is_leaf(); }

    virtual ~tree() = default;

  public:
    class iterator : public boost::iterator_facade<iterator, binding_t, boost::forward_traversal_tag, binding_t> {
        friend class boost::iterator_core_access;

      private:
        using branching_t = std::pair<tree_ptr, int>;
        using branching_stack_t = std::vector<branching_t>;

      private:
        tree_ptr _current;
        branching_stack_t _stack;

      public:
        iterator() = default;

        explicit iterator(tree_ptr t) { this->look_for_next_leaf(t); }

      private:
        void look_for_next_leaf(tree_ptr t) {
            if (t) {
                if (t->is_leaf()) {
                    this->_current = t;
                } else {
                    branching_t b(t, 0);
                    this->_stack.push_back(b);
                    this->look_for_next_leaf(t->left_branch());
                }
            } else {
                if (!this->_stack.empty()) {
                    branching_t up;
                    do {
                        up = this->_stack.back();
                        this->_stack.pop_back();
                    } while (!this->_stack.empty() && up.second == 1);
                    if (!(this->_stack.empty() && up.second == 1)) {
                        branching_t b(up.first, 1);
                        this->_stack.push_back(b);
                        this->look_for_next_leaf(up.first->right_branch());
                    }
                }
            }
        }

        void increment() {
            if (this->_current) {
                this->_current.reset();
                if (!this->_stack.empty()) {
                    branching_t up;
                    do {
                        up = this->_stack.back();
                        this->_stack.pop_back();
                    } while (!this->_stack.empty() && up.second == 1);
                    if (!(this->_stack.empty() && up.second == 1)) {
                        this->look_for_next_leaf(up.first->right_branch());
                    }
                }
            } else {
                CRAB_ERROR("Patricia tree: trying to increment an empty iterator");
            }
        }

        bool equal(const iterator& it) const {
            if (this->_current != it._current) {
                return false;
            }
            if (this->_stack.size() != it._stack.size()) {
                return false;
            }
            typename branching_stack_t::const_iterator s_it1, s_it2;
            s_it1 = this->_stack.begin();
            s_it2 = it._stack.begin();
            while (s_it1 != this->_stack.end()) {
                if (*s_it1 != *s_it2) {
                    return false;
                }
                ++s_it1;
                ++s_it2;
            }
            return true;
        }

        binding_t dereference() const {
            if (this->_current) {
                return this->_current->binding();
            } else {
                CRAB_ERROR("Patricia tree: trying to dereference an empty iterator");
            }
        }

    }; // class iterator

}; // class tree

template <typename Key, typename Value>
class node final : public tree<Key, Value> {
  private:
    using tree_ptr = typename tree<Key, Value>::tree_ptr;
    using binding_t = typename tree<Key, Value>::binding_t;
    using node_t = node<Key, Value>;

  private:
    std::size_t _size;
    index_t _prefix;
    index_t _branching_bit;
    tree_ptr _left_branch;
    tree_ptr _right_branch;

  public:
    node() = delete;
    node(const node_t&) = delete;
    node_t& operator=(const node_t&) = delete;

  public:
    node(index_t prefix_, index_t branching_bit_, tree_ptr left_branch_, tree_ptr right_branch_)
        : _size(0), _prefix(prefix_), _branching_bit(branching_bit_), _left_branch(left_branch_),
          _right_branch(right_branch_) {
        if (left_branch_) {
            this->_size += left_branch_->size();
        }
        if (right_branch_) {
            this->_size += right_branch_->size();
        }
    }

    [[nodiscard]] std::size_t size() const { return this->_size; }

    [[nodiscard]] index_t prefix() const { return this->_prefix; }

    [[nodiscard]] index_t branching_bit() const { return this->_branching_bit; }

    [[nodiscard]] bool is_leaf() const { return false; }

    binding_t binding() const { CRAB_ERROR("Patricia tree: trying to call binding() on a node"); }

    tree_ptr left_branch() const { return this->_left_branch; }

    tree_ptr right_branch() const { return this->_right_branch; }

    std::optional<Value> lookup(const Key& key) const {
        if (index(key) <= this->_prefix) {
            if (this->_left_branch) {
                return this->_left_branch->lookup(key);
            } else {
                return std::optional<Value>();
            }
        } else {
            if (this->_right_branch) {
                return this->_right_branch->lookup(key);
            } else {
                return std::optional<Value>();
            }
        }
    }

}; // class node

template <typename Key, typename Value>
class leaf final : public tree<Key, Value> {
  private:
    using tree_ptr = typename tree<Key, Value>::tree_ptr;
    using binding_t = typename tree<Key, Value>::binding_t;
    using leaf_t = leaf<Key, Value>;

  private:
    Key _key;
    Value _value;

  public:
    leaf() = delete;
    leaf(const leaf_t&) = delete;
    leaf_t& operator=(const leaf_t&) = delete;

  public:
    leaf(const Key& key_, const Value& value_) : _key(key_), _value(value_) {}

    [[nodiscard]] std::size_t size() const { return 1; }

    [[nodiscard]] index_t prefix() const { return index(this->_key); }

    [[nodiscard]] index_t branching_bit() const { return 0; }

    [[nodiscard]] bool is_leaf() const { return true; }

    binding_t binding() const { return binding_t(this->_key, this->_value); }

    tree_ptr left_branch() const { CRAB_ERROR("Patricia tree: trying to call left_branch() on a leaf"); }

    tree_ptr right_branch() const { CRAB_ERROR("Patricia tree: trying to call right_branch() on a leaf"); }

    std::optional<Value> lookup(const Key& key_) const {
        if (index(this->_key) == index(key_)) {
            return std::optional<Value>(this->_value);
        } else {
            return std::optional<Value>();
        }
    }

}; // class leaf

template <typename Key, typename Value>
auto tree<Key, Value>::make_node(index_t prefix, index_t branching_bit, tree_ptr left_branch, tree_ptr right_branch) -> tree_ptr {
    if (left_branch) {
        if (right_branch) {
            return
                typename tree<Key, Value>::tree_ptr(new node<Key, Value>(prefix, branching_bit, left_branch, right_branch));
        } else {
            return left_branch;
        }
    } else {
        if (right_branch) {
            return right_branch;
        } else {
            return {}; // both branches are empty
        }
    }
}

template <typename Key, typename Value>
auto tree<Key, Value>::make_leaf(const Key& key, const Value& value) -> tree_ptr {
    return typename tree<Key, Value>::tree_ptr(new leaf<Key, Value>(key, value));
}

template <typename Key, typename Value>
auto tree<Key, Value>::join(tree_ptr t0, tree_ptr t1) -> tree_ptr {
    index_t p0 = t0->prefix();
    index_t p1 = t1->prefix();
    index_t m = compute_branching_bit(p0, t0->branching_bit(), p1, t1->branching_bit());
    tree_ptr t;

    if (zero_bit(p0, m)) {
        t = make_node(mask(p0, m), m, t0, t1);
    } else {
        t = make_node(mask(p0, m), m, t1, t0);
    }
    return t;
}

template <typename Key, typename Value>
auto tree<Key, Value>::insert(tree_ptr t, const Key& key_, const Value& value_, binary_action_t& op,
                              bool combine_left_to_right) -> std::pair<bool, tree_ptr> {
    tree_ptr nil;
    std::pair<bool, tree_ptr> res_lb, res_rb;
    std::pair<bool, std::optional<Value>> new_value;
    std::pair<bool, tree_ptr> bottom = {true, nil};
    if (t) {
        if (t->is_node()) {
            index_t branching_bit = t->branching_bit();
            index_t prefix = t->prefix();
            if (match_prefix(index(key_), prefix, branching_bit)) {
                if (zero_bit(index(key_), branching_bit)) {
                    tree_ptr lb = t->left_branch();
                    tree_ptr new_lb;
                    if (lb) {
                        auto [is_bottom, tree_ptr] = insert(lb, key_, value_, op, combine_left_to_right);
                        if (is_bottom) {
                            return bottom;
                        }
                        new_lb = tree_ptr;
                    } else {
                        if (!op.default_is_absorbing()) {
                            new_lb = make_leaf(key_, value_);
                        }
                    }
                    if (new_lb == lb) {
                        return {false, t};
                    } else {
                        return {false, make_node(prefix, branching_bit, new_lb, t->right_branch())};
                    }
                } else {
                    tree_ptr rb = t->right_branch();
                    tree_ptr new_rb;
                    if (rb) {
                        auto [is_bottom, tree_ptr] = insert(rb, key_, value_, op, combine_left_to_right);
                        if (is_bottom) {
                            return bottom;
                        }
                        new_rb = tree_ptr;
                    } else {
                        if (!op.default_is_absorbing()) {
                            new_rb = make_leaf(key_, value_);
                        }
                    }
                    if (new_rb == rb) {
                        return {false, t};
                    } else {
                        return {false, make_node(prefix, branching_bit, t->left_branch(), new_rb)};
                    }
                }
            } else {
                if (op.default_is_absorbing()) {
                    return {false, t};
                } else {
                    return {false, join(make_leaf(key_, value_), t)};
                }
            }
        } else {
            const auto& [key, value] = t->binding();
            if (index(key) == index(key_)) {
                new_value = combine_left_to_right ? op.apply(value, value_) : op.apply(value_, value);
                if (new_value.first) {
                    return bottom;
                }
                if (new_value.second) {
                    if (*(new_value.second) == value) {
                        return {false, t};
                    } else {
                        return {false, make_leaf(key_, *(new_value.second))};
                    }
                } else {
                    return {false, nil};
                }
            } else {
                if (op.default_is_absorbing()) {
                    return {false, t};
                } else {
                    return {false, join(make_leaf(key_, value_), t)};
                }
            }
        }
    } else {
        if (op.default_is_absorbing()) {
            return {false, nil};
        } else {
            return {false, make_leaf(key_, value_)};
        }
    }
}

template <typename Key, typename Value>
auto tree<Key, Value>::remove(tree_ptr t, const Key& key_) -> tree_ptr {
    tree_ptr nil;
    index_t id = index(key_);
    if (t) {
        if (t->is_node()) {
            index_t branching_bit = t->branching_bit();
            index_t prefix = t->prefix();
            if (match_prefix(id, prefix, branching_bit)) {
                if (zero_bit(id, branching_bit)) {
                    tree_ptr lb = t->left_branch();
                    tree_ptr new_lb;
                    if (lb) {
                        new_lb = remove(lb, key_);
                    }
                    if (new_lb == lb) {
                        return t;
                    } else {
                        return make_node(prefix, branching_bit, new_lb, t->right_branch());
                    }
                } else {
                    tree_ptr rb = t->right_branch();
                    tree_ptr new_rb;
                    if (rb) {
                        new_rb = remove(rb, key_);
                    }
                    if (new_rb == rb) {
                        return t;
                    } else {
                        return make_node(prefix, branching_bit, t->left_branch(), new_rb);
                    }
                }
            } else {
                return t;
            }
        } else {
            binding_t b = t->binding();
            if (index(b.first) == id) {
                return nil;
            } else {
                return t;
            }
        }
    } else {
        return nil;
    }
}

template <typename Key, typename Value>
auto tree<Key, Value>::merge(tree_ptr s, tree_ptr t, binary_action_t& op,
                             bool combine_left_to_right) -> std::pair<bool, tree_ptr> {
    constexpr tree_ptr nil;
    constexpr std::pair<bool, tree_ptr> bottom = {true, nil};
    if (s) {
        if (t) {
            if (s == t) {
                return {false, s};
            } else if (s->is_leaf()) {
                auto& [key, old_value] = s->binding();
                if (op.default_is_absorbing()) {
                    if (std::optional<Value> value = t->lookup(key, old_value)) {
                        auto [is_bottom, tree] = combine_left_to_right ? op.apply(old_value, *value) : op.apply(*value, old_value);
                        if (is_bottom) {
                            return bottom;
                        }
                        if (tree) {
                            if (*tree == old_value) {
                                return {false, s};
                            } else {
                                return {false, make_leaf(key, *value)};
                            }
                        } else {
                            return {false, nil};
                        }
                    } else {
                        return {false, nil};
                    }
                } else {
                    return insert(t, key, old_value, op, !combine_left_to_right);
                }
            } else if (t->is_leaf()) {
                auto& [key, old_value] = t->binding();
                if (op.default_is_absorbing()) {
                    if (std::optional<Value> value = s->lookup(key)) {
                        auto [is_bottom, new_value] = combine_left_to_right ? op.apply(*value, old_value) : op.apply(old_value, *value);
                        if (is_bottom) {
                            return bottom;
                        }
                        if (new_value) {
                            if (*new_value == old_value) {
                                return {false, t};
                            } else {
                                return {false, make_leaf(old_value, *new_value)};
                            }
                        } else {
                            return {false, nil};
                        }
                    } else {
                        return {false, nil};
                    }
                } else {
                    return insert(s, key, old_value, op, combine_left_to_right);
                }
            } else {
                if (s->branching_bit() == t->branching_bit() && s->prefix() == t->prefix()) {
                    auto [is_bottom_lb, new_lb] = merge(s->left_branch(), t->left_branch(), op, combine_left_to_right);
                    if (is_bottom_lb) {
                        return bottom;
                    }
                    auto [is_bottom_rb, new_rb] = merge(s->right_branch(), t->right_branch(), op, combine_left_to_right);
                    if (is_bottom_rb) {
                        return bottom;
                    }
                    if (new_lb == s->left_branch() && new_rb == s->right_branch()) {
                        return {false, s};
                    } else if (new_lb == t->left_branch() && new_rb == t->right_branch()) {
                        return {false, t};
                    } else {
                        return {false, make_node(s->prefix(), s->branching_bit(), new_lb, new_rb)};
                    }
                } else if (s->branching_bit() > t->branching_bit() &&
                           match_prefix(t->prefix(), s->prefix(), s->branching_bit())) {
                    if (zero_bit(t->prefix(), s->branching_bit())) {
                        auto [is_bottom, new_lb] = merge(s->left_branch(), t, op, combine_left_to_right);
                        if (is_bottom) {
                            return bottom;
                        }
                        tree_ptr new_rb = op.default_is_absorbing() ? nil : s->right_branch();
                        if (new_lb == s->left_branch() && new_rb == s->right_branch()) {
                            return {false, s};
                        } else {
                            return {false, make_node(s->prefix(), s->branching_bit(), new_lb, new_rb)};
                        }
                    } else {
                        tree_ptr new_lb = op.default_is_absorbing() ? nil : s->left_branch();
                        auto [is_bottom, new_rb] = merge(s->right_branch(), t, op, combine_left_to_right);
                        if (is_bottom) {
                            return bottom;
                        }
                        if (new_lb == s->left_branch() && new_rb == s->right_branch()) {
                            return {false, s};
                        } else {
                            return {false, make_node(s->prefix(), s->branching_bit(), new_lb, new_rb)};
                        }
                    }
                } else if (s->branching_bit() < t->branching_bit() &&
                           match_prefix(s->prefix(), t->prefix(), t->branching_bit())) {
                    if (zero_bit(s->prefix(), t->branching_bit())) {
                        auto [is_bottom, new_lb] = merge(s, t->left_branch(), op, combine_left_to_right);
                        if (is_bottom) {
                            return bottom;
                        }
                        tree_ptr new_rb = op.default_is_absorbing() ? nil : t->right_branch();
                        if (new_lb == t->left_branch() && new_rb == t->right_branch()) {
                            return {false, t};
                        } else {
                            return {false, make_node(t->prefix(), t->branching_bit(), new_lb, new_rb)};
                        }
                    } else {
                        tree_ptr new_lb = op.default_is_absorbing() ? nil : t->left_branch();
                        auto [is_bottom, new_rb] = merge(s, t->right_branch(), op, combine_left_to_right);
                        if (is_bottom) {
                            return bottom;
                        }
                        if (new_lb == t->left_branch() && new_rb == t->right_branch()) {
                            return {false, t};
                        } else {
                            return {false, make_node(t->prefix(), t->branching_bit(), new_lb, new_rb)};
                        }
                    }
                } else {
                    if (op.default_is_absorbing()) {
                        return {false, nil};
                    } else {
                        return {false, join(s, t)};
                    }
                }
            }
        } else {
            if (op.default_is_absorbing()) {
                return {false, nil};
            } else {
                return {false, s};
            }
        }
    } else {
        if (op.default_is_absorbing()) {
            return {false, nil};
        } else {
            return {false, t};
        }
    }
}

template <typename Key, typename Value>
bool tree<Key, Value>::compare(tree_ptr s, tree_ptr t, partial_order_t& po,
                               bool compare_left_to_right) {
    if (s) {
        if (t) {
            if (s != t) {
                if (s->is_leaf()) {
                    auto [key, value] = s->binding();
                    std::optional<Value> value_ = t->lookup(key);
                    if (value_) {
                        Value left = compare_left_to_right ? value : *value_;
                        Value right = compare_left_to_right ? *value_ : value;
                        if (!po.leq(left, right)) {
                            return false;
                        }
                    } else {
                        if ((compare_left_to_right && !po.default_is_top()) ||
                            (!compare_left_to_right && po.default_is_top())) {
                            return false;
                        }
                    }
                    if (compare_left_to_right && po.default_is_top() && !t->is_leaf()) {
                        return false;
                    }
                    if (!compare_left_to_right && !po.default_is_top() && !t->is_leaf()) {
                        return false;
                    }
                } else if (t->is_leaf()) {
                    if (!compare(t, s, po, !compare_left_to_right)) {
                        return false;
                    }
                } else {
                    if (s->branching_bit() == t->branching_bit() && s->prefix() == t->prefix()) {
                        if (!compare(s->left_branch(), t->left_branch(), po, compare_left_to_right)) {
                            return false;
                        }
                        if (!compare(s->right_branch(), t->right_branch(), po, compare_left_to_right)) {
                            return false;
                        }
                    } else if (s->branching_bit() > t->branching_bit() &&
                               match_prefix(t->prefix(), s->prefix(), s->branching_bit())) {
                        if ((compare_left_to_right && !po.default_is_top()) ||
                            (!compare_left_to_right && po.default_is_top())) {
                            return false;
                        }
                        if (zero_bit(t->prefix(), s->branching_bit())) {
                            if (!compare(s->left_branch(), t, po, compare_left_to_right)) {
                                return false;
                            }
                        } else {
                            if (!compare(s->right_branch(), t, po, compare_left_to_right)) {
                                return false;
                            }
                        }
                    } else if (s->branching_bit() < t->branching_bit() &&
                               match_prefix(s->prefix(), t->prefix(), t->branching_bit())) {
                        if ((compare_left_to_right && po.default_is_top()) ||
                            (!compare_left_to_right && !po.default_is_top())) {
                            return false;
                        }
                        if (zero_bit(s->prefix(), t->branching_bit())) {
                            if (!compare(s, t->left_branch(), po, compare_left_to_right)) {
                                return false;
                            }
                        } else {
                            if (!compare(s, t->right_branch(), po, compare_left_to_right)) {
                                return false;
                            }
                        }
                    } else {
                        return false;
                    }
                }
            }
        } else {
            if ((compare_left_to_right && !po.default_is_top()) || (!compare_left_to_right && po.default_is_top())) {
                return false;
            }
        }
    } else {
        if (t) {
            if ((compare_left_to_right && po.default_is_top()) || (!compare_left_to_right && !po.default_is_top())) {
                return false;
            }
        } else {
            // s and t are empty
        }
    }
    return true;
}

} // namespace patricia_trees_impl

template <typename Key, typename Value>
class patricia_tree final {
  private:
    using tree_t = patricia_trees_impl::tree<Key, Value>;
    using tree_ptr = typename tree_t::tree_ptr;

  public:
    using patricia_tree_t = patricia_tree<Key, Value>;
    using binary_action_t = typename tree_t::binary_action_t;
    using partial_order_t = typename tree_t::partial_order_t;
    using binding_t = typename tree_t::binding_t;

  private:
    tree_ptr _tree;

  public:
    class iterator : public boost::iterator_facade<iterator, binding_t, boost::forward_traversal_tag, binding_t> {
        friend class boost::iterator_core_access;
        friend class patricia_tree<Key, Value>;

      private:
        typename tree_t::iterator _it;

      public:
        iterator() = default;

        explicit iterator(const patricia_tree_t& pt) : _it(pt._tree) {}

      private:
        explicit iterator(tree_ptr t) : _it(t) {}

        void increment() { ++this->_it; }

        bool equal(const iterator& other) const { return this->_it == other._it; }

        binding_t dereference() const { return *this->_it; }

    }; // class iterator

    class insert_op : public binary_action_t {
        std::pair<bool, std::optional<Value>> apply(const Value& /* old_value */, const Value& new_value) {
            return {false, std::optional<Value>(new_value)};
        }
        bool default_is_absorbing() { return false; }
    }; // class insert_op

  public:
    patricia_tree() = default;

    patricia_tree(const patricia_tree_t& t) : _tree(t._tree) {}

    patricia_tree& operator=(const patricia_tree_t& t) {
        this->_tree = t._tree;
        return *this;
    }

    [[nodiscard]] std::size_t size() const {
        if (this->_tree) {
            return this->_tree->size();
        } else {
            return 0;
        }
    }

    iterator begin() const { return iterator(this->_tree); }

    iterator end() const { return iterator(); }

    std::optional<Value> lookup(const Key& key) const {
        if (this->_tree) {
            return this->_tree->lookup(key);
        } else {
            return std::optional<Value>();
        }
    }

    bool merge_with(const patricia_tree_t& t, binary_action_t& op) {
        auto [is_bottom, tree_ptr] = tree_t::merge(this->_tree, t._tree, op, true);
        if (is_bottom) {
            return true; // bottom must be propagated
        } else {
            this->_tree = tree_ptr;
            return false;
        }
    }

    void insert(const Key& key, const Value& value) {
        insert_op op;
        auto [is_bottom, tree_ptr] = tree_t::insert(this->_tree, key, value, op, true);
        this->_tree = tree_ptr;
    }

    void remove(const Key& key) { this->_tree = tree_t::remove(this->_tree, key); }

    void clear() { this->_tree.reset(); }

    [[nodiscard]] bool empty() const { return !this->_tree; }

    bool leq(const patricia_tree_t& t, partial_order_t& po) const {
        return tree_t::compare(this->_tree, t._tree, po, true);
    }

}; // class patricia_tree

} // namespace crab