#import <gdb.h>
#import "../breakpoint.h"
extern RegionManager *regionManager;
#import <string.h>
const static char breakpoint_insn[] = BREAKPOINT;

typedef struct _Safety {
    const char *name;
    CORE_ADDR address;
    char shadow[sizeof(breakpoint_insn)];
} Safety;

static Safety safeties[] = {
    {"accept"},
    {"adjtime"},
    {"bind"},
    {"connect"},
    {"fcntl"},
    {"fstat"},
    {"fstatfs"},
    {"setjmp"},
    {"getdents"},
    {"getdirentries"},
    {"getdomainname"},
    {"getfh"},
    {"getgroups"},
    {"gethostname"},
    {"getitimer"},
    {"getpeername"},
    {"getrlimit"},
    {"getrusage"},
    {"getsockname"},
    {"getsockopt"},
    {"ioctl"},
    {"lstat"},
    {"mmap"},
    {"pipe"},
    {"read"},
    {"readlink"},
    {"readv"},
    {"recv"},
    {"recvfrom"},
    {"recvmsg"},
    {"select"},
    {"setitimer"},
    {"setquota"},
    {"sigstack"},
    {"socketpair"},
    {"stat"},
    {"statfs"},
    {"syscall"},
    {"table"},
    {"ustat"},
    {"wait"},
    {"wait3"},
    {"wait4"},
    {"msg_receive"},
    {"msg_rpc"}
};

BOOL safetiesInserted = NO;
static BOOL safetiesInitialized = NO;

static const int numSafeties = sizeof(safeties) / sizeof(*safeties);

static void initializeSafeties()
{
    if (!safetiesInitialized) {
	int i;
	Safety *safety;
	
	for (i = 0, safety = safeties; i < numSafeties; i++, safety++) {
	    struct minimal_symbol *minSym
	                           = lookup_minimal_symbol(safety->name, NULL, NULL);
	    if (minSym) {
		safety->address = SYMBOL_VALUE_ADDRESS(minSym);
		read_memory(safety->address, safety->shadow,  
			    sizeof(safety->shadow));
	    }
	}
	safetiesInitialized = YES;
    }
}

BOOL isSafetyBreakpoint(CORE_ADDR address)
{
    if (safetiesInserted) {
	int i;
	Safety *safety;
    
	for (i = 0, safety = safeties; i < numSafeties; i++, safety++)
	    if (address == safety->address)
		return YES;
    }
    return NO;
}

void insertSafetyBreakpoints()
{
    if (!safetiesInserted) {
	int i;
	Safety *safety;
    
	for (i = 0, safety = safeties; i < numSafeties; i++, safety++) {
	    [regionManager putDataAt: (void *)safety->address
				 for: sizeof(breakpoint_insn)
				from: (void *)breakpoint_insn
		            markOnly: YES];
	}
	safetiesInserted = YES;
    }
}

void removeSafetyBreakpoints()
{
    if (safetiesInserted) {
	int i;
	Safety *safety;
    
	for (i = 0, safety = safeties; i < numSafeties; i++, safety++) {
	    [regionManager putDataAt: (void *)safety->address
				 for: sizeof(breakpoint_insn)
				from: (void *)safety->shadow
		            markOnly: YES];
	}
	safetiesInserted = NO;
    }
}

void insertDataBreakpoint(struct breakpoint *b)
{
    kern_return_t r;
    r = vm_protect(inferior_task, b->start_page, b->end_page - b->start_page, NO, VM_PROT_READ);
    if (r != KERN_SUCCESS) {
      mach_error("error inserting data breakpoint",r);
      }
    else {
      b->inserted = YES;
      }
}

void removeDataBreakpoint(struct breakpoint *b)
{
    vm_protect(inferior_task, b->start_page, b->end_page - b->start_page, NO,
               VM_PROT_READ | VM_PROT_WRITE);
    b->inserted = NO;
//    removeSafetyBreakpoints();
}

static void dbCommand(char *args, int from_tty)
{
    char *address, *bytes;
    void *newAddress;
    int newLength;
    value_ptr val;
    CORE_ADDR startPage;
    int sizePage;
    vm_address_t offset;
    vm_address_t s;
    
    initializeSafeties();
    if (address = args) {
	if (bytes = index(address, ' ')) {
	    *bytes = '\0';
	    bytes++;
	    newAddress = (void *)parse_and_eval_address(address);
	    newLength = value_as_long(parse_and_eval(bytes));
	} else {
	    val = parse_and_eval(address);
	    if (val->lval != lval_memory)
		error("Can't insert databreakpoint.  %s is %s.", address, val->lval == lval_register ? "in a register" : "not a memory location");
	    newAddress = (void *)(VALUE_ADDRESS(val) + VALUE_OFFSET(val));
	    if (newAddress)
		newLength = TYPE_LENGTH(VALUE_TYPE(val));
	    else {
		newAddress = (void *)value_as_long(val);
		newLength = 1;
	    }
	}
	offset = ((vm_address_t)newAddress % vm_page_size);
	s = newLength + offset;
	
	startPage = (vm_address_t)newAddress - offset;
	sizePage =  (s - (s % vm_page_size)) + vm_page_size;
	set_data_breakpoint(newAddress, newLength, startPage, sizePage);
    }
}

void _initialize_db()
{
    add_com("data-break", class_breakpoint, dbCommand,
            "break if data specified changes\n");
    add_com_alias("db", "data-break", class_breakpoint, 1);
}	
