#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "src/board.h"
#include "src/conf_core.h"
#include "src/layer.h"
#include "layout.h"
#include "config.h"

static inline layout_object_t *search_append(layout_search_t *s, void *obj)
{
	layout_object_t *o;
	if (s->used >= s->alloced) {
		s->alloced += 256;
		s->objects = realloc(s->objects, s->alloced * sizeof(layout_object_t));
	}
	o = s->objects + s->used;
	s->used++;
	o->type = s->searching;
	o->layer = s->layer;
	switch(s->searching) {
		case OM_LINE:     o->obj.l   = obj; break;
		case OM_TEXT:     o->obj.t   = obj; break;
		case OM_POLYGON:  o->obj.p   = obj; break;
		case OM_ARC:      o->obj.a   = obj; break;
		default:
			assert(!"Unimplemented object type");
	}
	return o;
}

static pcb_r_dir_t search_callback (const pcb_box_t * b, void *cl)
{
	search_append(cl, (void *)b);
  return PCB_R_DIR_FOUND_CONTINUE;
}

hash_t *layout_searches = NULL;
static layout_search_t *new_search(const char *search_ID)
{
	layout_search_t *s;
	layout_search_free(search_ID);

	if (layout_searches == NULL) {
		layout_searches = hash_create(64, gpmi_hash_ptr);
		assert(layout_searches != NULL);
	}

	s = calloc(sizeof(layout_search_t), 1);

	hash_store(layout_searches, search_ID, s);
	return s;
}

int layout_search_empty(const char *search_ID)
{
	layout_search_t *s = new_search(search_ID);
	if (s != NULL)
		return 0;
	return -1;
}


int layout_search_box(const char *search_ID, layout_object_mask_t obj_types, int x1, int y1, int x2, int y2)
{
  pcb_box_t spot;
	layout_search_t *s = new_search(search_ID);

	spot.X1 = x1;
	spot.Y1 = y1;
	spot.X2 = x2;
	spot.Y2 = y2;

	s->layer = -1;
	if (obj_types & OM_LINE) {
		s->searching = OM_LINE;
		pcb_r_search(CURRENT->line_tree, &spot, NULL, search_callback, s, NULL);
	}

	if (obj_types & OM_TEXT) {
		s->searching = OM_TEXT;
		pcb_r_search(CURRENT->text_tree, &spot, NULL, search_callback, s, NULL);
	}

	if (obj_types & OM_ARC) {
		s->searching = OM_ARC;
		pcb_r_search(CURRENT->arc_tree, &spot, NULL, search_callback, s, NULL);
	}

	if (obj_types & OM_POLYGON) {
		s->searching = OM_POLYGON;
		for (s->layer = 0; s->layer < PCB_MAX_LAYER; s->layer++)
			pcb_r_search(PCB->Data->Layer[s->layer].polygon_tree, &spot, NULL, search_callback, s, NULL);
		s->layer = -1;
	}

	return s->used;
}

/* PCB_FLAG_SELECTED */

typedef struct {
	int flag;
	layout_search_t *search;
} select_t;

static void select_cb(void *obj_, void *ud)
{
	select_t *ctx = ud;
	pcb_any_obj_t *obj = obj_;
	if (PCB_FLAG_TEST(ctx->flag, obj))
		search_append(ctx->search, obj);
}

#define select2(s, om, flag, lst) \
	do { \
		gdl_iterator_t it; \
		void *item; \
		select_t ctx; \
		ctx.flag = flag; \
		ctx.search = s; \
		s->searching = om; \
		linelist_foreach(lst, &it, item) select_cb(item, &ctx); \
		s->searching = 0; \
	} while(0)

static int layout_search_flag(const char *search_ID, multiple layout_object_mask_t obj_types, int flag)
{
	pcb_cardinal_t l, n;
	layout_search_t *s = new_search(search_ID);
	pcb_layer_t *layer = PCB->Data->Layer;

	for (l =0; l < PCB_MAX_LAYER; l++, layer++) {
		s->layer = l;
		select2(s, OM_ARC,     flag, &layer->Arc);
		select2(s, OM_LINE,    flag, &layer->Line);
		select2(s, OM_TEXT,    flag, &layer->Text);
		select2(s, OM_POLYGON, flag, &layer->Polygon);
	}

	return s->used;
}
#undef select

int layout_search_selected(const char *search_ID, multiple layout_object_mask_t obj_types)
{
	return layout_search_flag(search_ID, obj_types, PCB_FLAG_SELECTED);
}

int layout_search_found(const char *search_ID, multiple layout_object_mask_t obj_types)
{
	return layout_search_flag(search_ID, obj_types, PCB_FLAG_FOUND);
}

layout_object_t *layout_search_get(const char *search_ID, int n)
{
	const layout_search_t *s;

	s = hash_find(layout_searches, search_ID);

/*	printf("s=%p\n", (void *)s);*/
	if ((s == NULL) || (n < 0) || (n >= s->used))
		return NULL;
	return s->objects+n;
}

int layout_search_free(const char *search_ID)
{
	layout_search_t *s;

	if (layout_searches == NULL)
		return 1;

	s = (layout_search_t *)hash_find(layout_searches, search_ID);
	if (s != NULL) {
		hash_del_key(layout_searches, search_ID);
		free(s->objects);
		free(s);
		return 0;
	}
	return 2;
}


layout_object_t *search_persist_created(const char *search_id, pcb_layer_id_t layer, void *obj, layout_object_mask_t type)
{
	layout_search_t *s = NULL;
	static layout_object_t temp;

	if ((search_id != NULL) && (*search_id != '\0'))
		s = (layout_search_t *)hash_find(layout_searches, search_id);

	if (s == NULL) {
		temp.layer = layer;
		temp.obj.l = obj;
		temp.type = type;
		return &temp;
	}
	s->searching = type;
	s->layer = layer;
	return search_append(s, obj);
}
