/*
 *	$Id$
 */

#include  <stdio.h>
#include  <unistd.h>	/* read */
#include  <stdlib.h>	/* malloc */
#include  <string.h>
#include  <sys/stat.h>
#include  <fcntl.h>	/* open */
#include  <X11/Xlib.h>
#include  <X11/Xutil.h>


#ifndef  SYSCONFDIR
#define  SYSCONFDIR  "/usr/local/etc"
#endif

#define  MENU_FILE  SYSCONFDIR "/mlterm/menu-simple"

#define  FONT_NAME  "-*-fixed-*-*-*--12-*-*-*-*-*-iso8859-1"


typedef struct entry
{
	char *  name ;
	char *  seq ;

} entry_t ;


/* --- static variables --- */

static Display *  disp ;
static Window  win ;
static GC  gc1 ;
static GC  gc2 ;
static XFontStruct *  xfont ;
static entry_t  entries[124] ;
static u_int  n_ent ;
static int  cur_ent = -1 ;


/* --- static functions --- */

static char *
get_value(
	char *  dev ,
	char *  key
	)
{
	int count ;
	char ret[1024] ;
	char c ;

	if( dev)
	{
		printf("\x1b]5381;%s:%s\x07", dev, key) ;
	}
	else
        {
		printf("\x1b]5381;%s\x07", key) ;
	}
	
	fflush(stdout) ;

	for( count = 0 ; count < 1024 ; count++)
	{
		if( read(STDIN_FILENO, &c, 1) == 1)
		{
			if( c != '\n')
			{
				ret[count] = c ;
			}
			else
			{
				ret[count] = '\0' ;

				if( count < 2 + strlen(key) || strcmp(ret, "#error") == 0)
				{
					return  NULL ;
				}

				/*
				 * #key=value
				 */
				return  strdup(ret + 2 + strlen(key)) ;
			}
		}
		else
		{
			return  NULL ;
		}
	}

	return  NULL ;
}

static int
open_menu_file(void)
{
	char *  menu_file ;
	
	if( getenv( "HOME") && ( menu_file = malloc( strlen( getenv( "HOME")) + 13 + 1)))
	{
		int  fd ;
		
		sprintf( menu_file , "%s/.mlterm/menu" , getenv( "HOME")) ;
		
		fd = open( menu_file , O_RDONLY , 0600) ;

		free( menu_file) ;

		if( fd >= 0)
		{
			return  fd ;
		}
	}

	return  open( MENU_FILE , O_RDONLY , 0600) ;
}

static int
init_entries(
	u_int *  cols ,
	u_int *  rows
	)
{
	int  fd ;
	struct stat  st ;
	char *  buf ;
	char *  line ;
	char *  p ;

	/*
	 * XXX Use mmap instead of open/read/close.
	 */
	 
	if( ( fd = open_menu_file()) == -1)
	{
		return  0 ;
	}
	
	fstat( fd , &st) ;

	if( ( buf = malloc( st.st_size + 1)) == NULL)
	{
		return  0 ;
	}

	read( fd , buf , st.st_size) ;
	buf[st.st_size] = '\0' ;

	close( fd) ;

	*cols = 0 ;
	while( n_ent < 124 && buf)
	{
		char *  name ;
		char *  seq ;

		line = buf ;
		
		if( ( p = strchr( line , '\n')))
		{
			*(p ++) = '\0' ;
		}

		buf = p ;
		p = line ;

		/* Ignore leading white space and tab. */
		while( *p == ' ' || *p == '\t')
		{
			p ++ ;
		}
		
		if( *p == '#')
		{
			continue ;
		}

		name = p ;

		/*
		 * '\t' is the separator of name and seq.
		 */
		while(1)
		{
			if( *p == '\0')
			{
				seq = "" ;
				
				goto  end ;
			}
			else if( *p != '\t' && *p != '\n')
			{
				p ++ ;
			}
			else
			{
				*(p ++) = '\0' ;
				break ;
			}
		}

		/* Ignore tab(separator) and trailing white space. */
		while( *p == ' ' || *p == '\t')
		{
			p ++ ;
		}
		seq = p ;

		while( *p != ' ' && *p != '\t' && *p != '\n' && *p != '\0')
		{
			p ++ ;
		}
		*p = '\0' ;

	end:
		if( strcmp( name , "pty_list") == 0 && *seq == '\0')
		{
			char *  pty_list ;
			char *  pty ;
			int  is_active ;
			
			if ((pty_list = get_value(NULL, "pty_list")) == NULL)
			{
				return 1;
			}

			while (pty_list)
			{
				pty = pty_list;
				pty_list = strchr(pty_list, ':');
				
				if (pty_list)
				{
					*pty_list = '\0';
				}
				else
				{
					break;
				}
				
				if (*(pty_list + 1) == '1')
				{
					is_active = 1;
				}
				else
				{
					is_active = 0;
				}
				
				if( ( pty_list = strchr(pty_list + 1, ';')))
				{
					pty_list++;
				}

				if ((name = get_value(pty, "pty_name")) == NULL)
				{
					name = pty;
				}
				
				if (strncmp(name, "/dev/", 5) == 0)
				{
					name += 5;
				}

				seq = malloc(strlen(pty) + 12);
				sprintf(seq, "select_pty=%s", pty);

				entries[n_ent].name = name ;
				entries[n_ent].seq = seq ;

				if( *cols < strlen( entries[n_ent].name))
				{
					*cols = strlen( entries[n_ent].name) ;
				}

				if( ++ n_ent >= 124)
				{
					break ;
				}
			}
		}
		else
		{
			if( *name != '\0')
			{
				entries[n_ent].name = strdup( name) ;
				entries[n_ent].seq = strdup( seq) ;

				if( *cols < strlen( entries[n_ent].name))
				{
					*cols = strlen( entries[n_ent].name) ;
				}
				
				if( ++ n_ent >= 124)
				{
					break ;
				}
			}
		}
	}

	if( n_ent == 0)
	{
		return  0 ;
	}

	*rows = n_ent ;

	return  1 ;
}

