#define _POSIX_C_SOURCE 200112L

#include <stdlib.h>
#include <string.h>

#include "gramscii.h"
#include "config.h"

/** Extern declarations **/

extern int WIDTH, HEIGHT;
extern lineset_t screen; /* what is visualised */
extern lineset_t cutbuf; /* cut/paste buffer */
extern lineset_t *undo;  /* undo list */

extern int undo_cur;/* undo position */
extern int undo_lst;/* last valid undo position */


extern int mode;/* mode */
extern int dir;/* line direction */
extern int step;/* current step */
extern int x;
extern int y;
extern char corner;
extern char modified; /* set to 1 if screen modified since last save */ 

/* line and arrow markers */
extern int cur_hl, cur_vl, cur_corn, cur_start, cur_end;
extern char line_h;
extern char line_v;
extern char mark_st;
extern char mark_end;

/* number of available markers for each type */
extern int hlines_sz;
extern int vlines_sz;
extern int corners_sz;
extern int stmarks_sz;
extern int endmarks_sz;


extern char autoend; /* set to 1 in auto-arrow mode */

/* Used by draw_arrow to identify the bounding box */
extern int a_miny;
extern int a_maxy;

/*** drawing-related functions ***/

/*** Lines and markers ***/

void toggle_hline(){

	cur_hl = (cur_hl + 1) % hlines_sz;
	line_h = hlines[cur_hl];

}

void toggle_corner(){

	cur_corn = (cur_corn + 1 ) % corners_sz;
	corner = corners[cur_corn];

}

void toggle_vline(){

	cur_vl = (cur_vl + 1) % vlines_sz;
	line_v = vlines[cur_vl];

}

void toggle_st_mark(){

	cur_start = (cur_start + 1 ) % stmarks_sz;
	mark_st = st_marks[cur_start];
}

void toggle_end_mark(){

	cur_end = (cur_end+ 1 ) % endmarks_sz;
	mark_end = end_marks[cur_end];
}

int change_style(char c){
	switch(c){
		case '-':
			toggle_hline();
			break;
		case '|':
			toggle_vline();
			break;
		case '+':
			toggle_corner();
			break;
		case '<':
			toggle_st_mark();
			break;
		case '>':
			toggle_end_mark();
			break;
		case '.':
			reset_styles();
			break;
		default: 
			return 0;
	}
	return c;
}




/*****  text, box, arrows  *****/

void get_text(FILE *fc){
	char c;
	int orig_x = x;

	redraw();
	copy_lines_to_ring(y, y, PRV_STATE);
	while((c=fgetc(fc))!=EOF && c != 27){
		if(c=='\n'){
			set_cur(BG);
			copy_lines_to_ring(y,y, NEW_STATE);
			y += 1;
			copy_lines_to_ring(y, y, PRV_STATE);
			x = orig_x;
		}
		else {
			set_cur(c);
			update_current();
			modified = 1;
			x += 1;
			if (x >= WIDTH)
				x = orig_x;
		}
		check_bound(&x, &y);
		status_bar();
		show_cursor();
	}
	if (modified)
		copy_lines_to_ring(y, y, NEW_STATE);
	mode=MOVE;
}

void draw_box(int x1, int y1, int fix){

	int xmin, ymin, xmax, ymax;
	int i;
	void (*f)(int, int, char);


	xmin = MIN(x, x1);
	xmax = MAX(x, x1);
	ymin = MIN(y, y1);
	ymax = MAX(y, y1);

	if (fix == FIX){
		f = set_xy;
		copy_lines_to_ring(ymin, ymax, PRV_STATE);
	}
	else
		f = draw_xy;

	for(i=xmin+1; i<=xmax; i++){
		f(i, ymin, line_h);
		f(i, ymax, line_h);
	}
	for(i=ymin+1; i<=ymax; i++){
		f(xmin, i, line_v);
		f(xmax, i, line_v);
	}
	f(xmin, ymin, corner);
	f(xmin, ymax, corner);
	f(xmax, ymin, corner);
	f(xmax, ymax, corner);
	if (fix == FIX)
		copy_lines_to_ring(ymin, ymax, NEW_STATE);
	show_cursor();
}

