#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "maqview.h"

#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/gl.h>
#include <GL/glu.h>
#include "GL/glut.h"
#ifdef HAVE_FREEGLUT
#include "GL/freeglut_ext.h"
#endif
#endif

static float mv_color_bg[3] = { 0, 0, 0 },
	mv_color_A[3] = { 0.4, 0.8, 0.0 },
	mv_color_C[3] = { 0.6, 1.0, 1.0 },
	mv_color_G[3] = { 1.0, 0.6, 0.0 },
	mv_color_T[3] = { 1.0, 0.0, 0.0 },
	mv_color_N[3] = { 0.1, 0.1, 0.1 },
	mv_color_ref[3] = { 1.0, 1.0, 1.0 },
	mv_color_nav[3] = { 1.0, 1.0, 0.5 },
	mv_color_tip[3] = { 1.0, 1.0, 0.5 },
	mv_color_sel[3] = { 0.4, 0.4, 0.3 },
	mv_color_lif[3] = { 0.7, 0.0, 0.3 },
	mv_color_lib[3] = { 0.3, 0.0, 0.7 },
	mv_color_def[3] = { 1.0, 1.0, 1.0 };;

View* createView(const char *map_file, const char *cns_file, int width, int height, int style){
	MapIndex *index;
	View *view;
	char *cns_name;
	int i;
	if(style != 0){
		fprintf(stderr, "Cannot support style:%d\n", style);
		return NULL;
	}
	index = load_map_index(map_file, 0);
	if(index == NULL) return NULL;
	view = (View*)malloc(sizeof(View));
	view->map_file    = (char*)malloc(strlen(map_file) + 1);
	strcpy(view->map_file, map_file);
	view->style       = style;
	view->font        = GLUT_BITMAP_8_BY_13;
	view->big_font    = GLUT_BITMAP_9_BY_15;
	view->width       = width;
	view->height      = height;
	view->font_width  = 8;
	view->font_height = 13;
	view->font_line_height = 13;
	view->ch_width    = width / view->font_width;

	view->mode = VIEW_MODE_CHARACTER;

	view->base_width  = 5;
	view->base_margin = 0.5;
	view->base_height = 4;
	view->base_line_height = 5;

	view->line_width  = 5;
	view->line_height = 6;

	view->scale_x = 1.0;
	view->scale_y = 1.0;

	view->nav.square_size = 10;
	view->nav.center_x    = width / 2;
	view->nav.center_y    = 4 * view->nav.square_size;
	view->nav.arrow_size  = 20;
	view->nav.arrow_x    = view->nav.center_x;
	view->nav.arrow_y    = view->nav.center_y;
	view->nav.state       = 0;

	memcpy(view->bgColor.rgb, mv_color_bg, sizeof(float) * 3);
	memcpy(view->ref_color.rgb, mv_color_ref, sizeof(float) * 3);
	memcpy(view->select_color.rgb, mv_color_sel, sizeof(float) * 3);
	memcpy(view->default_color.rgb, mv_color_def, sizeof(float) * 3);
	memcpy(view->line_forward_color.rgb, mv_color_lif, sizeof(float) * 3);
	memcpy(view->line_backward_color.rgb, mv_color_lib, sizeof(float) * 3);

	view->mismatch_color.rgb[0] = 1;
	view->mismatch_color.rgb[1] = 0;
	view->mismatch_color.rgb[2] = 0;

	memcpy(view->nav_color.rgb, mv_color_nav, sizeof(float) * 3);
	memcpy(view->tip_color.rgb, mv_color_tip, sizeof(float) * 3);

	i = 100;
	view->qual_color_table = (Color*)malloc(sizeof(Color) * (i + 1));
	while (i >= 0) {
		float value = (float)((i >= MAX_BASE_Q)? MAX_BASE_Q : i) / MAX_BASE_Q;
		value = 0.8 * value + 0.2;
		view->qual_color_table[i].rgb[0] = value;
		view->qual_color_table[i].rgb[1] = value;
		view->qual_color_table[i].rgb[2] = value;
		i --;
	}

	view->base_color_table = (Color*)malloc(sizeof(Color) * 5);
	memcpy(view->base_color_table[0].rgb, mv_color_A, sizeof(float) * 3);
	memcpy(view->base_color_table[1].rgb, mv_color_C, sizeof(float) * 3);
	memcpy(view->base_color_table[2].rgb, mv_color_G, sizeof(float) * 3);
	memcpy(view->base_color_table[3].rgb, mv_color_T, sizeof(float) * 3);
	memcpy(view->base_color_table[4].rgb, mv_color_N, sizeof(float) * 3);

	view->default_ref_char = '-';

	view->refs.cache = read_cache_init(index);

	view->refs.show_id    = 0;
	view->refs.show_start = 0;
	view->refs.tooltips     = (unsigned char*)malloc(TOOLTIP_SIZE);
	memset(view->refs.tooltips, 0, TOOLTIP_SIZE);

	cns_name = cns_file? (char*)cns_file : (char*)calloc(strlen(map_file)+6, 1);
	view->refs.cns = open_cns_cache(cns_name);
	if(view->refs.cns == NULL){
		view->refs.cns = cns_cache_init();
	} else {
		justify_cns_cache(view->refs.cns, view->refs.cache->index->mm->ref_name, view->refs.cache->index->mm->n_ref);
		for(i=0;i<view->refs.cache->index->mm->n_ref;i++){
			view->refs.cache->ref_lengths[i] = view->refs.cns->ref_lengths[i];
			if(view->refs.cache->index->trees[i]->right < view->refs.cns->ref_lengths[i]){
				view->refs.cache->index->trees[i]->right = view->refs.cns->ref_lengths[i];
			}
		}
	}
	view->cns_file = (char*)malloc(strlen(cns_name) + 1);
	strcpy(view->cns_file, cns_name);
	if(!cns_file) free(cns_name);

	view->layer_offset = 1; // Deafult move down one line

	view->last_x = view->last_y = 0;
	view->old_x  = view->old_y  = 0;
	view->state = 0;

	view->rolling = 0;

	view->selecting = 0;

	view->step = 1;

	view->observer = (Observer*)malloc(sizeof(Observer));
	view->observer->n_ob = 0;
	view->observer->objects = NULL;
	view->observer->notify  = NULL;


	return view;
}