static int
init(void)
{
	int  x ;
	int  y ;
	u_int  cols ;
	u_int  rows ;
	u_int  width ;
	u_int  height ;

	if( ! init_entries( &cols , &rows))
	{
		return  0 ;
	}

	if( ( disp = XOpenDisplay(NULL)) == NULL)
	{
		return  0 ;
	}

	if( ( xfont = XLoadQueryFont( disp , FONT_NAME)) == NULL)
	{
		return  0 ;
	}

	width = xfont->max_bounds.width * cols ;
	height = (xfont->ascent + xfont->descent) * rows ;

	{
		Window  root ;
		Window  child ;
		int  root_x ;
		int  root_y ;
		u_int  mask ;

		XQueryPointer( disp , DefaultRootWindow(disp) , &root , &child , &root_x , &root_y ,
			&x , &y , &mask) ;
	}

	if( ! ( win = XCreateSimpleWindow( disp , DefaultRootWindow(disp) , x , y ,
			width , height , 1 ,
			BlackPixel( disp , DefaultScreen(disp)) ,
			WhitePixel( disp , DefaultScreen(disp))) ) )
	{
		return  0 ;
	}

	{
		XSetWindowAttributes  attr ;

		attr.override_redirect = True ;
		XChangeWindowAttributes( disp , win , CWOverrideRedirect , &attr) ;
	}

	{
		XGCValues  gc_value ;
		
		gc_value.graphics_exposures = 0 ;
		gc1 = XCreateGC( disp , win , GCGraphicsExposures , &gc_value) ;
		gc2 = XCreateGC( disp , win , GCGraphicsExposures , &gc_value) ;
	}

	XSetForeground( disp , gc1 , BlackPixel( disp , DefaultScreen(disp))) ;
	XSetBackground( disp , gc1 , WhitePixel( disp , DefaultScreen(disp))) ;
	XSetFont( disp , gc1 , xfont->fid) ;

	XSetForeground( disp , gc2 , WhitePixel( disp , DefaultScreen(disp))) ;
	XSetBackground( disp , gc2 , BlackPixel( disp , DefaultScreen(disp))) ;
	XSetFont( disp , gc2 , xfont->fid) ;
	
	XSelectInput( disp , win ,
		EnterWindowMask | LeaveWindowMask | PointerMotionMask | ButtonPressMask) ;

	if( XGrabPointer( disp , DefaultRootWindow(disp) , True , ButtonPressMask , GrabModeAsync ,
		GrabModeAsync , None , None , CurrentTime) != GrabSuccess)
	{
		return  0 ;
	}

	XMapWindow( disp , win) ;
	XClearWindow( disp , win) ;

	return  1 ;
}

static int
update_screen(
	int  n ,
	int  reverse
	)
{
	int  start ;
	int  end ;
	int  count ;
	GC  gc ;
	
	if( n < 0)
	{
		/* n == -1 */
		
		start = 0 ;
		end = n_ent - 1 ;
	}
	else if( n >= n_ent)
	{
		return  0 ;
	}
	else
	{
		start = n ;
		end = n ;
	}

	if( reverse)
	{
		gc = gc2 ;
	}
	else
	{
		gc = gc1 ;
	}

	for( count = start ; count <= end ; count ++)
	{
		XDrawImageString( disp , win , gc , 0 ,
			count * (xfont->ascent + xfont->descent) + xfont->ascent ,
			entries[count].name , strlen(entries[count].name)) ;
	}

	return  1 ;
}

static int
mouse_motion(
	int  y
	)
{
	int  entry ;

	entry = y / (xfont->ascent + xfont->descent) ;
	if( entry != cur_ent)
	{
		if( cur_ent >= 0)
		{
			update_screen( cur_ent , 0) ;
		}

		update_screen( cur_ent = entry , 1) ;
	}

	return  1 ;
}

static int
mouse_pressed(void)
{
	printf( "\x1b]5379;%s\x07" , entries[cur_ent].seq) ;
	fflush( stdout) ;

	return  1 ;
}

static int
event_loop(void)
{
	XEvent  ev ;

	while( 1)
	{
		XNextEvent( disp , &ev) ;
		if( ev.xany.window != win)
		{
			break ;
		}

		if( ev.type == EnterNotify)
		{
			mouse_motion( ev.xcrossing.y - 1) ;
		}
		else if( ev.type == MotionNotify)
		{
			mouse_motion( ev.xcrossing.y - 1) ;
		}
		else if( ev.type == ButtonPress)
		{
			mouse_pressed() ;

			break ;
		}
		else if( ev.type == LeaveNotify)
		{
			update_screen( cur_ent , 0) ;
			cur_ent = -1 ;
		}
	}

	return  1 ;
}


/* --- global functions --- */

int
main(
	int  argc ,
	char **  argv
	)
{
	if( ! init() || ! update_screen( -1 , 0) || ! event_loop())
	{
		return  1 ;
	}

	return  0 ;
}