void draw_parallelogram(int x1, int y1, char st, char fix){
	int xmin, ymin, xmax, ymax;
	int dy, minoff, maxoff, xoff, xincr;
	int i;
	char lean;
	void (*f)(int, int, char);


	xmin = MIN(x, x1);
	xmax = MAX(x, x1);
	ymin = MIN(y, y1);
	ymax = MAX(y, y1);
	dy = ymax - ymin;
	
	if (fix == FIX){
		f = set_xy;
		copy_lines_to_ring(ymin, ymax, PRV_STATE);
	}
	else
		f = draw_xy;
	if (st == BOX_PARR){
		minoff = dy;
		maxoff = 0;
		lean = '/';
		xincr = -1;
	}
	else {
		minoff = 0;
		maxoff = dy;
		lean = '\\';
		xincr = +1;
	}
	for(i=xmin+1; i<=xmax-dy; i++){
		f(i+minoff, ymin, line_h);
		f(i+maxoff, ymax, line_h);
	}
	
	for(i=ymin+1, xoff=minoff; i<=ymax; i++, xoff += xincr){
		f(xmin+(xoff+xincr), i, lean);
		if (minoff)
			f(xmax - (minoff - xoff - xincr), i, lean);
		else 
			f(xmax - (maxoff - xoff - xincr), i, lean);
	}
	f(xmin+minoff, ymin, corner);
	f(xmin+maxoff, ymax, corner);
	f(xmax-maxoff, ymin, corner);
	f(xmax-minoff, ymax, corner);
	if (fix == FIX)
		copy_lines_to_ring(ymin, ymax, NEW_STATE);
	show_cursor();

}

char flip_par_lean(char st){
	if (st == BOX_PARR)
		return BOX_PARL;
	else if (st == BOX_PARL)
		return BOX_PARR;
	return st;
}

void draw_trapezium(int x1, int y1, char st, char fix){
	int xmin, ymin, xmax, ymax;
	int dx, dy, ylong, yshort, xoff;
	int xincr;
	int i;
	void (*f)(int, int, char);
	char left_c, right_c;

	xmin = MIN(x, x1);
	xmax = MAX(x, x1);
	ymin = MIN(y, y1);
	ymax = MAX(y, y1);
	dx = (xmax - xmin);
	dy = ymax - ymin;
	/* dy = MAX(dx2, dy); */
#ifdef DEBUG
	fprintf(stderr, "dy: %d dx: %d\n", dy, dx);
#endif	
	if (fix == FIX){
		f = set_xy;
		copy_lines_to_ring(ymin, ymax, PRV_STATE);
	}
	else
		f = draw_xy;

	/* This is valid only for "upper" trapezoids */
	if (STYLE_IS(st, BOX_TRAP_U)){
#ifdef DEBUG
		fprintf(stderr, "This is an 'upward' trapezium\n");
#endif
		ylong = ymax;
		yshort = ymin;
		xoff = dy;
		xincr = -1;
		left_c = '/';
		right_c = '\\';
	}
	else if (STYLE_IS(st, BOX_TRAP_D)){
#ifdef DEBUG
		fprintf(stderr, "This is a 'downward' trapezium\n");
#endif
		ylong = ymin;
		yshort = ymax;
		xoff = dy;
		xincr = +1;
		right_c = '/';
		left_c = '\\';
	}
	/* Long side */
	for(i=xmin+1; i<=xmax; i++){
		f(i, ylong, line_h);
	}
	
	if (STYLE_IS(st, BOX_TRAP_L)){
		/* short side */
		left_c = '/';
		right_c = line_v;
		for(i=xmin+xoff;i<xmax; i++){
			f(i, yshort, line_h);
		}
		xoff = dy;
		if (STYLE_IS(st, BOX_TRAP_D)){
			xoff = 0;
			left_c = '\\';
		}
		for(i=ymin; i<ymax; i++, xoff += xincr){
			f(xmin+xoff, i, left_c);
			f(xmax, i, right_c);
		}
		f(xmin+dy, yshort, corner);
		f(xmax, yshort, corner);
	}
	else if (STYLE_IS(st, BOX_TRAP_R)){
		right_c = '\\';
		left_c = line_v;
		for(i=xmin; i<xmax-xoff; i++){
			f(i, yshort, line_h);
		}
		xoff = dy-1;
		if (STYLE_IS(st, BOX_TRAP_D)){
			xoff = 1;
			right_c = '/';
		}
		for(i=ymin+1; i<ymax; i++, xoff += xincr){
			f(xmin, i, left_c);
			f(xmax-xoff, i, right_c);
		}
		f(xmin, yshort, corner);
		f(xmax-dy, yshort, corner);
	}
	else if (STYLE_IS(st, BOX_TRAP_C)){
		xoff = dy;
		for (i=xmin+xoff; i<=xmax-xoff; i++){
			f(i, yshort, line_h);
		}
		xoff = dy - 1;
		if (STYLE_IS(st, BOX_TRAP_D))
			xoff = 1;
		for(i=ymin+1; i<ymax; i++, xoff += xincr){
			f(xmin + xoff, i, left_c);
			f(xmax - xoff, i, right_c);
		}
		f(xmin+dy, yshort, corner);
		f(xmax-dy, yshort, corner);
	}
	

	f(xmin, ylong, corner);
	f(xmax, ylong, corner);

	
	if (fix == FIX)
		copy_lines_to_ring(ymin, ymax, NEW_STATE);
	show_cursor();

}

