/* radare - LGPL - Copyright 2008-2020 pancake<nopcode.org> */

#include <r_util.h>

// TODO: use r_list instead of list.h
// TODO: redesign this api.. why? :)
// TODO: add tags to ranges

//void (*ranges_new_callback)(struct range_t *r) = NULL;

R_API RRange *r_range_new(void) {
	RRange *r = R_NEW0 (RRange);
	if (r) {
		r->count = r->changed = 0;
		r->ranges = r_list_new ();
		if (!r->ranges) {
			r_range_free (r);
			return NULL;
		}
		r->ranges->free = free;
	}
	return r;
}

R_API RRange *r_range_free(RRange *r) {
	r_list_purge (r->ranges);
	free (r);
	return NULL;
}

// TODO: optimize by just returning the pointers to the internal foo?
R_API int r_range_get_data(RRange *rgs, ut64 addr, ut8 *buf, int len) {
	RRangeItem *r = r_range_item_get (rgs, addr);
	if (!r) {
		return 0;
	}
	if (r->datalen < len) {
		len = r->datalen;
	}
	memcpy (buf, r->data, len);
	return len;
}

R_API int r_range_set_data(RRange *rgs, ut64 addr, const ut8 *buf, int len) {
	RRangeItem *r = r_range_item_get (rgs, addr);
	if (!r) {
		return 0;
	}
	r->data = (ut8*)malloc (len);
	if (!r->data) {
		return 0;
	}
	r->datalen = len;
	memcpy (r->data, buf, len);
	return 1;
}

RRangeItem *r_range_item_get(RRange *rgs, ut64 addr) {
	RRangeItem *r;
	RListIter *iter;
	r_list_foreach (rgs->ranges, iter, r) {
		if (addr >= r->fr && addr < r->to) {
			return r;
		}
	}
	return NULL;
}

/* returns the sum of all the ranges contained */
// XXX: can be caught while adding/removing elements
R_API ut64 r_range_size(RRange *rgs) {
	ut64 sum = 0;
	RListIter *iter;
	RRangeItem *r;
	r_list_foreach (rgs->ranges, iter, r) {
		sum += r->to - r->fr;
	}
	return sum;
}

R_API RRange *r_range_new_from_string(const char *string) {
	RRange *rgs = r_range_new ();
	r_range_add_from_string (rgs, string);
	return rgs;
}

R_API char *r_range_tostring(RRange *rgs) {
	RStrBuf *sb = r_strbuf_new ("");
	// squash and then walk the thingk
	RListIter *iter;
	RRangeItem *r;
	bool second = false;
	r_list_foreach_prev (rgs->ranges, iter, r) {
		if (second) {
			r_strbuf_append (sb, ",");
		}
		r_strbuf_appendf (sb, "0x%08"PFMT64x"-0x%08"PFMT64x, r->fr, r->to);
		second = true;
	}
	return r_strbuf_drain (sb);
}

R_API int r_range_add_from_string(RRange *rgs, const char *string) {
	ut64 addr, addr2;
	int i, len = strlen (string) + 1;
	char *str, *ostr = malloc (len);
	if (!ostr) {
		return 0;
	}
	char *p = str = ostr;
	char *p2 = NULL;

	memcpy (str, string, len);
	for (i = 0; i < len; i++) {
		switch (str[i]) {
		case '-':
			str[i] = '\0';
			p2 = p;
			p = str + i + 1;
			break;
		case ',':
			str[i] = '\0';
			if (p2) {
				addr = r_num_get (NULL, p);
				addr2 = r_num_get (NULL, p2);
				r_range_add (rgs, addr, addr2, 1);
				p2 = NULL;
			} else {
				addr = r_num_get (NULL, p);
				r_range_add (rgs, addr, addr + 1, 1);
			}
			p = str + i + 1;
			str[i] = ',';
			break;
		}
	}
	if (p2) {
		addr = r_num_get (NULL, p);
		addr2 = r_num_get (NULL, p2);
		r_range_add (rgs, addr, addr2, 1);
	} else if (p) {
		addr = r_num_get (NULL, p);
		r_range_add (rgs, addr, addr + 1, 1);
	}
	free (ostr);
	return rgs? rgs->changed: 0;
}

