#ifndef ehp_priv_hpp
#define ehp_priv_hpp

#include <iostream>
#include <iomanip>
#include <fstream>
#include <limits>
#include <stdlib.h>
#include <string.h>
#include <map>
#include <assert.h>
#include <algorithm>
#include <memory>
#include <set>

#include "ehp_dwarf2.hpp"
#include "scoop_replacement.hpp"


namespace EHP
{

using namespace std;


template <int ptrsize>
class eh_frame_util_t 
{
	public: 
	template <class T> 
	static bool read_type(T &value, uint32_t &position, const uint8_t* const data, const uint32_t max);
	template <class T> 
	static bool read_type_with_encoding
		(const uint8_t encoding, T &value, 
		uint32_t &position, 
		const uint8_t* const data, 
		const uint32_t max, 
		const uint64_t section_start_addr );

	static bool read_string 
		(std::string &s, 
		uint32_t & position, 
		const uint8_t* const data, 
		const uint32_t max);


	// see https://en.wikipedia.org/wiki/LEB128
	static bool read_uleb128 
		( uint64_t &result, 
		uint32_t& position, 
		const uint8_t* const data, 
		const uint32_t max);

	// see https://en.wikipedia.org/wiki/LEB128
	static bool read_sleb128 ( 
		int64_t &result, 
		uint32_t & position, 
		const uint8_t* const data, 
		const uint32_t max);
	
	static bool read_length(
		uint64_t &act_length, 
		uint32_t &position, 
		const uint8_t* const data, 
		const uint32_t max);
};

template <int ptrsize>
class eh_program_insn_t  : public EHProgramInstruction_t
{
	public: 
	
	eh_program_insn_t() ;
	eh_program_insn_t(const std::string &s) ;

	void print(uint64_t &pc, int64_t caf=1) const;

	void push_byte(uint8_t c) ;

	static void print_uleb_operand(
		uint32_t pos, 
		const uint8_t* const data, 
		const uint32_t max) ;

	static void print_sleb_operand(
		uint32_t pos, 
		const uint8_t* const data, 
		const uint32_t max) ;

	bool parse_insn(
		uint8_t opcode, 
		uint32_t& pos, 
		const uint8_t* const data, 
		const uint32_t &max);

	bool isNop() const ;
	bool isRestoreState() const ;
	bool isRememberState() const ;

	bool advance(uint64_t &cur_addr, uint64_t CAF) const ;

	const std::vector<uint8_t>& getBytes() const ;
	std::vector<uint8_t>& getBytes() ;

	private:

	std::vector<uint8_t> program_bytes;
};

template <int ptrsize>
bool operator<(const eh_program_insn_t<ptrsize>& a, const eh_program_insn_t<ptrsize>& b);

template <int ptrsize>
class eh_program_t : public EHProgram_t
{
	public:
	void push_insn(const eh_program_insn_t<ptrsize> &i); 

	void print(const uint64_t start_addr=0) const;

	bool parse_program(
		const uint32_t& program_start_position, 
		const uint8_t* const data, 
		const uint32_t &max_program_pos);
        virtual shared_ptr<EHProgramInstructionVector_t> getInstructions() const ;
	std::vector<eh_program_insn_t <ptrsize> >& getInstructionsInternal() ;
	const std::vector<eh_program_insn_t <ptrsize> >& getInstructionsInternal() const ;

	private:
	std::vector<eh_program_insn_t <ptrsize> > instructions;
};

template <int ptrsize>
bool operator<(const eh_program_t<ptrsize>& a, const eh_program_t<ptrsize>& b);

template <int ptrsize>
class cie_contents_t : public CIEContents_t, private eh_frame_util_t<ptrsize>
{
	private:
	uint64_t cie_position;
	uint64_t length;
	uint8_t cie_id;
	uint8_t cie_version;
	std::string augmentation;
	uint64_t code_alignment_factor;
	int64_t data_alignment_factor;
	uint64_t return_address_register_column;
	uint64_t augmentation_data_length;
	uint8_t personality_encoding;
	uint64_t personality;
	uint8_t lsda_encoding;
	uint8_t fde_encoding;
	eh_program_t<ptrsize> eh_pgm;

	public:

	cie_contents_t() ;
	
	const eh_program_t<ptrsize>& getProgram() const ;
	uint64_t getCAF() const ;
	int64_t getDAF() const ;
	uint64_t getPersonality() const ;
	uint64_t getReturnRegister() const ;

	std::string getAugmentation() const ;
	uint8_t getLSDAEncoding() const ;
	uint8_t getFDEEncoding() const ;

	bool parse_cie(
		const uint32_t &cie_position, 
		const uint8_t* const data, 
		const uint32_t max, 
		const uint64_t eh_addr);
	void print() const ;
};

template <int ptrsize>
class lsda_call_site_action_t : public LSDACallSiteAction_t, private eh_frame_util_t<ptrsize>
{
	private:
	int64_t action;

	public:
	lsda_call_site_action_t() ;
	int64_t getAction() const ;

	bool parse_lcsa(uint32_t& pos, const uint8_t* const data, const uint64_t max, bool &end);
	void print() const;
};

template <int ptrsize>
bool operator< (const lsda_call_site_action_t <ptrsize> &lhs, const lsda_call_site_action_t <ptrsize> &rhs);

template <int ptrsize>
class lsda_type_table_entry_t: public LSDATypeTableEntry_t, private eh_frame_util_t<ptrsize>
{
	private:
	uint64_t pointer_to_typeinfo;
	uint64_t tt_encoding;
	uint64_t tt_encoding_size;

	public:
	lsda_type_table_entry_t() ; 

	uint64_t getTypeInfoPointer() const ;
	uint64_t getEncoding() const ;
	uint64_t getTTEncodingSize() const ;

	bool parse(
		const uint64_t p_tt_encoding, 	
		const uint64_t tt_pos, 	
		const uint64_t index,
		const uint8_t* const data, 
		const uint64_t max,  
		const uint64_t data_addr
		);

	void print() const;
	
};

template <int ptrsize>
class lsda_call_site_t : public LSDACallSite_t, private eh_frame_util_t<ptrsize>
{
	private:
	uint64_t call_site_offset;
	uint64_t call_site_addr;
	uint64_t call_site_length;
	uint64_t call_site_end_addr;
	uint64_t landing_pad_offset;
	uint64_t landing_pad_addr;
	uint64_t action;
	uint64_t action_table_offset;
	uint64_t action_table_addr;

	std::vector<lsda_call_site_action_t <ptrsize> > action_table;

	public:
	lsda_call_site_t() ;

	shared_ptr<LSDCallSiteActionVector_t> getActionTable() const       { assert(0); }
	const std::vector<lsda_call_site_action_t <ptrsize> >& getActionTableInternal() const { return action_table; }
	      std::vector<lsda_call_site_action_t <ptrsize> >& getActionTableInternal()       { return action_table; }

	uint64_t getCallSiteAddress() const  { return call_site_addr ; } 
	uint64_t getCallSiteEndAddress() const  { return call_site_end_addr ; } 
	uint64_t getLandingPadAddress() const  { return landing_pad_addr ; } 

	bool parse_lcs(	
		const uint64_t action_table_start_addr, 	
		const uint64_t cs_table_start_addr, 	
		const uint8_t cs_table_encoding, 
		uint32_t &pos, 
		const uint8_t* const data, 
		const uint64_t max,  /* call site table max */
		const uint64_t data_addr, 
		const uint64_t landing_pad_base_addr,
		const uint64_t gcc_except_table_max);

	void print() const;

//	bool appliesTo(const libIRDB::Instruction_t* insn) const;

};


// short hand for a vector of call sites
template <int ptrsize>  using call_site_table_t = std::vector<lsda_call_site_t <ptrsize> > ;
template <int ptrsize>  using lsda_type_table_t = std::vector<lsda_type_table_entry_t <ptrsize> > ;

template <int ptrsize>
class lsda_t : public LSDA_t, private eh_frame_util_t<ptrsize>
{
	private:
	uint8_t landing_pad_base_encoding;
	uint64_t landing_pad_base_addr; // often ommitted. when ommitted, filled in from FDE region start.
	uint8_t type_table_encoding;
	uint64_t type_table_offset;
	uint64_t type_table_addr;
	uint8_t cs_table_encoding;
	uint64_t cs_table_start_offset;
	uint64_t cs_table_start_addr;
	uint64_t cs_table_length;
	uint64_t cs_table_end_addr;
	uint64_t action_table_start_addr;
	call_site_table_t<ptrsize>  call_site_table;
	lsda_type_table_t<ptrsize> type_table;

