#ifndef STARS_ida_Interface_h
#define STARS_ida_Interface_h

#include <cstdio>

#include <map>

#include <pro.h>
#include <fpro.h>
#include <lines.hpp>
#include <segment.hpp>
#include <funcs.hpp>

#include "interfaces/abstract/STARSInterface.h"
#include "interfaces/abstract/STARSInstructionID.h"
#include "interfaces/idapro/STARSSegment.h"
#include "interfaces/idapro/STARSFunction.h"
#include "interfaces/idapro/STARSInstruction.h"

class STARS_IDA_Interface_t : public STARS_Interface_t
{
public:

	// Constructors
	STARS_IDA_Interface_t() : CGCBinary(false) {};

	// Destructor
	~STARS_IDA_Interface_t() {
		this->funcmap.clear();
		this->segmap.clear();
	}

	// Segment accessors

	virtual STARS_Segment_t *getseg(const STARS_ea_t &addr) {
		segment_t* seg = ::getseg(addr);
		return seg2Seg(seg);
	}

	virtual STARS_Segment_t *getnseg(const int &index) {
		segment_t* seg = ::getnseg(index);
		return seg2Seg(seg);
	}

	virtual std::size_t get_segm_qty() { return ::get_segm_qty(); };

	virtual STARS_Segment_t *get_next_seg(const STARS_ea_t &addr) {
		segment_t* seg = ::get_next_seg(addr);
		return seg2Seg(seg);
	}

	// Function accessors

	// find out how many functions there are
	virtual std::size_t get_func_qty() { return ::get_func_qty(); };

	// get the index-th function
	virtual STARS_Function_t *getn_func(int index) {
		func_t* Func = ::getn_func(index);
		return func2Func(Func);
	}

	// get the function at the given address
	virtual STARS_Function_t *get_func(const STARS_ea_t ea) {
		func_t* f = ::get_func(ea);
		return func2Func(f);
	}

	virtual void get_func_name(const STARS_ea_t &ea, char* name, const std::size_t &len) {
		func_t* f = ::get_func(ea);
		if (f) {
			func2Func(f)->GetFunctionName(name, len);
			return;
		}
		/* empty name */
		name[0] = '\0';
		return;
	}

	// Instruction creation.
	virtual STARS_Instruction_t *CreateInst(STARS_InstructionID_t InstID) {
		return new STARS_IDA_Instruction_t(InstID);
	}

	// File methods.
	virtual FILE *STARS_fopen(const char *file, const char *mode) { return qfopen(file, mode); };
	virtual int STARS_fclose(FILE *fp) { return qfclose(fp); };
	virtual int STARS_msg(const char *format, ...);
	virtual int STARS_fprintf(FILE *fp, const char *format, ...);
	virtual int STARS_fscanf(FILE *fp, const char *format, ...);
	virtual long STARS_ftell(FILE *fp);
	virtual char *STARS_fgets(char *buffer, int buflen, FILE *fp);
	virtual int STARS_fseek(FILE *fp, long offset, int whence);
	virtual int STARS_fgetc(FILE *fp) { return qfgetc(fp); };

	// String methods
	virtual char *STARS_strncat(char *dst, const char *src, std::size_t dstsize) { return qstrncat(dst, src, dstsize); };
	virtual char *STARS_strncpy(char *dst, const char *src, std::size_t dstsize) { return qstrncpy(dst, src, dstsize); };
	virtual int STARS_snprintf(char *buffer, std::size_t n, const char *format, ...);

	// Disasm methods.
#if (IDA_SDK_VERSION < 700)
	virtual STARS_ssize_t STARS_tag_remove(char *instr, char *buf, std::size_t bufsize) { return tag_remove(instr, buf, bufsize); };
#else
	virtual STARS_ssize_t STARS_tag_remove(char *instr, char *buf, std::size_t bufsize) { 
		qstring *temp = new qstring(); 
		temp->resize(bufsize);
		STARS_ssize_t ReturnLen = tag_remove(temp, buf); 
		if (0 <= ReturnLen) {
			qstrncpy(instr, temp->c_str(), bufsize);
		}
		delete temp;
		return ReturnLen;
	};
#endif
#if (IDA_SDK_VERSION < 700)
	virtual bool STARS_generate_disasm_line(STARS_ea_t addr, char *buf, std::size_t bufsize, int flags = 0) {
		return ::generate_disasm_line(addr, buf, bufsize, flags);
	}
#else
	virtual bool STARS_generate_disasm_line(STARS_ea_t addr, char *buf, std::size_t bufsize, int flags = 0) {
		qstring *temp = new qstring();
		temp->resize(bufsize);
		bool success = ::generate_disasm_line(temp, addr, flags);
		if (success) {
			qstrncpy(buf, temp->c_str(), bufsize);
		}
		delete temp;
		return success;
	}
#endif

	// Miscellaneous methods.
	virtual bool STARS_getenv(const char *varname) const { return qgetenv(varname, NULL); };
	virtual void SetCGCBinary(void) { CGCBinary = true; };
	virtual bool IsCGCBinary(void) const { return CGCBinary; };
	virtual bool InstHasNoCodeXrefs(STARS_InstructionID_t InstID) const;
	virtual bool IsInstJumpTarget(STARS_InstructionID_t InstID) const;
	virtual STARS_InstructionID_t FindFirstCallTarget(STARS_InstructionID_t CallInstID) const; // Find call target; first one, if any, for indirect call

	// Miscellaneous IDA-only methods.
	virtual void AuditTailChunkOwnership(void);
	virtual void AuditCodeTargets(void);

	// Detect IDA Pro func boundary problems, if code segment range; true if problems found
	virtual bool AuditFunctionBoundaries(const STARS_ea_t startEA, const STARS_ea_t endEA) const;
	// Detect IDA Pro func boundary problems using EH_FRAME FDE info; true if problems found
	virtual bool AuditEHFunctionBoundaries(void) const;

	virtual bool STARS_patch_byte(STARS_ea_t InstAddr, uint32_t ByteValue); // Patch IDA Pro database.

private:

	bool CGCBinary;

		STARS_Segment_t* seg2Seg(segment_t* seg)
			{
				if (!seg)
					return NULL;
				if (segmap[seg] == NULL)
					segmap[seg] = new STARS_IDA_Segment_t(seg);
				return segmap[seg];
			}
		std::map<segment_t*, STARS_Segment_t*> segmap;

		STARS_Function_t* func2Func(func_t* f)
			{
				if (!f)
					return NULL;
				if (funcmap[f] == NULL)
					funcmap[f] = new STARS_IDA_Function_t(f);
				return funcmap[f];
			}
		std::map<func_t*, STARS_Function_t*> funcmap;
};


#endif