#define _POSIX_C_SOURCE 2 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <ctype.h>

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

/*** screen management functions ***/

/*** _isblank ***/

int _isblank(int c){
	return c==32 || c==9 ? 1 : 0;
}


/*** Status bar ***/

char* mode_str(){
	switch(mode){
		case MOVE:
			return "mov";
		case TEXT:
			return "txt";
		case BOX:
			return "box";
		case ARROW:
			return "arr";
		case DEL:
			return "del";
		case VIS:
			return "vis";
		default:
			return "ERR";
	}
	return "ERR";
}

char get_mark(char dir){
	switch(dir){
		case DIR_U:
			return '^';
		case DIR_D:
			return 'v';
		case DIR_L:
			return '<';
		case DIR_R:
			return '>';
	}
	return '>';
}


void status_bar(){

	if (silent)
		return;
	printf("\033[%d;1f\033[7m", HEIGHT+1);
	printf("%*s", WIDTH-1, "");
	printf("\033[%d;1f\033[7m", HEIGHT+1);
	printf(" x:%3d y:%3d -- MODE:%4s HL:%c VL:%c CN:%c SP:%c EP:%c %10s",
		x, y, mode_str(), line_h, line_v, corner, mark_st, mark_end, "");
	if (!modified)
		printf(" [%s]", fname );
	else
		printf(" *%s*", fname );
#ifdef DEBUG
	printf("  '%d'  ", screen.l[y].s[x]);
#endif
	printf("\033[0m");
	fflush(stdout);
}

char get_key(FILE *fc, char *msg){

	if (silent)
		return 0;
	printf("\033[%d;1f\033[7m", HEIGHT+1);
	printf("%*s", WIDTH, "");
	printf("\033[%d;1f\033[7m", HEIGHT+1);
	printf("%s", msg);
	fflush(stdout);
	printf("\033[0m");
	fflush(stdout);
	return fgetc(fc);
}

void get_string(FILE *fc, char *msg, char *s, int sz){

	if (!silent){
		printf("\033[%d;1f\033[7m", HEIGHT+1);
		printf("%*s", WIDTH, "");
		printf("\033[%d;1f\033[7m", HEIGHT+1);
	
		/* We must activate echo now */
		t3 = t2;
		t3.c_lflag |= (ECHO | ICANON);
		tcsetattr(0, TCSANOW, &t3);
		printf("%s", msg);
		printf("\033[0m");
	}
	fgets(s, sz, fc);
	s[strlen(s)-1] = '\0';
	tcsetattr(0, TCSANOW, &t2);
	if (!silent)
		fflush(stdout);
}

int is_yes(char c){
	return c=='y' ? 1 : c == 'Y'? 1 : 0;
}

/*** Screen management ***/


void show_cursor(){
	if (silent)
		return;
	printf("\033[%d;%df", y+1, x+1);
	fflush(stdout);
}


void set_xy(int _x, int _y, char c){
	ensure_num_lines(&screen, _y + 1);
	ensure_line_length(&(screen.l[_y]), _x + 1);
	while (screen.l[_y].lst<_x){
		screen.l[_y].lst ++;
		screen.l[_y].s[screen.l[_y].lst] = BG;
	}
	screen.l[_y].s[_x] = c;
	if (_x == screen.l[_y].lst)
		screen.l[_y].s[_x+1] = '\0';
}

void set_cur(char c){
	set_xy(x, y, c);
}

void draw_xy(int x, int y, char c){
	/* FIXME: check if x and y are valid!!!! */
	if (silent)
		return;
	printf("\033[%d;%df",y+1,x+1);
	putchar(c);
	fflush(stdout);
}

void update_current(){
	if (silent)
		return;
	printf("\033[%d;%df",y+1,x+1);
	putchar(screen.l[y].s[x]);
	fflush(stdout);
}

void erase_blank_lines(int y1, int y2){
	int j;
	if (y1 > y2){
		y1 ^= y2;
		y2 ^= y1;
		y1 ^= y2;
	}
	
	for (; y1 <= y2; y1++){
		j = screen.l[y1].lst; 
		while (j>=0 && _isblank(screen.l[y1].s[j]))
			j--;
		if (j<0){
			screen.l[y1].lst = -1;
			screen.l[y1].s[0] = '\0';
		}
	}
}


