#ifndef XEX_H
#define XEX_H

/* A xex_segment represents one segment of a XEX file. */
typedef struct {
	unsigned short start_addr;
	unsigned short end_addr;
	unsigned char *object;
	int len;
	unsigned char has_ff_header;
} xex_segment;

/* object points to an array containing ONLY the data bytes, not the
	header bytes! Use xex_get_object() to reconstitute the full
	segment with headers.

	(Note that I'm using the word "object" in the sense of "object code",
	not in its OOP sense)
 */

#define XEX_RUNAD 0x2e0
#define XEX_INITAD 0x2e2

#define XEX_LSB(x) (x & 0xff)
#define XEX_MSB(x) ((x >> 8) & 0xff)
#define XEX_ADDR(x, y) ((unsigned char)x | ((unsigned char)y << 8))

/* All int functions return true for success or false for failure.
	On failure, the variable xex_errno will be set to one of the
	XERR_* constants. Caller may use xex_strerror(xex_errno) to
	get a human-readable error message (or xex_perror()).

	Note that XERR_NONE == 0, so you can use the construct:
	if(xex_errno) {
		// handle error here
	}
 */

#define XERR_NONE      0
#define XERR_SYSCALL   1
#define XERR_REVERSED  2
#define XERR_TRUNCATED 3
#define XERR_NULLDATA  4
#define XERR_MAXERR    5

extern int xex_errno;
extern int xex_verbose;

/* Initialize a xex_segment from the data pointed to by segment, which must
	not be null. segment must be a complete segment, including start/end
	addresses and possible $FFFF header. seg->object will point within the
	data, so don't free() it until you're done with seg! */
int xex_new_seg(xex_segment *seg, unsigned char *segment);

/* Set up seg as an init address segment. object must have room for at
	least 2 characters. A copy of the object pointer is stored in
	seg->object, so don't free(object) until you're done with seg. */
int xex_init_seg(xex_segment *seg, unsigned char *object, unsigned short addr);

/* Set up seg as an run address segment. object must have room for at
	least 2 characters. A copy of the object pointer is stored in seg->object,
	so don't free(object) until you're done with seg.*/
int xex_run_seg(xex_segment *seg, unsigned char *object, unsigned short addr);

/* Since this library does NOT allocate any memory, reading a segment must
	be done in two pieces. xex_fread_seg_header() reads the 4- or 6-byte
	header and sets seg->len to the size of the segment. xex_fread_seg_data()
	then reads this number of bytes from the file, into seg->object.

	Example usage:

	xex_segment seg;

	xex_fread_seg_header(&seg, my_file);
	seg.object = malloc(seg->len);
	xex_fread_seg_data(&seg, my_file);

	// ...later, the caller must free(seg.object) when done with it.

	xex_fread_seg_header() returns 0 on EOF, with xex_errno set to
	XERR_NONE. It also returns 0 on failure, but xex_errno will be
	something other than XERR_NONE.
*/
int xex_fread_seg_header(xex_segment *seg, FILE *file);
int xex_fread_seg_data(xex_segment *seg, FILE *file);

/* If you're dealing with xex files sequentially, and want to use a
	static buffer instead of malloc(), you can use xex_fread_seg() instead.
	seg->object should point to a buffer large enough to hold any segment's
	object code (64K, to be on the safe side), or you WILL get segfaults. */
int xex_fread_seg(xex_segment *seg, FILE *file);

/* xex_fwrite_seg() writes a segment to a file, which must be opened
	for output. */
int xex_fwrite_seg(xex_segment *seg, FILE *file);

/* Extract the object code from a segment. data must have room for len+6
	bytes (or len+4 if has_ff_header is false). data will be the raw
	segment data, suitable for writing to a XEX file. */
int xex_get_object(xex_segment *seg, unsigned char *data);

/* Sanity check a segment. Make sure start_addr is less than end_addr,
	that it doesn't wrap around the 6502 address space, and so on.
	Returns true if segment is OK. */
int xex_check_seg(xex_segment *seg);

/* Get run address from a segment, if there is one. This can handle the case
	where the address is embedded in a larger segment (that doesn't just consist
	of 2 bytes loaded at RUNAD). Returns -1 if there is no run address. */
int xex_get_run_addr(xex_segment *seg);

/* Same as above, for init address. */
int xex_get_init_addr(xex_segment *seg);

/* Get human-readable error message. If xex_errno is XSYSCALL, the system's
	strerror() is called. */
char *xex_strerror(int err);

/* Shortcut for xex_strerror(), like system's perror() */
void xex_perror(char *msg);

/* Debugging aid */
void xex_print_seg_info(xex_segment *seg);

#endif /* XEX_H */