/* 
 * draw the current box, being it an actual box, a parallelogram, or a 
 *  trapezium
 */
void update_box(int x1, int y1, char st, char fix){

	if (st == BOX_RECT)
		draw_box(x1, y1, fix);
	else if (st & BOX_PAR)
		draw_parallelogram(x1, y1, st, fix);
	else if (st & BOX_TRAP)
		draw_trapezium(x1, y1, st, fix);
	status_bar();
	show_cursor();
}

char toggle_trap_type(char st){
	if (st & BOX_TRAP){
		if (st != BOX_TRAP_DR)
			st += 1;
		else 
			st = BOX_TRAP_UR;
	}
	if (st == BOX_TRAP_D) 
		st += 1;
	return st;
}

int box_end(char c, char st){
	if (c == '\n' || 
	    c == 27   ||
	    ((st == BOX_RECT) &&  c == 'b') ||
	    ((st & BOX_PAR)   &&  c == 'z') ||
	    ((st & BOX_TRAP)  &&  c == 't'))
		return 1;
	return 0; 
}

/* draw boxes, parallelograms, and trapezia */
void get_box(FILE *fc, char st){
	char c;
	int orig_x=x, orig_y=y;
	redraw();
	step = 1;
#ifdef DEBUG
	fprintf(stderr, "box style: %d\n", st);
#endif
	draw_box(x,y,NOFIX);
	while((c=fgetc(fc))!=EOF && !box_end(c, st)){
		if (c == 'Z' && (st & BOX_PAR)){
			st = flip_par_lean(st);
			redraw();
#ifdef DEBUG
			fprintf(stderr, "new parallelogram style: %d\n", st);
#endif
			update_box(orig_x, orig_y, st, NOFIX);
			continue;
		}
		else if (c == 'T' && (st & BOX_TRAP)){
			st = toggle_trap_type(st);
#ifdef DEBUG
			fprintf(stderr, "new trapezium style: %d\n", st);
#endif
			redraw();
			update_box(orig_x, orig_y, st, NOFIX);
			continue;
		}
		if (change_style(c)){
			update_box(orig_x, orig_y, st, NOFIX);
			continue;
		}
		if (!move_around(c, fc, 1)) 
			continue;
		check_bound(&x, &y);
		redraw();
		step = 1;
		update_box(orig_x, orig_y, st, NOFIX);
	}
	if (((st == BOX_RECT ) && (c == 'b' || c == '\n')) ||
	   ( (st &  BOX_PAR  ) && (c == 'z' || c == '\n')) ||
	   ( (st &  BOX_TRAP ) && (c == 't' || c == '\n'))){
		update_box(orig_x, orig_y, st, FIX);
		modified = 1;
	}
	redraw();
	mode = MOVE;
}