void erase_line(int i){
	screen.l[i].lst = -1;
	screen.l[i].s[0] = '\0';
}

void erase_box(int x1, int y1, char c){
	int x_incr, y_incr, i; 

	x_incr = x1 < x? +1: -1;
	y_incr = y1 < y? +1: -1;
	do{
		i = y1;
		do{
			set_xy(x1, i, c);
		} while(i != y && (1 | (i += y_incr)));
	} while(x1 != x && (1 | (x1 += x_incr)));
	
}

void erase_screen(){
	int i;
	for(i=0;i<HEIGHT; i++)
		erase_line(i);
}

void check_bound(int *x, int *y){
	if (*x<0) *x=0;
	else if (*x>=WIDTH) *x = WIDTH-1;
	if (*y<0) *y=0;
	else if (*y>=HEIGHT) *y = HEIGHT -1;
}

void reset_styles(){

	cur_corn = 0;
	corner = corners[0];
	cur_hl = cur_vl = 0;
	cur_start = cur_end = 0;
	line_h = hlines[cur_hl];
	line_v = vlines[cur_vl];
	mark_st = st_marks[cur_start];
	mark_end = end_marks[cur_end];
}

void redraw(){
	int i;

	if (silent)
		return;
	printf("\033[2J\033[1;1H");
	for (i=0;i<HEIGHT;i++){
		fprintf(stdout,"%s\n",screen.l[i].s);
	}
	status_bar();
	show_cursor();
}

void go_to(int where){
	switch(where){
		case HOME:
			x = y = 0;
			break;
		case END:
			x = WIDTH-1;
			y = HEIGHT-1;
			break;
		case MIDDLE:
			x = WIDTH/2;
			y = HEIGHT/2;
			break;
	}
	check_bound(&x, &y);
	show_cursor();
}

void handle_goto(char global){
	char c;	
	c=getchar();
	switch(c){
		case 'h':
			dir = DIR_L;
			step = x;
			x = 0;
			break;
		case 'l':
			dir = DIR_R;
			step = WIDTH - x -1;
			x = WIDTH - 1;
			break;
		case 'j':
			dir = DIR_D;
			step = HEIGHT - y-1;
			y = HEIGHT - 1;
			break;
		case 'k':
			dir = DIR_U;
			step = y;
			y = 0;
			break;
		case 'g':
			if (global){
				dir = DIR_N;
				go_to(HOME);
			} else step = 0;
			break;
		case 'G':
			if (global){
				dir = DIR_N;
				go_to(END);
			} else step = 0;
			break;
		case 'm':
			if (global){
				dir = DIR_N;
				go_to(MIDDLE);
			} else step = 0;
			break;
	}

#ifdef DEBUG
	fprintf(stderr, "global move: dir: %d x: %d y: %d\n", dir, x, y);
#endif
	check_bound(&x, &y);
	show_cursor();
}


int get_escape(FILE *fc){
	char c[4];
	
	c[0] = fgetc(fc);
	if (c[0] == '['){
		c[1] = fgetc(fc);
		switch(c[1]){
			case 'D':
				dir = DIR_L;
				x -= step;
				break;
			case 'B':
				dir = DIR_D;
				y += step;
				break;
			case 'A':
				dir = DIR_U;
				y -= step;
				break;
			case 'C':
				dir = DIR_R;
				x += step;
				break;
		}
		return 1;
	}
	else{
		ungetc(c[0], fc);
		return 0;
	}

}