	public:

	uint8_t getTTEncoding() const ;
	
	lsda_t() ;

	bool parse_lsda(const uint64_t lsda_addr, 
			const ScoopReplacement_t* gcc_except_scoop_data,
	                const uint64_t fde_region_start
	                );
	void print() const;

        shared_ptr<CallSiteVector_t> getCallSites() const ;
        const call_site_table_t<ptrsize> getCallSitesInternal() const { return call_site_table;}

        shared_ptr<TypeTableVector_t> getTypeTable() const ;

};



template <int ptrsize>
class fde_contents_t : public FDEContents_t, eh_frame_util_t<ptrsize> 
{
	uint32_t fde_position;
	uint32_t cie_position;
	uint64_t length;
	uint8_t id;
	uint64_t fde_start_addr;
	uint64_t fde_end_addr;
	uint64_t fde_range_len;
	uint64_t lsda_addr;


	lsda_t<ptrsize> lsda;
	eh_program_t<ptrsize> eh_pgm;
	cie_contents_t<ptrsize> cie_info;

	public:
	fde_contents_t() ;
	fde_contents_t(const uint64_t start_addr, const uint64_t end_addr)
		: 
		fde_start_addr(start_addr),
		fde_end_addr(end_addr)
	{} 

//	bool appliesTo(const libIRDB::Instruction_t* insn) const;

	uint64_t getStartAddress() const { return fde_start_addr; } 
	uint64_t getEndAddress() const {return fde_end_addr; }

	uint64_t getFDEStartAddress() const { return fde_start_addr; } 
	uint64_t getFDEEndAddress() const {return fde_end_addr; }

	const cie_contents_t<ptrsize>& getCIE() const ;
	cie_contents_t<ptrsize>& getCIE() ;

	const eh_program_t<ptrsize>& getProgram() const ;
	eh_program_t<ptrsize>& getProgram() ;

	shared_ptr<LSDA_t> getLSDA() const { return shared_ptr<LSDA_t>(new lsda_t<ptrsize>(lsda)) ;  }
	const lsda_t<ptrsize>& getLSDAInternal() const { return lsda; }

        uint64_t getLSDAAddress() const { return lsda_addr; }  

	bool parse_fde(
		const uint32_t &fde_position, 
		const uint32_t &cie_position, 
		const uint8_t* const data, 
		const uint64_t max, 
		const uint64_t eh_addr,
		const ScoopReplacement_t *gcc_except_scoop);

	void print() const;


};

template <int ptrsize>
bool operator<(const fde_contents_t<ptrsize>& a, const fde_contents_t<ptrsize>& b) { return a.getFDEEndAddress()-1 < b.getFDEStartAddress(); }


template <int ptrsize>
class split_eh_frame_impl_t : public EHFrameParser_t
{
	private: 

	unique_ptr<ScoopReplacement_t> eh_frame_scoop;
	unique_ptr<ScoopReplacement_t> eh_frame_hdr_scoop;
	unique_ptr<ScoopReplacement_t> gcc_except_table_scoop;

	std::vector<cie_contents_t <ptrsize> > cies;
	std::set<fde_contents_t <ptrsize> > fdes;


	bool iterate_fdes();

	public:

	split_eh_frame_impl_t
		(
		const ScoopReplacement_t &eh_frame,
		const ScoopReplacement_t &eh_frame_hdr,
		const ScoopReplacement_t &gcc_except_table 
		)
		:
			eh_frame_scoop(new ScoopReplacement_t(eh_frame)),
			eh_frame_hdr_scoop(new ScoopReplacement_t(eh_frame_hdr)),
			gcc_except_table_scoop(new ScoopReplacement_t(gcc_except_table))
	{
	}

	bool parse();
	void print() const;

        virtual const shared_ptr<FDEVector_t> getFDEs() const;
        virtual const shared_ptr<CIEVector_t> getCIEs() const;


};

}
#endif