/*Debugger process control module */

#include <sys/types.h>

/* Reasons for child process to stop */
enum stop_reason {
    BREAKPOINT = 0, /* Child process hit a breakpoint */
    SIGNAL = 1,     /* Child process received a signal */
    EXITED = 2,     /* Child process exited */
    KILLED = 3      /* Child process was killed by a signal (i.e. from a kill -9) */
};

/* Struct to hold information about the current state of the program.
 * More info may be added here as needed.
*/
typedef struct {
    long eip; /* Instruction pointer */
    int signal; /*Signal that caused the program to stop */
    pthread_t thread; /*Currently running thread*/
    enum stop_reason reason; /*Reason the process stopped */
    
} program_info;

/* Represents a program stack. */
typedef struct {
    int dummy; /*Supress warnings for now*/
} stack;

/* Start a process under control of the debugger. argv must be an array of strings, with a NULL pointer
 * marking the last element in the array (as expected by execv).
 * Returns immediately after ptrace gains control
 * (on the return from the exec system call)
 * Returns the pid of the new process.
 * Either load_process or attach_process must be called before any other functions. Once the debugger
 * is attached to a process, neither load_process or attach_process should not be called again.
*/
pid_t load_process(char *command, char **argv);

/* Attach the debugger to a running process.
 * Returns as soon as ptrace gets control
*/
void attach_process(pid_t pid);

/* Detach from the current process. The process continues to run on its own.
 * After detaching, you can load or attach to a new process.
*/
void detach_process();

/* Set a breakpoint at the given address */
void implement_breakpoint(long addr);

/* Remove a breakpoint at the given address. Breakpoints are reference counted, so if a breakpoint is
 * set n times at the same address, it must be unimplemented n times before it is actually removed from
 * the code.
 * Should only be called if there is actually a breakpoint at addr.
*/   
void unimplement_breakpoint(long addr);

/* Return true if there is a breakpoint in place at addr */
int is_breakpoint(long addr);

/* Watch the specified memory. The entire page(s) will be set read-only */
void watch_memory(long addr, int len);

/* Stop watching the memory at addr. Watchpoints are reference counted like breakpoints.
 * The original permissions of a page are restored only when all watchpoints in the page
 * have their reference count reduced to 0.
*/
void unwatch_memory(long addr, int len);

/* Return true if addr is watched. Returns false if addr is in a page that is read-only, but
 * addr itself is not explicitly watched.
*/
int is_watched(long addr);

/* Resume execution of the process. Restarts all threads. Handles the details of temporarily clearing breakpoints.
 * Blocks until the process receives a
 * signal and stops.
 * Returns the state of the process when it stops.
*/
program_info run();

/* Single-step one thread of the process. Other threads remain stopped.
 * Handles the details of temporarily clearing breakpoints.
 * Blocks until the process stops, either because it has completed its single-step, or because it
 * was interrupted for some other reason. Returns the state of the program when it stops.
*/
program_info single_step(pthread_t tid);

/* Interrupt the process and return immediately. This call should be made asynchronously, while
 * a run or single_step call is in progress. The call to interrupt will return immediately, and will
 * cause the run or single_step call to return when the program stops.
 * If the program is already paused, this call does nothing.
*/
void interrupt();

/* Return the current stack */
stack get_stack();

/* Write the bytes in data to the given memory location */
void write_memory(int addr, int length, char *data);
/* Read the bytes at the given memory location into data */
void read_memory(int addr, int length, char *data);