#if 0
	    update to      new one     update fr   update fr/to  ignore

	   |______|        |___|           |_____|      |____|      |_______|  range_t
	+     |______|   +      |__|   + |___|      + |_________|  +  |__|     fr/to
	  ------------   -----------   -----------  -------------  -----------
	=  |_________|   = |___||__|   = |_______|  = |_________|   |_______|  result
#endif

RRangeItem *r_range_add(RRange *rgs, ut64 fr, ut64 to, int rw) {
	RListIter *iter;
	RRangeItem *r, *ret = NULL;
	int add = 1;

	r_num_minmax_swap (&fr, &to);
	r_list_foreach (rgs->ranges, iter, r) {
		if (r->fr == fr && r->to == to) {
			add = 0;
		} else if (r->fr <= fr && r->fr <= to && r->to >= fr && r->to <= to) {
			r->to = to;
			ret = r;
			add = 0;
		} else if (r->fr >= fr && r->fr <= to && r->to >= fr && r->to >= to) {
			r->fr = fr;
			ret = r;
			add = 0;
		} else if (r->fr <= fr && r->fr <= to && r->to >= fr && r->to >= to) {
			/* ignore */
			add = 0;
		} else if (r->fr >= fr && r->fr <= to && r->to >= fr && r->to <= to) {
			r->fr = fr;
			r->to = to;
			ret = r;
			add = 0;
		}
	}

	if (rw && add) {
		ret = R_NEW (RRangeItem);
		ret->fr = fr;
		ret->to = to;
		ret->datalen = 0;
		ret->data = NULL;
		r_list_append (rgs->ranges, ret);
		rgs->changed = 1;
	}

	return ret;
}

#if 0
	    update to      ignore      update fr      delete        split

	   |______|        |___|           |_____|      |____|       |________|  range_t
	-     |______|   -      |__|   - |___|      - |_________|  -    |__|     fr/to
	  ------------   -----------   -----------  -------------  ------------
	=  |__|          =             =     |___|  =                |__|  |__|   result
#endif

R_API int r_range_sub(RRange *rgs, ut64 fr, ut64 to) {
	RRangeItem *r;
	RListIter *iter;

	r_num_minmax_swap (&fr, &to);

	__reloop:
	r_list_foreach (rgs->ranges, iter, r) {
		/* update to */
		if (r->fr < fr && r->fr < to && r->to > fr && r->to < to) {
			r->to = fr;
		} else if (r->fr > fr && r->fr < to && r->to > fr && r->to > to) {
			/* update fr */
			r->fr = to;
		}
		/* delete */
		if (r->fr > fr && r->fr < to && r->to > fr && r->to < to) {
			/* delete */
			r_list_delete (rgs->ranges, iter);
			rgs->changed = 1;
			goto __reloop;
		}
		/* split */
		if (r->fr < fr && r->fr < to && r->to > fr && r->to > to) {
			r->to = fr;
			r_range_add (rgs, to, r->to, 1);
			//ranges_add(rang, to, r->to, 1);
			goto __reloop;
		}
	}
	return 0;
}

#if 0
/* TODO: should remove some of them right? */
R_API void r_range_merge(RRange *rgs, RRange *r) {
	RListIter *iter;
	RRangeItem *r;
	r_list_foreach (rgs->ranges, iter, r)
		r_range_add (rgs, r->fr, r->to, 0);
}
#endif

//int ranges_is_used(ut64 addr)
R_API int r_range_contains(RRange *rgs, ut64 addr) {
	RRangeItem *r;
	RListIter *iter;
	r_list_foreach (rgs->ranges, iter, r) {
		if (addr >= r->fr && addr <= r->to) {
			return true;
		}
	}
	return false;
}

static int cmp_ranges(void *a, void *b) {
	RRangeItem *first = (RRangeItem *)a;
	RRangeItem *second = (RRangeItem *)b;
	return (first->fr > second->fr) - (first->fr < second->fr);
}

R_API int r_range_sort(RRange *rgs) {
	bool ch = rgs->ranges->sorted;
	if (!rgs->changed) {
		return false;
	}
	rgs->changed = false;
	r_list_sort (rgs->ranges, (RListComparator)cmp_ranges);
	if (ch != rgs->ranges->sorted) {
		rgs->changed = true;
	}
	return rgs->changed;
}

