/*
 * libtilemcore - Graphing calculator emulation library
 *
 * Copyright (C) 2009-2012 Benjamin Moody
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see
 * <http://www.gnu.org/licenses/>.
 */

#ifndef _TILEM_H
#define _TILEM_H

#include "tilemint.h"

#ifdef __cplusplus
extern "C" {
#endif

/* Basic integer types */
typedef uint8_t byte;
typedef uint16_t word;
typedef uint32_t dword;
typedef uint64_t qword;

/* Structure types */
typedef struct _TilemHardware TilemHardware;
typedef struct _TilemCalc TilemCalc;

/* Useful macros */
#if __GNUC__ >= 3
# define TILEM_ATTR_PURE __attribute__((__pure__))
# define TILEM_ATTR_UNUSED __attribute__((__unused__))
# define TILEM_ATTR_MALLOC __attribute__((__malloc__))
# define TILEM_ATTR_PRINTF(x,y) __attribute__((__format__(__printf__,x,y)))
# define TILEM_LIKELY(xxx) (__builtin_expect((xxx), 1))
# define TILEM_UNLIKELY(xxx) (__builtin_expect((xxx), 0))
#else
# define TILEM_ATTR_PURE
# define TILEM_ATTR_UNUSED
# define TILEM_ATTR_MALLOC
# define TILEM_ATTR_PRINTF(x,y)
# define TILEM_LIKELY(xxx) (xxx)
# define TILEM_UNLIKELY(xxx) (xxx)
#endif

#define TILEM_DWORD_TO_PTR(xxx) ((void*)(uintptr_t)(xxx))
#define TILEM_PTR_TO_DWORD(xxx) ((dword)(uintptr_t)(xxx))

/* Memory allocation */
void* tilem_malloc(size_t size) TILEM_ATTR_MALLOC;
void* tilem_malloc0(size_t size) TILEM_ATTR_MALLOC;
void* tilem_malloc_atomic(size_t size) TILEM_ATTR_MALLOC;
void* tilem_try_malloc(size_t size) TILEM_ATTR_MALLOC;
void* tilem_try_malloc0(size_t size) TILEM_ATTR_MALLOC;
void* tilem_try_malloc_atomic(size_t size) TILEM_ATTR_MALLOC;
void* tilem_realloc(void* ptr, size_t size) TILEM_ATTR_MALLOC;
void tilem_free(void* ptr);
#define tilem_new(ttt, nnn) ((ttt*) tilem_malloc((nnn) * sizeof(ttt)));
#define tilem_new0(ttt, nnn) ((ttt*) tilem_malloc0((nnn) * sizeof(ttt)));
#define tilem_new_atomic(ttt, nnn) ((ttt*) tilem_malloc_atomic((nnn) * sizeof(ttt)));

#define tilem_try_new(ttt, nnn) ((ttt*) tilem_try_malloc((nnn) * sizeof(ttt)));
#define tilem_try_new0(ttt, nnn) ((ttt*) tilem_try_malloc0((nnn) * sizeof(ttt)));
#define tilem_try_new_atomic(ttt, nnn) ((ttt*) tilem_try_malloc_atomic((nnn) * sizeof(ttt)));
#define tilem_renew(ttt, ppp, nnn) ((ttt*) tilem_realloc((ppp), (nnn) * sizeof(ttt)))

/* Message/error logging */

/* Write an informative message.  This can be used to notify the user
   of major occurences, such as changes in the Flash protection.
   These messages can occur regularly in normal operation and are
   provided chiefly to aid in debugging. */
void tilem_message(TilemCalc* calc, const char* msg, ...)
	TILEM_ATTR_PRINTF(2, 3);

/* Write a warning message.  These messages occur when the calculator
   software (either the OS or a user program) performs an invalid
   operation; these messages often indicate a bug in the calculator
   software, but are otherwise harmless. */
void tilem_warning(TilemCalc* calc, const char* msg, ...)
	TILEM_ATTR_PRINTF(2, 3);

/* Write a warning about an internal error.  These messages should
   never occur and indicate a bug in TilEm. */
void tilem_internal(TilemCalc* calc, const char* msg, ...)
	TILEM_ATTR_PRINTF(2, 3);


/* Z80 CPU */

/* This union allows us to manipulate register pairs as either byte or
   word values.  It may need to be modified for really unusual host
   CPUs. */
typedef union _TilemZ80Reg {
#ifdef WORDS_BIGENDIAN
	struct {
		byte h3, h2, h, l;
	} b;

	struct {
		word h, l;
	} w;

	dword d;
#else
	struct {
		byte l, h, h2, h3;
	} b;

	struct {
		word l, h;
	} w;

	dword d;
#endif
} TilemZ80Reg;

typedef struct _TilemZ80Regs {
	TilemZ80Reg af, bc, de, hl;
	TilemZ80Reg ix, iy, pc, sp;
	TilemZ80Reg ir, wz, wz2;
	TilemZ80Reg af2, bc2, de2, hl2;
	int iff1, iff2, im;
	byte r7;
} TilemZ80Regs;

/* Breakpoint types */
enum {
	TILEM_BREAK_MEM_READ = 1, /* Break after reading from memory */
	TILEM_BREAK_MEM_EXEC,	  /* Break prior to executing from memory */
	TILEM_BREAK_MEM_WRITE,	  /* Break after writing to memory */
	TILEM_BREAK_PORT_READ,	  /* Break after reading from port */
	TILEM_BREAK_PORT_WRITE,	  /* Break after writing to port */
	TILEM_BREAK_EXECUTE,	  /* Break after executing opcode */

	TILEM_BREAK_TYPE_MASK = 0xffff,

	TILEM_BREAK_PHYSICAL = 0x10000, /* Use physical addresses */
	TILEM_BREAK_DISABLED = 0x20000  /* Disabled breakpoint */
};

/* Emulation flags */
enum {
	TILEM_Z80_BREAK_INVALID = 1,      /* Break on invalid
					     instructions */
	TILEM_Z80_BREAK_UNDOCUMENTED = 2, /* Break on undocumented
					     instructions */
	TILEM_Z80_SKIP_UNDOCUMENTED  = 4, /* Ignore undocumented
					     instructions entirely
					     (act as two NOPs) */
	TILEM_Z80_RESET_UNDOCUMENTED = 8, /* Reset CPU following
	                                     undocumented instructions */
	TILEM_Z80_BREAK_EXCEPTIONS = 16,  /* Break on hardware exceptions */
	TILEM_Z80_IGNORE_EXCEPTIONS = 32  /* Ignore hardware exceptions */
};

/* Reasons for stopping emulation */
enum {
	TILEM_STOP_TIMEOUT = 0,            /* stopped due to timeout */
	TILEM_STOP_BREAKPOINT = 1,         /* stopped due to breakpoint */
	TILEM_STOP_INVALID_INST = 2,       /* invalid instruction */
	TILEM_STOP_UNDOCUMENTED_INST = 4,  /* undocumented instruction */
	TILEM_STOP_EXCEPTION = 8,	   /* hardware exception */
	TILEM_STOP_LINK_STATE = 16,        /* blacklink state change */
	TILEM_STOP_LINK_READ_BYTE = 32,    /* graylink finished reading byte */
	TILEM_STOP_LINK_WRITE_BYTE = 64,   /* graylink finished writing byte */
	TILEM_STOP_LINK_ERROR = 128        /* graylink encountered error */
};

/* Types of interrupt */
enum {
	TILEM_INTERRUPT_ON_KEY = 1, /* ON key pressed */
	TILEM_INTERRUPT_TIMER1 = 2, /* Main interrupt timer */
	TILEM_INTERRUPT_TIMER2 = 4, /* Alt. interrupt timer (83/83+) */
	TILEM_INTERRUPT_USER_TIMER1 = 8, /* Programmable timers (83+SE) */
	TILEM_INTERRUPT_USER_TIMER2 = 16,
	TILEM_INTERRUPT_USER_TIMER3 = 32,
	TILEM_INTERRUPT_LINK_ACTIVE = 512, /* Link port state changed */
	TILEM_INTERRUPT_LINK_READ = 1024,  /* Link assist read a byte */
	TILEM_INTERRUPT_LINK_IDLE = 2048,  /* Link assist is idle */
	TILEM_INTERRUPT_LINK_ERROR = 4096  /* Link assist failed */
};

/* Types of hardware exception */
enum {
	TILEM_EXC_RAM_EXEC = 1,    /* Executing at invalid RAM address */
	TILEM_EXC_FLASH_EXEC = 2,  /* Executing at invalid Flash address */
	TILEM_EXC_FLASH_WRITE = 4, /* Writing to invalid Flash address */
	TILEM_EXC_INSTRUCTION = 8  /* Invalid instruction */
};

/* Constant hardware timer IDs */
enum {
	TILEM_TIMER_NONE = 0,
	TILEM_TIMER_LCD_DELAY,
	TILEM_TIMER_FLASH_DELAY,
	TILEM_TIMER_LINK_ASSIST,
	TILEM_TIMER_USER1,
	TILEM_TIMER_USER2,
	TILEM_TIMER_USER3,
	TILEM_TIMER_HW
};

#define TILEM_NUM_SYS_TIMERS (TILEM_TIMER_HW - 1)

/* Type of a timer callback function.  Second arg is the callback data
   passed to tilem_z80_add_timer(). */
typedef void (*TilemZ80TimerFunc)(TilemCalc*, void*);

/* Type of a breakpoint test function.  Second arg is the memory
   address (or opcode in the case of TILEM_BREAK_EXECUTE breakpoints.)
   Third arg is the callback data passed to
   tilem_z80_add_breakpoint(). */
typedef int (*TilemZ80BreakpointFunc)(TilemCalc*, dword, void*);

typedef struct _TilemZ80Timer TilemZ80Timer;
typedef struct _TilemZ80Breakpoint TilemZ80Breakpoint;

typedef struct _TilemZ80 {
	TilemZ80Regs r;
	unsigned int interrupts; /* Currently active interrupts */
	int clockspeed;		/* Current CPU speed (kHz) */
	int halted;
	unsigned int exception;
	dword clock;
	dword lastwrite;
	dword lastlcdwrite;

	unsigned int emuflags;

	int ntimers;
	TilemZ80Timer* timers;
	int timer_cpu;	       /* Sorted list of timers (CPU-based) */
	int timer_rt;	       /* Sorted list of timers (realtime) */
	int timer_free;	       /* List of free timer structs */

	int nbreakpoints;
	TilemZ80Breakpoint* breakpoints;
	int breakpoint_mr;	/* Memory read breakpoints */
	int breakpoint_mx;	/* Memory exec breakpoints */
	int breakpoint_mw;	/* Memory write breakpoints */
	int breakpoint_pr;	/* Port read breakpoints */
	int breakpoint_pw;	/* Port write breakpoints */
	int breakpoint_op;	/* Opcode breakpoints */
	int breakpoint_mpr;	/* Physical mem read breakpoints */
	int breakpoint_mpx;	/* Physical mem exec breakpoints */
	int breakpoint_mpw;	/* Physical mem write breakpoints */
	int breakpoint_disabled; /* Disabled breakpoints */
	int breakpoint_free;	/* List of free bp structs */

	int stopping;
	dword stop_reason;
	dword stop_mask;
	int stop_breakpoint;
} TilemZ80;

/* Reset CPU */
void tilem_z80_reset(TilemCalc* calc);

/* Halt simulation */
void tilem_z80_stop(TilemCalc* calc, dword reason);

/* Set CPU speed (kHz) */
void tilem_z80_set_speed(TilemCalc* calc, int speed);

/* Raise a hardware exception */
void tilem_z80_exception(TilemCalc* calc, unsigned type);

/* Add a timer with the given callback function and data.  The
   callback function will be called after 'count' time units, and
   every 'period' time units thereafter.  If rt = 0, the time units
   are CPU clock cycles; if rt = 1, time units are microseconds.

   Note that if a timer is set in response to a memory read or write,
   the length of the delay may be off by as much as 19 clock cycles;
   the precise timings for these are not (yet) properly emulated.
   Timers set in response to port I/O events will be accurate to the
   nearest CPU clock cycle.

   The timer is considered to have "fired" as soon as the specified
   amount of time has elapsed.  The callback function, however, may
   not be called until after the instruction finishes.  If multiple
   timers fire during the same instruction, the order in which the
   callback functions will be called is undefined.

   If you create a timer using this function (either repeating or
   non-repeating), you must call tilem_z80_remove_timer() when the
   timer is no longer needed.
*/
int tilem_z80_add_timer(TilemCalc* calc, dword count, dword period,
			int rt, TilemZ80TimerFunc func, void* data);

/* Change settings for an existing timer.  Arguments are the same as
   above.  If count = 0, the timer is disabled. */
void tilem_z80_set_timer(TilemCalc* calc, int id, dword count,
			 dword period, int rt);

/* Change period for an existing timer without affecting the current
   interval. */
void tilem_z80_set_timer_period(TilemCalc* calc, int id, dword period);

/* Delete a timer. */
void tilem_z80_remove_timer(TilemCalc* calc, int id);

/* Check whether a timer is currently running. */
int tilem_z80_timer_running(TilemCalc* calc, int id)
	TILEM_ATTR_PURE;

/* Get the number of clock ticks from now until the next time the
   given timer fires.  (This may be negative, if the timer has already
   fired during this instruction.)  NOTE: If the timer is disabled,
   the return value is undefined. */
int tilem_z80_get_timer_clocks(TilemCalc* calc, int id)
	TILEM_ATTR_PURE;

/* Get the number of microseconds from now until the next time the
   given timer fires. */
int tilem_z80_get_timer_microseconds(TilemCalc* calc, int id)
	TILEM_ATTR_PURE;

/* Add a breakpoint.  The breakpoint will be triggered if the address,
   ANDed with the given mask, falls between the given start and end
   inclusive.  If a callback function is specified it acts as an
   additional filter, to determine whether the simulation should be
   halted. */
int tilem_z80_add_breakpoint(TilemCalc* calc, int type,
			     dword start, dword end, dword mask,
			     TilemZ80BreakpointFunc func, void* data);

/* Remove the given breakpoint. */
void tilem_z80_remove_breakpoint(TilemCalc* calc, int id);

/* Enable the given breakpoint. */
void tilem_z80_enable_breakpoint(TilemCalc* calc, int id);

/* Disable the given breakpoint. */
void tilem_z80_disable_breakpoint(TilemCalc* calc, int id);

/* Check whether the given breakpoint is currently enabled. */
int tilem_z80_breakpoint_enabled(TilemCalc* calc, int id);

/* Get the type of the given breakpoint. */
int tilem_z80_get_breakpoint_type(TilemCalc* calc, int id);

/* Get the start address of the given breakpoint. */
dword tilem_z80_get_breakpoint_address_start(TilemCalc* calc, int id);

/* Get the start address of the given breakpoint. */
dword tilem_z80_get_breakpoint_address_end(TilemCalc* calc, int id);

/* Get the start address of the given breakpoint. */
dword tilem_z80_get_breakpoint_address_mask(TilemCalc* calc, int id);

/* Get the callback/filter function associated to the given breakpoint. */
TilemZ80BreakpointFunc tilem_z80_get_breakpoint_callback(TilemCalc* calc,
							 int id);

/* Get the data associated to the given breakpoint. */
void* tilem_z80_get_breakpoint_data(TilemCalc* calc, int id);

/* Set the type of the given breakpoint. */
void tilem_z80_set_breakpoint_type(TilemCalc* calc, int id, int type);

/* Set the start address of the given breakpoint. */
void tilem_z80_set_breakpoint_address_start(TilemCalc* calc, int id,
					    dword start);

/* Set the start address of the given breakpoint. */
void tilem_z80_set_breakpoint_address_end(TilemCalc* calc, int id, dword end);

/* Set the start address of the given breakpoint. */
void tilem_z80_set_breakpoint_address_mask(TilemCalc* calc, int id, dword mask);

/* Set the callback/filter function associated to the given breakpoint. */
void tilem_z80_set_breakpoint_callback(TilemCalc* calc, int id,
				       TilemZ80BreakpointFunc func);

/* Set the data associated to the given breakpoint. */
void tilem_z80_set_breakpoint_data(TilemCalc* calc, int id, void* data);


/* Run the simulated CPU for the given number of clock
   ticks/microseconds, or until a breakpoint is hit or
   tilem_z80_stop() is called. */
dword tilem_z80_run(TilemCalc* calc, int clocks, int* remaining);
dword tilem_z80_run_time(TilemCalc* calc, int microseconds, int* remaining);


/* LCD driver */

/* Emulation flags */
enum {
	TILEM_LCD_REQUIRE_DELAY = 1,	  /* Emulate required delay
					     between commands */
	TILEM_LCD_REQUIRE_LONG_DELAY = 2  /* Require extra-long delay */
};

typedef struct _TilemLCD {
	/* Common settings */
	byte active;		/* LCD driver active */
	byte contrast;		/* Contrast value (0-63) */
	int rowstride;		/* Number of bytes per row */
	unsigned int emuflags;

	/* T6A43 internal driver */
	word addr;		/* Memory address */

	/* T6A04 external driver */
	byte mode;		/* I/O mode (0 = 6bit, 1 = 8bit) */
	byte inc;		/* Increment mode (4-7) */
	byte nextbyte;		/* Output register */
	int x, y;		/* Current position */
	int rowshift;		/* Starting row for display */
	byte busy;
} TilemLCD;

/* Reset LCD driver */
void tilem_lcd_reset(TilemCalc* calc);

/* Get LCD driver status (port 10 input) */
byte tilem_lcd_t6a04_status(TilemCalc* calc);

/* Send command to LCD driver (port 10 output) */
void tilem_lcd_t6a04_control(TilemCalc* calc, byte val);

/* Read data from LCD driver (port 11 input) */
byte tilem_lcd_t6a04_read(TilemCalc* calc);

/* Write data to LCD driver (port 11 output) */
void tilem_lcd_t6a04_write(TilemCalc* calc, byte val);

/* Get screen image (T6A04 style) */
void tilem_lcd_t6a04_get_data(TilemCalc* calc, byte* data);

/* Get screen image (T6A43 style) */
void tilem_lcd_t6a43_get_data(TilemCalc* calc, byte* data);

/* Callback for TILEM_TIMER_LCD_DELAY */
void tilem_lcd_delay_timer(TilemCalc* calc, void* data);



/* DBUS link port driver */

/* Link port / assist mode flags */
enum {
	TILEM_LINK_MODE_ASSIST        = 1,  /* Enable link assist */
	TILEM_LINK_MODE_NO_TIMEOUT    = 2,  /* Assist doesn't time out (xp) */
	TILEM_LINK_MODE_INT_ON_ACTIVE = 4,  /* Interrupt on state change */
	TILEM_LINK_MODE_INT_ON_READ   = 8,  /* Interrupt on asst. read */
	TILEM_LINK_MODE_INT_ON_IDLE   = 16, /* Interrupt when asst. idle */
	TILEM_LINK_MODE_INT_ON_ERROR  = 32  /* Interrupt on asst. error */
};

/* Link port state flags */
enum {
	TILEM_LINK_ASSIST_READ_BYTE   = 1, /* Assisted read finished */
	TILEM_LINK_ASSIST_READ_BUSY   = 2, /* Assisted read in progress */
	TILEM_LINK_ASSIST_READ_ERROR  = 4, /* Assisted read failed */
	TILEM_LINK_ASSIST_WRITE_BUSY  = 8, /* Assisted write in progress */
	TILEM_LINK_ASSIST_WRITE_ERROR = 16 /* Assisted write failed */
};

/* Link emulation mode */
enum {
	TILEM_LINK_EMULATOR_NONE = 0,  /* Link port disconnected */
	TILEM_LINK_EMULATOR_BLACK = 1, /* Connected to virtual BlackLink
					  (exit emulation on state change) */
	TILEM_LINK_EMULATOR_GRAY = 2   /* Connected to virtual GrayLink
					  (auto send/receive bytes) */
};

typedef struct _TilemLinkport {
	byte lines, extlines;	/* Link line state for TI/PC
				   0 = both lines high
				   1 = red wire low
				   2 = white wire low
				   3 = both wires low */

	unsigned int mode;	/* Mode flags */

	/* Internal link assist */
	unsigned int assistflags; /* Assist state */
	byte assistin;		  /* Input buffer (recv from PC) */
	byte assistinbits;	  /* Input bit count */
	byte assistout;		  /* Output buffer (send to PC) */
	byte assistoutbits;	  /* Output bit count */
	byte assistlastbyte;	  /* Last byte received */

	/* External link emulator */
	byte linkemu;
	byte graylinkin;	/* Input buffer (recv from TI) */
	byte graylinkinbits;	/* Input bit count */
	byte graylinkout;	/* Output buffer (send to TI) */
	byte graylinkoutbits;	/* Output bit count */
} TilemLinkport;

/* Reset link port */
void tilem_linkport_reset(TilemCalc* calc);

/* Read link port lines */
byte tilem_linkport_get_lines(TilemCalc* calc);

/* Set link port lines */
void tilem_linkport_set_lines(TilemCalc* calc, byte lines);

/* Read from, and clear, link assist input buffer */
byte tilem_linkport_read_byte(TilemCalc* calc);

/* Write to link assist output buffer */
void tilem_linkport_write_byte(TilemCalc* calc, byte data);

/* Get assist state */
unsigned int tilem_linkport_get_assist_flags(TilemCalc* calc);

/* Set link port mode */
void tilem_linkport_set_mode(TilemCalc* calc, unsigned int mode);

/* Set line states for virtual BlackLink */
void tilem_linkport_blacklink_set_lines(TilemCalc* calc, byte lines);

/* Get line states from virtual BlackLink */
byte tilem_linkport_blacklink_get_lines(TilemCalc* calc);

/* Reset GrayLink */
void tilem_linkport_graylink_reset(TilemCalc* calc);

/* Check if GrayLink is ready to send data */
int tilem_linkport_graylink_ready(TilemCalc* calc);

/* Send a byte via virtual GrayLink */
int tilem_linkport_graylink_send_byte(TilemCalc* calc, byte value);

/* Get byte received by virtual GrayLink (-1 = none available) */
int tilem_linkport_graylink_get_byte(TilemCalc* calc);

/* Callback for TILEM_TIMER_LINK_ASSIST */
void tilem_linkport_assist_timer(TilemCalc* calc, void* data);


/* Keypad */

typedef struct _TilemKeypad {
	byte group;
	byte onkeydown;
	byte onkeyint;
	byte keysdown[8];
} TilemKeypad;

/* Reset keypad */
void tilem_keypad_reset(TilemCalc* calc);

/* Set current group (port 1 output) */
void tilem_keypad_set_group(TilemCalc* calc, byte group);

/* Read keys from current group (port 1 input) */
byte tilem_keypad_read_keys(TilemCalc* calc);

/* Press a key */
void tilem_keypad_press_key(TilemCalc* calc, int scancode);

/* Release a key */
void tilem_keypad_release_key(TilemCalc* calc, int scancode);


/* Flash */

/* Emulation flags */
enum {
	TILEM_FLASH_REQUIRE_DELAY = 1 /* Require delay after
					 program/erase */
};

typedef struct _TilemFlashSector {
	dword start;
	dword size;
	byte protectgroup;
} TilemFlashSector;

typedef struct _TilemFlash {
	byte unlock;
	byte state;
	unsigned int emuflags;
	byte busy;
	dword progaddr;
	byte progbyte;
	byte toggles;
	byte overridegroup;
} TilemFlash;

/* Reset Flash */
void tilem_flash_reset(TilemCalc* calc);

/* Read a byte from the Flash chip */
byte tilem_flash_read_byte(TilemCalc* calc, dword pa);

/* Erase a Flash sector */
void tilem_flash_erase_address(TilemCalc* calc, dword pa);

/* Write a byte to the Flash chip */
void tilem_flash_write_byte(TilemCalc* calc, dword pa, byte v);

/* Callback for TILEM_TIMER_FLASH_DELAY */
void tilem_flash_delay_timer(TilemCalc* calc, void* data);


/* MD5 assist */

enum {
	TILEM_MD5_REG_A = 0,	/* initial 'a' value */
	TILEM_MD5_REG_B = 1,	/* 'b' value */
	TILEM_MD5_REG_C = 2,	/* 'c' value */
	TILEM_MD5_REG_D = 3,	/* 'd' value */
	TILEM_MD5_REG_X = 4,	/* 'X' (or 'T') value */
	TILEM_MD5_REG_T = 5	/* 'T' (or 'X') value */
};

enum {
	TILEM_MD5_FUNC_FF = 0,
	TILEM_MD5_FUNC_GG = 1,
	TILEM_MD5_FUNC_HH = 2,
	TILEM_MD5_FUNC_II = 3
};

typedef struct _TilemMD5Assist {
	dword regs[6];
	byte shift;
	byte mode;
} TilemMD5Assist;

/* Reset MD5 assist */
void tilem_md5_assist_reset(TilemCalc* calc);

/* Get output value */
dword tilem_md5_assist_get_value(TilemCalc* calc);


/* Programmable timers */

#define TILEM_MAX_USER_TIMERS 3

enum {
	TILEM_USER_TIMER_LOOP        = 1,   /* loop when counter
					       reaches 0 */
	TILEM_USER_TIMER_INTERRUPT   = 2,   /* generate interrupt when
					       finished */
	TILEM_USER_TIMER_OVERFLOW    = 4,   /* timer has expired at
					       least twice since last
					       mode setting */
	TILEM_USER_TIMER_FINISHED    = 256, /* timer has expired at
					       least once since last
					       mode setting (port 4
					       status bit) */
	TILEM_USER_TIMER_NO_HALT_INT = 512  /* suppress interrupt if
					       CPU is halted */
};

typedef struct _TilemUserTimer {
	byte frequency;
	byte loopvalue;
	unsigned int status;
} TilemUserTimer;

/* Reset timers */
void tilem_user_timers_reset(TilemCalc* calc);

/* Set frequency control register */
void tilem_user_timer_set_frequency(TilemCalc* calc, int n, byte value);

/* Set status flags */
void tilem_user_timer_set_mode(TilemCalc* calc, int n, byte mode);

/* Start timer */
void tilem_user_timer_start(TilemCalc* calc, int n, byte value);

/* Get timer value */
byte tilem_user_timer_get_value(TilemCalc* calc, int n);

/* Callback function */
void tilem_user_timer_expired(TilemCalc* calc, void* data);


/* Calculators */

/* Model IDs */
enum {
	TILEM_CALC_TI73 = '7',	       /* TI-73 / TI-73 Explorer */
	TILEM_CALC_TI76 = 'f',	       /* TI-76.fr */
	TILEM_CALC_TI81 = '1',	       /* TI-81 */
	TILEM_CALC_TI82 = '2',	       /* TI-82 */
	TILEM_CALC_TI83 = '3',	       /* TI-83 / TI-82 STATS [.fr] */
	TILEM_CALC_TI83P = 'p',	       /* TI-83 Plus */
	TILEM_CALC_TI83P_SE = 's',     /* TI-83 Plus Silver Edition */
	TILEM_CALC_TI84P = '4',	       /* TI-84 Plus */
	TILEM_CALC_TI84P_SE = 'z',     /* TI-84 Plus Silver Edition */
	TILEM_CALC_TI84P_NSPIRE = 'n', /* TI-Nspire 84 Plus emulator */
	TILEM_CALC_TI85 = '5',	       /* TI-85 */
	TILEM_CALC_TI86 = '6'	       /* TI-86 */
};

/* Calculator flags */
enum {
	TILEM_CALC_HAS_LINK        = 1,  /* Has link port */
	TILEM_CALC_HAS_LINK_ASSIST = 2,  /* Has hardware link assist */
	TILEM_CALC_HAS_USB         = 4,  /* Has USB controller */
	TILEM_CALC_HAS_FLASH       = 8,  /* Has (writable) Flash */
	TILEM_CALC_HAS_T6A04       = 16, /* Has separate LCD driver */
	TILEM_CALC_HAS_MD5_ASSIST  = 32  /* Has hardware MD5 assist */
};

/* Calculator hardware description */
struct _TilemHardware {
	char model_id;	      /* Single character identifying model */
	const char* name;     /* Short name (e.g. ti83p) */
	const char* desc;     /* Full name (e.g. TI-83 Plus) */

	unsigned int flags;

	int lcdwidth, lcdheight; /* Size of LCD */
	dword romsize, ramsize;	 /* Size of ROM and RAM */
	dword lcdmemsize;	 /* Size of external LCD memory */
	byte rampagemask;	 /* Bit mask used for RAM page */

	int nflashsectors;
	const TilemFlashSector* flashsectors;

	int nusertimers;

	int nhwregs;		 /* Number of hardware registers */
	const char** hwregnames; /* Harware register names */

	int nhwtimers;		 /* Number of hardware timers */
	const char** hwtimernames; /* Hardware timer names*/

	const char** keynames;

	/* Reset calculator */
	void	(*reset)	(TilemCalc*);

	/* Reinitialize after loading state */
	void    (*stateloaded)  (TilemCalc*, int);

	/* Z80 ports and memory */
	byte	(*z80_in)	(TilemCalc*, dword);
	void	(*z80_out) 	(TilemCalc*, dword, byte);
	void	(*z80_wrmem)	(TilemCalc*, dword, byte);
	byte	(*z80_rdmem)	(TilemCalc*, dword);
	byte	(*z80_rdmem_m1)	(TilemCalc*, dword);

	/* Evaluate a non-standard instruction */
	void    (*z80_instr)    (TilemCalc*, dword);

	/* Persistent timer callback */
	void    (*z80_ptimer)   (TilemCalc*, int);

	/* Retrieve LCD contents */
	void    (*get_lcd)      (TilemCalc*, byte*);

	/* Convert physical <-> logical addresses */
	dword	(*mem_ltop)	(TilemCalc*, dword);
	dword	(*mem_ptol)	(TilemCalc*, dword);
};

/* Current state of the calculator */
struct _TilemCalc {
	TilemHardware hw;

	TilemZ80 z80;
	byte* mem;
	byte* ram;
	byte* lcdmem;
	byte mempagemap[4];

	TilemLCD lcd;
	TilemLinkport linkport;
	TilemKeypad keypad;
	TilemFlash flash;
	TilemMD5Assist md5assist;
	TilemUserTimer usertimers[TILEM_MAX_USER_TIMERS];

	byte poweronhalt;	/* System power control.  If this is
				   zero, turn off LCD, timers,
				   etc. when CPU halts */

	byte battery;		/* Battery level (units of 0.1 V) */

	dword* hwregs;
};

/* Get a list of supported hardware models */
void tilem_get_supported_hardware(const TilemHardware*** models,
				  int* nmodels);

/* Create a new calculator.  This function returns NULL if
   insufficient memory is available. */
TilemCalc* tilem_calc_new(char id);

/* Make an exact copy of an existing calculator (including both
   internal and external state.)  Be careful when using this in
   conjunction with custom timer/breakpoint callback functions.  This
   function returns NULL if insufficient memory is available. */
TilemCalc* tilem_calc_copy(TilemCalc* calc);

/* Free a calculator that was previously created by tilem_calc_new()
   or tilem_calc_copy(). */
void tilem_calc_free(TilemCalc* calc);

/* Reset calculator (essentially, remove and replace batteries.) */
void tilem_calc_reset(TilemCalc* calc);

/* Load calculator state from ROM and/or save files. */
int tilem_calc_load_state(TilemCalc* calc, FILE* romfile, FILE* savfile);

/* Save calculator state to ROM and/or save files. */
int tilem_calc_save_state(TilemCalc* calc, FILE* romfile, FILE* savfile);


/* LCD image conversion/scaling */

/* Scaling algorithms */
enum {
	TILEM_SCALE_FAST = 0,	/* Fast scaling (nearest neighbor) -
				   looks lousy unless the scaling
				   factor is fairly large */
	TILEM_SCALE_SMOOTH	/* Smooth scaling - slower and looks
				   better at small sizes; note that
				   this falls back to using the "fast"
				   algorithm if we are scaling up by
				   an integer factor */
};

/* Buffer representing a snapshot of the LCD state */
typedef struct _TilemLCDBuffer {
	byte width;             /* Width of LCD */
	byte height;            /* Height of LCD */
	byte rowstride;         /* Offset between rows in buffer */
	byte contrast;          /* Contrast value (0-63) */
	dword stamp;            /* Timestamp */
	dword tmpbufsize;       /* Size of temporary buffer */
	byte *data;             /* Image data (rowstride*height bytes) */
	void *tmpbuf;           /* Temporary buffer used for scaling */
} TilemLCDBuffer;

/* Create new TilemLCDBuffer. */
TilemLCDBuffer* tilem_lcd_buffer_new(void)
	TILEM_ATTR_MALLOC;

/* Free  a TilemLCDBuffer. */
void tilem_lcd_buffer_free(TilemLCDBuffer *buf);

/* Convert current LCD memory contents to a TilemLCDBuffer (i.e., a
   monochrome snapshot.) */
void tilem_lcd_get_frame(TilemCalc * restrict calc,
                         TilemLCDBuffer * restrict buf);

/* Convert current LCD memory contents to a TilemLCDBuffer (i.e., a
   monochrome snapshot.) 
   Output is only 0 and 1 */
void tilem_lcd_get_frame1(TilemCalc * restrict calc,
                         TilemLCDBuffer * restrict buf);

/* Convert and scale image to an 8-bit indexed image buffer.  IMGWIDTH
   and IMGHEIGHT are the width and height of the output image,
   ROWSTRIDE the number of bytes from the start of one row to the next
   (often equal to IMGWIDTH), and SCALETYPE the scaling algorithm to
   use. */
void tilem_draw_lcd_image_indexed(TilemLCDBuffer * restrict frm,
                                  byte * restrict buffer,
                                  int imgwidth, int imgheight,
                                  int rowstride, int scaletype);

/* Convert and scale image to a 24-bit RGB or 32-bit RGBA image
   buffer.  IMGWIDTH and IMGHEIGHT are the width and height of the
   output image, ROWSTRIDE the number of bytes from the start of one
   row to the next (often equal to 3 * IMGWIDTH), PIXBYTES the number
   of bytes per pixel (3 or 4), and SCALETYPE the scaling algorithm to
   use.  PALETTE is an array of 256 color values. */
void tilem_draw_lcd_image_rgb(TilemLCDBuffer * restrict frm,
                              byte * restrict buffer,
                              int imgwidth, int imgheight, int rowstride,
                              int pixbytes, const dword * restrict palette,
                              int scaletype);

/* Calculate a color palette for use with the above functions.
   RLIGHT, GLIGHT, BLIGHT are the RGB components (0 to 255) of the
   lightest possible color; RDARK, GDARK, BDARK are the RGB components
   of the darkest possible color.  GAMMA is the gamma value for the
   output device (2.2 for most current computer displays and image
   file formats.) */
dword* tilem_color_palette_new(int rlight, int glight, int blight,
                               int rdark, int gdark, int bdark,
                               double gamma);

/* Calculate a color palette, as above, and convert it to a packed
   array of bytes (R, G, B) */
byte* tilem_color_palette_new_packed(int rlight, int glight, int blight,
                                     int rdark, int gdark, int bdark,
                                     double gamma);


/* Grayscale LCD simulation */

typedef struct _TilemGrayLCD TilemGrayLCD;

/* Create a new LCD and attach to a calculator.  Sampling for
   grayscale is done across WINDOWSIZE frames, with samples taken
   every SAMPLEINT microseconds. */
TilemGrayLCD* tilem_gray_lcd_new(TilemCalc *calc, int windowsize,
                                 int sampleint);

/* Detach and free an LCD. */
void tilem_gray_lcd_free(TilemGrayLCD *glcd);

/* Generate a grayscale image for the next frame, based on current
   calculator state.  This function also updates the frame counter and
   internal state; for proper grayscale behavior, this function needs
   to be called at regular intervals. */
void tilem_gray_lcd_get_frame(TilemGrayLCD * restrict glcd,
                              TilemLCDBuffer * restrict frm);


/* Miscellaneous functions */

/* Guess calculator type for a ROM file */
char tilem_guess_rom_type(FILE* romfile);

/* Get calculator type for a SAV file */
char tilem_get_sav_type(FILE* savfile);

/* Check validity of calculator certificate; repair if necessary */
void tilem_calc_fix_certificate(TilemCalc* calc, byte* cert,
                                int app_start, int app_end,
                                unsigned exptab_offset);

#ifdef __cplusplus
}
#endif

#endif
