#include "global.hh"

#include <stdlib.h>
#include <unistd.h>
#include <X11/keysym.h>

#include "commands.hh"
#include "config.hh"
#include "log.hh"
#include "events.hh"
#include "pager.hh"
#include "menus.hh"
#include "windows.hh"

CmdQueue cq;

//********************************************************************

CmdItem::CmdItem ()
{
	next = NULL;
}

CmdQueue::CmdQueue ()
{
	dont_exec = 0;
	first = last = NULL;
}

int
CmdQueue::add (CmdItem *t)
{
	// adds "t" to the end of queue
	if (last) {
		last->next = t;
		last = t;
	}
	else
		first = last = t;
}

CmdQueue::~CmdQueue ()
{
	CmdItem *i;
	while (first)
	{
		 i = first->next;
		delete first;
		first = i;
	};
}

int
CmdQueue::exec ()
{
	// execs and removes one queued command
	if (dont_exec)
		return 0;

	dont_exec = 1;
	Cmd *i;
	
	if (first) {
		// have to be in this order, otherwise the resize command would
		// cycle [resize is WinCmd, so it tries to find a target, which uses
		// event handler, and event handler calls cq.exec - so in the
		// time when we call i->exec, we must have set first so that it
		// points to i->next); new value for first must be set before
		// command is executed, because the command can change the list
		// (e.g. if command is a CPopup and user select some command from
		// menu, that is added to CmdQueue)]
		i = (Cmd *) first;

		first = first->next;
		if (!first) last = NULL;

		i->exec ();
		delete i;
	};
		
	dont_exec = 0;
}

//********************************************************************

int
LineParser::cmd (Cmd *&cmd)
{
	skip_space ();
	// Longer commands have to precede shorter one (if they have a common beginning), otherwise
	// it won't work correctly

	/**/ if (match_space ("Nop"))		cmd = new CNop;
	else if (match_space ("ToggleLock"))	cmd = new CToggleLock;
	else if (match_space ("Beep"))		cmd = new CBeep;
	else if (match_space ("Restart"))	cmd = new CRestart;
	else if (match_space ("Quit"))		cmd = new CQuit;
	else if (match_space ("Popup"))		cmd = new CPopup;
	else if (match_space ("SeqSwitch"))	cmd = new CSeqSwitch;
	else if (match_space ("MoveCursor"))	cmd = new CMoveCursor;
	else if (match ("Scroll")) {
		/**/ if (match_space (".swap"))	cmd = new CScroll (ST_SWAP);
		else if (match_space (".wrap"))	cmd = new CScroll (ST_WRAP);
		else				cmd = new CScroll (ST_ORDINARY);
	}
	else if (match_space ("Close"))		cmd = new WinCmd (CW_CLOSE);
	else if (match_space ("Destroy"))	cmd = new WinCmd (CW_DESTROY);
	else if (match_space ("Move"))		cmd = new WinCmd (CW_MOVE);
	else if (match_space ("Resize"))	cmd = new WinCmd (CW_RESIZE);
	else if (match_space ("RaiseMove"))	cmd = new WinCmd (CW_RAISE_MOVE);
	else if (match_space ("Raise"))		cmd = new WinCmd (CW_RAISE);
	else if (match_space ("Lower"))		cmd = new WinCmd (CW_LOWER);
	else if (match_space ("Stick"))		cmd = new WinCmd (CW_STICK_WINDOW);
	else if (match_space ("Stick.desktop"))	cmd = new WinCmd (CW_STICK_DESK);
	else if (match_space ("Hide"))		cmd = new WinCmd (CW_HIDE);
	else if (match_space ("Center"))	cmd = new WinCmd (CW_CENTER);
	else if (match_space ("Maximize"))	cmd = new WinCmd (CW_MAX);
	else if (match_space ("VMax"))		cmd = new WinCmd (CW_VMAX);
	else if (match_space ("HMax"))		cmd = new WinCmd (CW_HMAX);
	
	else if (match_space ("pager.raise"))	cmd = new CRaisePager;
	else if (match ("log")) {
		/**/ if (match_space (".raise"))	cmd = new CLogCmd (CL_RAISE);
		else if (match_space (".clear"))	cmd = new CLogCmd (CL_CLEAR);
		else if (match_space (".stop"))		cmd = new CLogCmd (CL_STOP);
		else if (match_space (".cont"))		cmd = new CLogCmd (CL_CONT);
	}
	else if (match_space ("Exec"))		cmd = new CExec;
	else {
		// everything not preceeded by func name is (automatically) an exec
		// but there is also explicit "Exec" (above) so that one could e.g.
		// call program called "Nop" by "Exec Nop"
		cmd = new CExec;
	}
}

WinCmd::WinCmd (SWindow *isw, int icmd)
{
	sw = isw;
	cmd = icmd;
}

WinCmd::WinCmd (int icmd)
{
	sw = NULL;
	cmd = icmd;
}