int move_around(char c, FILE *fc, char global){

	if (isdigit(c)){
		if (mult)
			mult *=10;
		mult += c - '0';
		return 0;
	}
	switch(c){
		case 27: /* control sequence? */
			c = get_escape(fc);
			break;
		case 'H': step = LONG_STEP;/** FALLTHROUGH **/
		case 'h':
			dir = DIR_L;
			if (mult) 
				step *= mult;
			x -= step;
			break;
		case 'J': step = LONG_STEP;/** FALLTHROUGH **/
		case 'j':
			if (mult) 
				step *= mult;
			dir = DIR_D;
			y += step;
			break;
		case 'K': step = LONG_STEP;/** FALLTHROUGH **/
		case 'k':
			if (mult) 
				step *= mult;
			dir = DIR_U;
			y -= step;
			break;
		case 'L': step = LONG_STEP;/** FALLTHROUGH **/
		case 'l':
			if (mult) 
				step *= mult;
			dir = DIR_R;
			x += step;
			break;
		case 'g':
#ifdef DEBUG
			fprintf(stderr, "before global: step: %d x: %d y: %d\n", step, x, y);
#endif
			handle_goto(global);
#ifdef DEBUG
			fprintf(stderr, "after global: step: %d x: %d y: %d\n", step, x, y);
#endif
			break;
		default:
			return 0;
	}
	mult = 0;
	return c;
}


void set_video(int v){
	if (silent)
		return;
	printf("\033[%dm", v);
	fflush(stdout);
}


void init_screen(){
	int i;
	struct winsize wsz;
	
	if (!ioctl(STDIN_FILENO, TIOCGWINSZ, &wsz)){
		WIDTH=wsz.ws_col - 2;
		HEIGHT=wsz.ws_row - 1;
	}
	else {
		WIDTH=80;
		HEIGHT=24;
	}
	screen.l = malloc(HEIGHT * sizeof(line_t));
	screen.sz = HEIGHT;
	screen.num = HEIGHT;
	if (screen.l == NULL){
		perror("allocating screen");
		cleanup(-1);
	}
	for (i=0; i<HEIGHT; i++){
		alloc_line(&(screen.l[i]));
	}
	hlines_sz= sizeof(hlines) -1;
	vlines_sz= sizeof(vlines) -1;
	corners_sz = sizeof(corners) -1;
	stmarks_sz = sizeof(st_marks) - 1;
	endmarks_sz = sizeof(st_marks) - 1;
	reset_styles();
	cutbuf.sz = 0;
	cutbuf.l = NULL;
	cutbuf.num = 0;
	
	undo = NULL;
	undo_sz = 0;
	undo_cur = -2;
	undo_lst = -2;
}

void find_nonblank_rect(int *x1, int *y1, int *x2, int *y2){

	int i, j;
	int first;
	*x1= WIDTH; /** FIXME: replace with num_cols **/
	*y1 = screen.num;
	*x2 = *y2 = 0; 

	for (i=0; i<screen.num; i++){
		if (screen.l[i].lst < 0)
			continue;
		*y2 = i;
		if (i < *y1)
			*y1 = i;
		j = 0;
		while((j <= screen.l[i].lst)  && _isblank(first=screen.l[i].s[j]))
			j++;
		if (j < *x1)
			*x1 = j;
		j = screen.l[i].lst;
		while(_isblank(screen.l[i].s[j]))
			j--;
		if (j > *x2)
			*x2 = j;
	}
}

void crop_to_rect(int x1, int y1, int x2, int y2){
	int i;

	for (i=0; i<= y2-y1; i ++){
		ensure_line_length(&(screen.l[i]), screen.l[i+y1].lst+1);
		sprintf(screen.l[i].s, "%s", screen.l[i+y1].s + x1);
		screen.l[i].lst = x2 - x1;
		screen.l[i].s[screen.l[i].lst + 1] = '\0';
	} 
	while (i< HEIGHT){
		screen.l[i].lst = -1;
		screen.l[i].s[0]= '\0';
		i ++;
	}
}

void crop_to_nonblank(){
	int x1, x2, y1, y2;
	find_nonblank_rect(&x1, &y1, &x2, &y2);
#ifdef DEBUG
	fprintf(stderr, "crop rectangle: (%d, %d)-(%d, %d)\n", x1, y1, x2, y2);
#endif
	copy_lines_to_ring(0, y2, PRV_STATE);
	crop_to_rect(x1, y1, x2, y2);
	copy_lines_to_ring(0, y2, NEW_STATE);
	modified=1;
	redraw();
}