View* view_clone(View *view){
	return createView(view->map_file, view->cns_file, view->width, view->height, view->style);
}

void observeView(View *view, void *observer, void (*notify)(void *obj, View *src, int event, int64_t last_x, int off_y)){
	if(!notify) return;
	view->observer->n_ob ++;
	view->observer->objects = (void**)realloc(view->observer->objects, sizeof(void*) * view->observer->n_ob);
	view->observer->notify  = (void*)realloc(view->observer->notify, sizeof(void*) * view->observer->n_ob);
	view->observer->objects[view->observer->n_ob-1] = observer;
	view->observer->notify[view->observer->n_ob-1]  = notify;
}

void removeObserverView(View * view, void *object){
	Observer *ob;
	int i;
	ob = view->observer;
	if(!ob) return;
	for(i=0;i<ob->n_ob;i++){
		if(ob->objects[i] == object){
			ob->n_ob --;
			if(ob->n_ob && i < ob->n_ob){
				memmove(ob->objects + i, ob->objects + i + 1, ob->n_ob - i);
				memmove(ob->notify + i, ob->notify + i + 1, ob->n_ob - i);
			}
			i --;
		}
	}
}

void notifyView(View *view, int evt, int64_t x, int y){
	int i;
	for(i=0;i<view->observer->n_ob;i++){
		view->observer->notify[i](view->observer->objects[i], view, evt, x, y);
	}
}

void view_resize(View *view, int w, int h){
	switch(view->mode){
		case VIEW_MODE_CHARACTER:
			view->ch_width = w / view->font_width;
			break;
		case VIEW_MODE_SQUARE:
			view->ch_width = w / (view->base_width * view->scale_x);
			break;
		case VIEW_MODE_LINE:
			view->ch_width = w / (view->line_width * view->scale_x);
			break;
		default: return;
	}
	memset(view->refs.tooltips, 0, TOOLTIP_SIZE);
	view->nav.center_x    = w / 2;
	view->nav.center_y    = 4 * view->nav.square_size;
	view->nav.arrow_x    = view->nav.center_x;
	view->nav.arrow_y    = view->nav.center_y;
	notifyView(view, VIEW_EVENT_RESIZE, view->refs.show_start, 0);
	view_goto(view, view->refs.show_id, view->refs.show_start);
}

void relayout(View *view){
	int i, j, lay_size;
	int64_t *layers;
	Read *read;
	lay_size = 256;
	layers = (int64_t*)malloc(sizeof(int64_t) * lay_size);
	memset(layers, 0, sizeof(int64_t) * lay_size);
	for(i=0;i<view->refs.cache->size;i++){
		read = view->refs.cache->reads + view->refs.cache->offset + i;
		j = 0;
		REPEAT:
		for(;j<lay_size;j++){
			if(read_pos(read->read.pos) >= layers[j]){
				read->y = j;
				layers[j] = read_pos(read->read.pos) + read->read.size + 1;
				break;
			}
		}
		if(j == lay_size){
			lay_size += 256;
			layers = (int64_t*)realloc(layers, sizeof(int64_t) * lay_size);
			memset(layers + j, 0, sizeof(int64_t) * (lay_size - j));
			goto REPEAT;
		}
	}
	free(layers);
}

