#define _POSIX_C_SOURCE 2 

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

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

/*** 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 get_box(FILE *fc){
	char c;
	int orig_x=x, orig_y=y;
	redraw();
	step = 1;
	draw_box(x,y,NOFIX);
	while((c=fgetc(fc))!=EOF && c != 27 && c!= 'b' && c != '\n'){
		if (change_style(c))
			goto update_box;
		if (!move_around(c, fc, 1)) 
			continue;
		check_bound(&x, &y);
		redraw();
		step = 1;
update_box:
		draw_box(orig_x, orig_y, NOFIX);
		status_bar();
		show_cursor();
	}
	if (c == 'b' || c == '\n'){
		draw_box(orig_x, orig_y, 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;
}

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();
}