int
WinCmd::exec_case ()
{
	switch (cmd)
	{
	case CW_CLOSE:		sw->close (); break;
	case CW_DESTROY:	sw->destroy (); break;
	case CW_MOVE:
		sw->switch_to_desk (); // if the win was selected on pager
		sw->do_move ();
		break;
	case CW_RAISE_MOVE: sw->raise (); sw->do_move (); break;
	case CW_RESIZE:
		//    sw->switch_to_desk (); // if the win was selected on pager
		sw->do_resize ();
		break;
	case CW_CENTER:		sw->center (); sw->sync_to_xserver (); break;
	case CW_MAX:		sw->max_toggle (); break;
	case CW_HIDE:		sw->hide (); break;
	case CW_STICK_WINDOW:	sw->stick_toggle (); break;
	case CW_STICK_DESK:	sw->stick_toggle (); break;
//	#warning in (already initiated) scroll_mode - if sole window on current desk - stick it (even when not under cursor)
	case CW_RAISE:		sw->raise (); break;
	case CW_LOWER:		sw->lower (); break;
	case CW_FOCUS:		sw->focus (); break;
	case CW_SWITCH_TO:	sw->switch_to (); sw->raise (); break;
	};

	if (cmd == CW_MAX  ||  cmd == CW_CENTER)
		cq.add (new WinCmd (sw, CW_FOCUS));
}

int
WinCmd::exec ()
{
	if (sw) {
		// pre-set WinCmd on specific window
		exec_case ();
		sw = NULL;
	}
	else
	{
		// interactive WinCmd
		// find the target for window command
		sw = win_list.focus;

		/*
		if (sw == NULL) log_window.dbg ("WinCmd (cmd=%d) ::find_target <none>", cmd);
		else log_window.dbg ("WinCmd (cmd=%d) -> %s", cmd, sw->name);
		*/

		if (sw == NULL && swm_mode == MODE_RUN && grab_setup (root_window)) {
			// no window focused, do the selection
			swm_mode = MODE_SELECT_WIN;
			log_window.pr ("select a window...");

			while (swm_mode == MODE_SELECT_WIN) {
				event->next ();
				
				switch (event->x.type)
				{
				case ButtonPress: break;
				case KeyPress:
					pointer_warping(); // warping the pointer
					break;
				case KeyRelease:
					if (event->keysym == XK_Return || event->keysym == XK_space) {
						sw = win_list.focus;
						swm_mode = MODE_RUN;
					}
					else if (event->keysym == XK_Escape) {
						swm_mode = MODE_RUN;
					}
					break;
				case ButtonRelease:
					sw = win_list.focus;
					swm_mode = MODE_RUN;
					break;
				};

				if (!(event->handled))
					event->handle ();
			}

			grab_cancel ();
			if (sw) log_window.pr ("...OK, got `%s'", sw->name); else log_window.pr ("...aborted");
			
			swm_mode = MODE_RUN;
		}
	
		if (sw) {
			exec_case ();
			sw = NULL;
		}
	}
}

int
CBeep::exec () {
	XBell (dpy, screen);
}

int
CQuit::exec () {
	swm_mode = MODE_DONE;
	swm_done ();
}

CRestart::CRestart () {
	lps->stripcpy (cmd);
}

int
CRestart::exec () {
	swm_mode = MODE_RESTART;
	restart_cmd = cmd;
	swm_done ();
}

CExec::CExec () {
	lps->stripcpy (cmd);
	
	// without " &" on the end, swm will hang occasionaly when creating
	// pipe for %%shell%% inline in config (one must run a program with
	// it first, e.g. press 't' to run xterm)

	// also, without " &": if you run a program with swm (e.g. xterm
	// again) and then kill swm, xterm will also crash; with " &",
	// this is no longer true
#ifdef NOTE_WARN
#warning DONT_KNOW_WHY
#endif

	if (cmd[strlen(cmd)-1] != '&') {
		char *ncmd = new char[strlen(cmd)+2];
		sprintf (ncmd, "%s &", cmd);
		delete cmd;
		cmd = ncmd;
	}
}

int
CExec::exec ()
{
	log_window.pr ("> %s", cmd);

	if (fork() == 0) {
		// get here in child process
		execl ("/bin/sh", "/bin/sh", "-c", cmd, NULL);

		// get here if execl fails
		exit (127);
	}
	// get here in parent process
}


CPopup::CPopup ()
{
	char *name;
	lps->stripcpy2 (name);
	menu_list.find (menu, name);
	
	if (!menu)
		lps->error ("popup \"%s\" not defined (yet)\n", name);

	delete name;
}

int
CPopup::exec ()
{
	if (menu)	menu->exec ();
}


CScroll::CScroll (int itype)
{
	type = itype;
	lps->num (dx);
 	lps->num (dy);
	restrict (dx, -dsk.cols+1, dsk.cols-1);
	restrict (dy, -dsk.rows+1, dsk.rows-1);
}

int
CScroll::exec ()
{
	dsk.scroll(dx, dy, type);
}


CMoveCursor::CMoveCursor ()
{
	lps->num (dx);
	lps->num (dy);
}

int
CMoveCursor::exec ()
{
	ppos.warp_by (dx, dy);
}

int
CRaisePager::exec ()
{
	// panel->raise ()
	win_list.raise (spager);
}


CLogCmd::CLogCmd (int icmd)
{
	cmd=icmd;
}

int
CLogCmd::exec ()
{
	switch (cmd) {
	case CL_RAISE:	win_list.raise (log_swindow); break;
	case CL_CLEAR:	log_window.clear (); break;
	case CL_STOP:	log_window.stop (); break;
	case CL_CONT:	log_window.cont (); break;
	}
}

int
CSeqSwitch::exec ()
{
	win_list.seq_switch ();
}

int
CNop::exec ()
{
	log_window.dbg("nop");
}

int
CToggleLock::exec ()
{
	dsk.lock = !dsk.lock;

	if (dsk.lock)
		log_window.pr("Desktop scrolling locked (constrained to virtual desktop size)");
	else
		log_window.pr("Desktop scrolling unlocked");
}