void draw_arrow(int xl, int yl, short *a, int a_len, int fix){

	int i, j, cur_dir;
	char line;
	void (*f)(int, int, char);

	a_miny = a_maxy = yl;
	if (fix == FIX)
		f = set_xy;
	else
		f = draw_xy;

	f(xl, yl, mark_st);
	if (!a_len){
		show_cursor();
		return;
	}
	cur_dir=DIR_N;
	for (i=0; i<a_len; i+=2){
		if (i>0) {
			/* If we are switching between horizontal and vertical, put a "corner" */
			if (((cur_dir & DIR_HOR) && (a[i] & DIR_VER)) ||
			    ((cur_dir & DIR_VER) && (a[i] & DIR_HOR))){
				f(xl, yl, corner);
				show_cursor();
			}
		}
		for(j=0; j<a[i+1]; j++){
			line = (a[i] & DIR_L) || (a[i] & DIR_R) ? line_h : line_v;
			xl += progr_x(a[i]);
			yl += progr_y(a[i]);
			check_bound(&xl, &yl);
			if (yl < a_miny) a_miny = yl;
			if (yl > a_maxy) a_maxy = yl;
			f(xl, yl, line);
		}
		/* f(x,y,mark_end);*/
		cur_dir = a[i];
	}
	if (autoend){
		if (cur_dir != DIR_N)
			f(xl,yl, get_mark(cur_dir));
	}
	else 
		f(xl,yl,mark_end);
	show_cursor();
}

void get_arrow(FILE *fc){

	char c;
	int orig_x=x, orig_y=y, arrow_len;
	static short  *arrow = NULL;
	short *tmp = NULL;
	static int arrow_sz;

	if (!arrow){
		arrow_sz = 100;
		arrow = malloc(arrow_sz * sizeof(short));
		if (arrow == NULL){
			fprintf(stderr, "Unable to allocate arrow");
			cleanup(1);
		}
	}
	arrow_len = 0;
	dir = DIR_N;

	redraw();
	step = 1;
	draw_arrow(x,y, arrow, 0, NOFIX);
	while((c=fgetc(fc))!=EOF && c != 27 && c!= 'a' && c != '\n'){
		if (change_style(c))
			goto update_arrow;
		if (!move_around(c, fc, 0))
			continue;
		check_bound(&x, &y);
		/* FIXME: if we are out of bound, do nothing? */
		if (arrow_len == arrow_sz){
			arrow_sz *=2;
			tmp = realloc(arrow, arrow_sz * sizeof(short));
			if (tmp == NULL){
				fprintf(stderr, "Unable to reallocate arrow");
				cleanup(1);
			}
			arrow = tmp;
		}
		if (dir != DIR_N){
			arrow[arrow_len++] = dir;
			arrow[arrow_len++] = step;
		}
 		redraw();
		step = 1;
update_arrow:
		draw_arrow(orig_x, orig_y, arrow, arrow_len, NOFIX);
		status_bar();
		show_cursor();
	}
	if (c == 'a' || c == '\n'){
		copy_lines_to_ring(a_miny, a_maxy, PRV_STATE);
		draw_arrow(orig_x, orig_y, arrow, arrow_len, FIX);
		copy_lines_to_ring(a_miny, a_maxy, NEW_STATE);
		modified = 1;
	}
	redraw();
	mode = MOVE;
}


void do_erase(int x1, int y1){
	int i;
	switch(dir){
		case DIR_R:
			for(i=x1; i<=x; i++) set_xy(i,y,BG);
			break;
		case DIR_L:
			for(i=x1; i>=x; i--) set_xy(i,y,BG);
			break;
		case DIR_U:
			for(i=y1; i>=y; i--) set_xy(x,i,BG);
			break;
		case DIR_D:
			for(i=y1; i<=y; i++) set_xy(x,i,BG);
			break;
	}
}