R_API void r_range_percent(RRange *rgs) {
	RListIter *iter;
	RRangeItem *r;
	int w, i;
	ut64 seek, step;
	ut64 dif, fr = -1, to = -1;

	r_list_foreach (rgs->ranges, iter, r) {
		if (fr == -1) {
			/* init */
			fr = r->fr;
			to = r->to;
		} else {
			if (fr > r->fr) {
				fr = r->fr;
			}
			if (to < r->to) {
				to = r->to;
			}
		}
	}
	w = 65 ; // columns
	if (fr != -1) {
		dif = to - fr;
		if (dif < w) {
			step = 1; // XXX
		} else {
			step = dif / w;
		}
	} else {
		step = fr = to = 0;
	}
	seek = 0;
	// XXX do not use printf here!
	printf ("0x%08"PFMT64x" [", fr);
	for (i = 0; i < w; i++) {
		if (r_range_contains (rgs, seek)) {
			printf ("#");
		} else {
			printf (".");
		}
		seek += step;
	}
	printf ("] 0x%08"PFMT64x"\n", to);
}

// TODO: total can be cached in rgs!!
int r_range_list(RRange *rgs, int rad) {
	ut64 total = 0;
	RRangeItem *r;
	RListIter *iter;
	r_range_sort (rgs);
	r_list_foreach (rgs->ranges, iter, r) {
		if (rad) {
			printf ("ar+ 0x%08"PFMT64x" 0x%08"PFMT64x"\n", r->fr, r->to);
		} else {
			printf ("0x%08"PFMT64x" 0x%08"PFMT64x" ; %"PFMT64d"\n", r->fr, r->to, r->to - r->fr);
		}
		total += (r->to - r->fr);
	}
	eprintf ("Total bytes: %"PFMT64d"\n", total);
	return 0;
}

int r_range_get_n(RRange *rgs, int n, ut64 *fr, ut64 *to) {
	int count = 0;
	RRangeItem *r;
	RListIter *iter;
	r_range_sort (rgs);
	r_list_foreach (rgs->ranges, iter, r) {
		if  (count == n) {
			*fr = r->fr;
			*to = r->to;
			return 1;
		}
		count++;
	}
	return 0;
}

#if 0
	 .....|______________________|...
	  |_____|  |____|  |_______|
	---------------------------------
		|__|    |__|       |_|
#endif
RRange *r_range_inverse(RRange *rgs, ut64 fr, ut64 to, int flags) {
	// ut64 total = 0;
	RListIter *iter;
	RRangeItem *r = NULL;
	RRange *newrgs = r_range_new();

	r_range_sort(rgs);

	r_list_foreach (rgs->ranges, iter, r) {
		if (r->fr > fr && r->fr < to) {
			r_range_add (newrgs, fr, r->fr, 1);
			//eprintf("0x%08"PFMT64x" .. 0x%08"PFMT64x"\n", fr, r->fr);
	//		total += (r->fr - fr);
			fr = r->to;
		}
	}
	if (fr < to) {
		//eprintf("0x%08"PFMT64x" .. 0x%08"PFMT64x"\n", fr, to);
		r_range_add (newrgs, fr, to, 1);
//		total += (to - fr);
	}
	// eprintf("Total bytes: %"PFMT64d"\n", total);
	return newrgs;
}

/*
	return true if overlap
	in *d
*/
// TODO: make it a macro
// TODO: move to num.c ?
R_API int r_range_overlap(ut64 a0, ut64 a1, ut64 b0, ut64 b1, int *d) {
	// TODO: ensure ranges minmax .. innecesary at runtime?
	//r_num_minmax_swap (&a0, &a1);
	//r_num_minmax_swap (&b0, &b1);
	return *d = (b0 - a0), !(a1 < b0 || a0 > b1);
#if 0
	// does not overlap
	// a  |__|           |__|
	// b      |__|   |__|
	if (a1<b0 || a0>b1)
		return 0;

	// a     |____|   |_____|  |____|     |_____|
	// b  |____|        |_|       |____| |_______|
	//      b needs    a needs   a needs   b needs
	// delta required
	return (b0-a0);
#endif
}