int view_locate(View *view, char *ref_name, int64_t pos){
	int ref_id;
	if(ref_name == NULL) return 0;
	for(ref_id=0;ref_id<view->refs.cache->index->mm->n_ref;ref_id++){
		if(strcmp(ref_name, view->refs.cache->index->mm->ref_name[ref_id]) == 0){
			return view_goto(view, ref_id, pos);
		}
	}
	return 0;
}

int view_goto(View *view, int ref_id, int64_t pos){
	int i;
	int64_t start, end, last_start;
	int size, ret;
	RefSeq *refs;
	refs = &(view->refs);
	size = view->ch_width;
	start = pos - MAX_READ_SIZE;
	end   = pos + size;
	last_start = view->refs.show_start;
	ret = read_cache_put(view->refs.cache, ref_id, start, end);
	if(ret < 0){
		printf("[error]: %s:%d\n", __FILE__, __LINE__);
		return 0;
	}
	refs->show_start = pos;
//	refs->show_start = (pos<0)? 0:(pos);
	switch(ret){
		case 1:
			notifyView(view, VIEW_EVENT_GOTO, last_start, 0);
			break;
		default:
			notifyView(view, VIEW_EVENT_MOVE, last_start, 0);
	}
	if(ret){
		relayout(view);
	}
	if(refs->show_id != ref_id){
		memset(refs->tooltips, 0, size);
		refs->show_id = ref_id;
	} else {
		if(refs->show_start < last_start + size && refs->show_start + size > last_start){
			if(refs->show_start < last_start){
				for(i=size-1;i>=last_start - refs->show_start;i--){
					refs->tooltips[i] = refs->tooltips[i+refs->show_start-last_start];
				}
				for(;i>=0;i--){
					refs->tooltips[i] = 0;
				}
			} else {
				for(i=0;i<size - refs->show_start + last_start;i++){
					refs->tooltips[i] = refs->tooltips[i+refs->show_start-last_start];
				}
				for(;i<size;i++){
					refs->tooltips[i] = 0;
				}
			}
		} else {
			memset(refs->tooltips, 0, size);
		}
	}
	if(refs->cns->stream && ref_id >= 0 && ref_id < refs->cns->n_mm_ref && refs->cns->mm_map[ref_id] >= 0){
		cns_cache_put(refs->cns, refs->cns->mm_map[ref_id], start, end);
	} else {
		cns_cache_guess(refs->cns, refs->cache);
	}
	return 1;
}

void closeView(View *view){
	RefSeq *refs;
	notifyView(view, VIEW_EVENT_CLOSE, 0, 0);
	free(view->observer->objects);
	free(view->observer->notify);
	free(view->observer);
	free(view->qual_color_table);
	free(view->base_color_table);
	refs = &(view->refs);
	close_cns_cache(refs->cns);
	free(refs->tooltips);
	read_cache_free(refs->cache);
	free(view->map_file);
	free(view->cns_file);
	free(view);
}

#ifdef MAIN_VIEW_GOTO

void print_view(View *view){
	int i;
	RefSeq *refs;
	Read *read;
	refs = &(view->refs);
	printf("------------------------ %lld %lld ---------------------------\n", refs->cache_start, refs->cache_end);
	for(i=0;i<refs->read_size;i++){
		read = refs->reads + refs->read_offset + i;
		printf("%s\t%d\n", read->read.name, read_pos(read->read.pos) + 1);
	}
	printf("------------------------------------------------------------\n\n");
}

int main(int argc, char **argv){
	View *view;
	int i;
	if(argc < 2){
		fprintf(stderr, "Usage: %s <map file>\n", argv[0]);
		return 1;
	}
	view = createView(argv[1], 1200, 800, 0);
	if(view == NULL){
		printf("Cannot create view on file %s\n", argv[1]);
		return 1;
	}
	view_goto(view, 0, 1000);
	for(i=0;i<100;i++){
		view_goto(view, 0, view->refs.show_start + 100);
	}
	for(;i>0;i--){
		view_goto(view, 0, view->refs.show_start - 100);
	}
	closeView(view);
	return 0;
}

#endif