void erase(FILE *fc){
	/*FIXME: add affected lines to undo */
	char c;
	int orig_x = x, orig_y = y;
	char first = 1, opened = 0;
	status_bar();
	show_cursor();
	while((c=fgetc(fc))!=EOF && c!=27 && c!= 'x' && c != '\n'){
		if (!move_around(c, fc, 0)) continue;
		check_bound(&x, &y);
		if (first || 
		    (y != orig_y && ! opened) ||
		    (y == orig_y && x != orig_x && !opened) ){
			copy_lines_to_ring(MIN(y, orig_y), MAX(y, orig_y), PRV_STATE);
			first = 0;
			opened = 1;
		}
		do_erase(orig_x, orig_y);
		if (y != orig_y && opened){
			copy_lines_to_ring(MIN(y, orig_y), MAX(y, orig_y), NEW_STATE);
			opened = 0;
		}
		step = 1;
		modified = 1;
		orig_x = x;
		orig_y = y;
		redraw();
		status_bar();
		show_cursor();
	}
	if (opened)
		copy_lines_to_ring(y, y, NEW_STATE);
	mode = MOVE;
}





/*** Visual ***/


void visual_box(FILE *fc){
	int orig_x =x, orig_y = y;
	char c, f = BG;

	redraw();
	step = 1;
	set_video(VIDEO_REV);
	draw_box(x,y,NOFIX);
	while((c=fgetc(fc))!=EOF && c != 27 && c!= 'v' && c != '\n'){
		if (!move_around(c, fc, 1)) switch(c){
			case 'y': /* yank (copy) */
				yank_region(MIN(orig_x,x), MIN(orig_y,y), MAX(orig_x, x), MAX(orig_y, y));
				goto vis_exit;
				break;
			case 'f':/* fill */
				f = get_key(fc, "fill char: "); /** FALLTHROUGH **/
			case 'x':/* erase */
				if (c == 'x')
					yank_region(MIN(orig_x,x), MIN(orig_y,y), MAX(orig_x, x), MAX(orig_y, y));
				copy_lines_to_ring(MIN(orig_y, y), MAX(orig_y, y), PRV_STATE);
				erase_box(orig_x, orig_y, f);
				erase_blank_lines(MIN(y,orig_y), MAX(y, orig_y));
				copy_lines_to_ring(MIN(orig_y, y), MAX(orig_y, y), NEW_STATE);
		
				modified = 1;
				goto vis_exit;
				break;
			case 'C':/* crop-to-region */
				copy_lines_to_ring(0, HEIGHT-1, PRV_STATE);
				crop_to_rect(MIN(x, orig_x), MIN(y, orig_y), MAX(x, orig_x), MAX(y, orig_y));
				copy_lines_to_ring(0, HEIGHT-1, NEW_STATE);
				modified = 1;
				goto vis_exit;
				break;
		} 
		check_bound(&x, &y);
		set_video(VIDEO_NRM);
		redraw();
		step = 1;
		f = BG;
		set_video(VIDEO_REV);
		draw_box(orig_x, orig_y, NOFIX);
		status_bar();
		show_cursor();
	}
vis_exit:
	set_video(VIDEO_NRM);
	redraw();
	mode = MOVE;
}

/*** yank/paste/undo ***/

void paste(){
	int y2;

	y2 = y + cutbuf.num - 1;
	copy_lines_to_ring(y, y2, PRV_STATE);
	paste_region(x, y);
	copy_lines_to_ring(y, y2, NEW_STATE);
	redraw();
}

void put_lines(lineset_t *u){
	int i, n;
	
	for (i=0; i< u->num; i++){
		n = u->l[i].n;
		ensure_line_length(&(screen.l[i]), strlen(u->l[i].s));
		strcpy(screen.l[n].s, u->l[i].s);
		screen.l[n].lst = strlen(u->l[i].s)-1;
	}
}


void undo_change(){
	if (undo_cur >= 0){
		if (undo_cur > undo_lst)
			undo_cur = undo_lst;
		put_lines(& (undo[undo_cur]));
		undo_cur -= 2;
		modified = 1;
	}
	redraw();
}

void redo_change(){
	if (undo_cur <= undo_lst-2){
		if (undo_cur > 0)
			put_lines(& (undo[undo_cur+1]));
		undo_cur +=2;
		put_lines(& (undo[undo_cur+1]));
		modified = 1;
	}
	redraw();
}


/** Comments **/

void get_comment(FILE *fc){
	char c;
	redraw();
	while((c = fgetc(fc)) != EOF && c != '\n');
	mode = MOVE;
}