/* dwarfpp: C++ binding for a useful subset of libdwarf, plus extra goodies.
 * 
 * abstract.hpp: base class and factory definitions
 *
 * Copyright (c) 2008--17, Stephen Kell. For licensing information, see the
 * LICENSE file in the root of the libdwarfpp tree.
 */

#ifndef DWARFPP_ABSTRACT_HPP_
#define DWARFPP_ABSTRACT_HPP_

#include "util.hpp"
#include "spec.hpp"
#include "opt.hpp"
#include "attr.hpp"

#include "libdwarf.hpp"

namespace dwarf
{
	using std::string;
	
	namespace core
	{
		using namespace dwarf::lib;
#ifndef NO_TLS
		extern __thread Dwarf_Error current_dwarf_error;
#else
#warning "No TLS, so DWARF error reporting is not thread-safe."
		extern Dwarf_Error current_dwarf_error;
#endif
		// forward declarations
		struct root_die;
		struct iterator_base;
		struct dwarf_current_factory_t;
		struct basic_die;
		struct compile_unit_die;
		using dwarf::spec::opt;
		using dwarf::spec::spec;
		using dwarf::spec::DEFAULT_DWARF_SPEC; // FIXME: ... or get rid of spec:: namespace?

		/* This is a small interface designed to be implementable over both 
		 * libdwarf Dwarf_Die handles and whatever other representation we 
		 * choose. */
		struct abstract_die
		{
			// basically the "libdwarf methods" on core::Die...
			// ... but note that we can change the interface a little, e.g. see get_name()
			// ... to make it more generic but perhaps a little less efficient
			
			virtual Dwarf_Off get_offset() const = 0;
			virtual Dwarf_Half get_tag() const = 0;
			virtual opt<string> get_name() const = 0;
			// we can't do this because 
			// - it fixes string deletion behaviour to libdwarf-style, 
			// - it creates a circular dependency with the contents of libdwarf-handles.hpp
			//virtual unique_ptr<const char, string_deleter> get_raw_name() const = 0;
			virtual Dwarf_Off get_enclosing_cu_offset() const = 0;
			virtual bool has_attr(Dwarf_Half attr) const = 0;
			inline bool has_attribute(Dwarf_Half attr) const { return has_attr(attr); }
			virtual encap::attribute_map copy_attrs() const = 0;
			/* HMM. Can we make this non-virtual? 
			 * Just move the code from Die (and get rid of that method)? */
			virtual spec& get_spec(root_die& r) const = 0;
			string summary() const;
		};
		
		/* the in-memory version, for synthetic (non-library-backed) DIEs. */
		struct in_memory_abstract_die: public virtual abstract_die
		{
			root_die *p_root;
			Dwarf_Off m_offset;
			Dwarf_Off m_cu_offset;
			Dwarf_Half m_tag;
			struct attribute_map : public encap::attribute_map
			{
			private:
				in_memory_abstract_die *p_owner;
			public:
				// constructor -- we start empty and belong to an instance of in_memory_abstract_die
				attribute_map(in_memory_abstract_die& d) : p_owner(&d) {}
				// using encap::attribute_map::attribute_map(
				typedef encap::attribute_map super;
			private:
				void update_cache_on_insert(iterator inserted);
			public:
				std::pair<iterator, bool> insert(const value_type& val)
				{
					auto ret = this->super::insert(val);
					auto inserted = ret.second;
					if (inserted) update_cache_on_insert(ret.first);
					return ret;
				}
				std::pair<iterator,bool> insert(value_type&& val) // was template <typename P>
				{
					auto ret = this->super::insert(val);
					auto inserted = ret.second;
					if (inserted) update_cache_on_insert(ret.first);
					return ret;
				}
				iterator
				insert(const_iterator position, const value_type& val)
				{
					unsigned size_before = size();
					auto ret = this->super::insert(position, val);
					if (size() > size_before) update_cache_on_insert(ret);
					return ret;
				}
				iterator
				insert(const_iterator position, value_type&& val) // was template <typename P>
				{
					unsigned size_before = size();
					auto ret = this->super::insert(position, val);
					if (size() > size_before) update_cache_on_insert(ret);
					return ret;
				}
			} m_attrs;
			
			Dwarf_Off get_offset() const { return m_offset; }
			Dwarf_Half get_tag() const { return m_tag; }
			opt<string> get_name() const 
			{ return has_attr(DW_AT_name) ? m_attrs.find(DW_AT_name)->second.get_string() : opt<string>(); }
			Dwarf_Off get_enclosing_cu_offset() const 
			{ return m_cu_offset; }
			bool has_attr(Dwarf_Half attr) const 
			{ return m_attrs.find(attr) != m_attrs.end(); }
			encap::attribute_map copy_attrs() const
			{ return m_attrs; }
			encap::attribute_map& attrs() 
			{ return m_attrs; }
			inline spec& get_spec(root_die& r) const;
			root_die& get_root() const
			{ return *p_root; }
			
			in_memory_abstract_die(root_die& r, Dwarf_Off offset, Dwarf_Off cu_offset, Dwarf_Half tag)
			 : p_root(&r), m_offset(offset), m_cu_offset(cu_offset), m_tag(tag), m_attrs(*this)
			{}
		};

		// now we can define factory
		struct factory
		{
			/* This mess is caused by spec bootstrapping. We have to construct 
			 * the CU payload to read its spec info, so this doesn't work in
			 * the case of CUs. All factories behave the same for CUs, 
			 * calling the make_cu_payload method. */
		protected:
			virtual basic_die *make_non_cu_payload(abstract_die&& h, root_die& r) = 0;
			compile_unit_die *make_cu_payload(abstract_die&& , root_die& r);
			compile_unit_die *make_new_cu(root_die& r, std::function<compile_unit_die*()> constructor);
		public:
			inline basic_die *make_payload(abstract_die&& h, root_die& r);
			basic_die *make_new(const iterator_base& parent, Dwarf_Half tag);
			
			static inline factory& for_spec(dwarf::spec::spec& def);
			virtual basic_die *dummy_for_tag(Dwarf_Half tag) = 0;
		};
		struct dwarf_current_factory_t : public factory
		{
			basic_die *make_non_cu_payload(abstract_die&& h, root_die& r);
			basic_die *dummy_for_tag(Dwarf_Half tag);
		};
		extern dwarf_current_factory_t dwarf_current_factory;
		inline factory& factory::for_spec(dwarf::spec::abstract_def& def)
		{
			if (&def == &dwarf::spec::dwarf_current) return dwarf_current_factory;
			assert(false); // FIXME support more specs
		}
		struct in_memory_abstract_die;
	}
}

#endif
