/*
 * $Id: Splash.cpp 3500 2013-01-05 03:39:09Z karijes $
 *
 * evoke, head honcho of everything
 * Part of Equinox Desktop Environment (EDE).
 * Copyright (c) 2007-2009 EDE Authors.
 *
 * This program is licensed under terms of the 
 * GNU General Public License version 2 or newer.
 * See COPYING for details.
 */

#include <FL/Fl.H>
#include <FL/Fl_Shared_Image.H>
#include <edelib/Debug.h>
#include <edelib/Nls.h>
#include <edelib/Util.h>
#include <edelib/Directory.h>
#include <edelib/FileTest.h>
#include <edelib/Resource.h>
#include <edelib/Run.h>
#include <edelib/Netwm.h>

#include "Splash.h"

#define TIMEOUT_START    0.5  /* timeout when splash is first time shown (also for first client) */
#define TIMEOUT_CONTINUE 0.4  /* timeout between starting rest of the cliens */

EDELIB_NS_USING(String)
EDELIB_NS_USING(Resource)
EDELIB_NS_USING(build_filename)
EDELIB_NS_USING(file_test)
EDELIB_NS_USING(run_async)
EDELIB_NS_USING(netwm_window_set_type)
EDELIB_NS_USING(RES_SYS_ONLY)
EDELIB_NS_USING(FILE_TEST_IS_DIR)
EDELIB_NS_USING(NETWM_WINDOW_TYPE_SPLASH)

#ifndef EDEWM_HAVE_NET_SPLASH
static Splash* global_splash = NULL;

static int splash_xmessage_handler(int e) {
	if(fl_xevent->type == MapNotify) {
		XRaiseWindow(fl_display, fl_xid(global_splash));
		return 1;
	}

	if(fl_xevent->type == ConfigureNotify) {
	 	if(fl_xevent->xconfigure.event == DefaultRootWindow(fl_display)) {
			XRaiseWindow(fl_display, fl_xid(global_splash));
			return 1;
		}
	}

	return 0;
}
#endif

/* repeatedly call runner() until all clients are started then hide splash window */
static void runner_cb(void* s) {
	Splash* sp = (Splash*)s;
	sp->next_client() ? Fl::repeat_timeout(TIMEOUT_CONTINUE, runner_cb, sp) : sp->hide();
}

Splash::Splash(StartupItemList& s, String& theme, bool show_it, bool dr) : Fl_Double_Window(480, 365) {
	slist = &s;
	splash_theme = &theme;
	show_splash = show_it;
	dryrun = dr;
	icons = NULL;
	counter = 0;

	box(FL_BORDER_BOX);
}

Splash::~Splash() {
	E_DEBUG(E_STRLOC ": Cleaning splash data\n");

	/* elements of icons cleans Fl_Group */
	delete [] icons;
	Fl::remove_timeout(runner_cb);
}

/* for wm's with _NET_WM_WINDOW_TYPE_SPLASH support */
#if EDEWM_HAVE_NET_SPLASH
void Splash::show(void) {
	if(shown()) return;

	Fl_X::make_xid(this);
	netwm_window_set_type(fl_xid(this), NETWM_WINDOW_TYPE_SPLASH);
}
#endif

void Splash::run(void) {
	E_ASSERT(slist != NULL);

	if(!show_splash) {
		while(next_client_nosplash()) 
			;
		return;
	}

	fl_register_images();
	String path, splash_theme_path;

#ifdef USE_LOCAL_CONFIG
	splash_theme_path = "splash-themes/";
#else
	splash_theme_path = Resource::find_data("themes/splash-themes", RES_SYS_ONLY);
	if(splash_theme_path.empty()) {
		E_WARNING(E_STRLOC ": Unable to locate splash themes in $XDG_DATA_DIRS directories\n");
		return;
	}
#endif

	splash_theme_path += E_DIR_SEPARATOR;
	splash_theme_path += *splash_theme;

	if(!file_test(splash_theme_path.c_str(), FILE_TEST_IS_DIR)) {
		E_WARNING(E_STRLOC ": Unable to locate '%s' in '%s' theme directory\n", splash_theme->c_str(), splash_theme_path.c_str());
		return;
	}

	/* setup widgets */
	begin();
		Fl_Box* bimg = new Fl_Box(0, 0, w(), h());
		Fl_Image* splash_img = 0;

		path = build_filename(splash_theme_path.c_str(), "background.png");
		splash_img = Fl_Shared_Image::get(path.c_str());

		if(splash_img) {
			int W = splash_img->w();
			int H = splash_img->h();
			/* update window and Box sizes */
			size(W, H);
			bimg->size(W, H);

			bimg->image(splash_img);
		}

		/* place message box at the bottom with nice offset (10 px) from window borders */
		msgbox = new Fl_Box(10, h() - 25 - 10, w() - 20, 25);

		/*
		 * Setup icons positions, based on position of msgbox assuming someone 
		 * will not abuse splash to start hundrets of programs, since icons will 
		 * be placed only in horizontal order, one line, so in case their large
		 * number, some of them will go out of window borders.
		 *
		 * Icon box will be 64x64 so larger icons can fit too.
		 *
		 * This code will use Fl_Group (moving group, later, will move all icons
		 * saving me from code mess), but will not update it's w() for two reasons:
		 * icons does not use it, and will be drawn correctly, and second, setting
		 * width will initiate fltk layout engine which will mess everything up.
		 */
		Fl_Group* icon_group = new Fl_Group(10, msgbox->y() - 10 - 64, 0, 64);
		int X = icon_group->x();
		int Y = icon_group->y();

		/* offset between icons */
		int ioffset = 5;

		/* FIXME: use malloc/something instead this */
		icons = new Fl_Box*[slist->size()];

		icon_group->begin();
			int         i = 0;
			const char* imgpath;
			Fl_Image*   iconimg = 0;

			for(StartupItemListIter it = slist->begin(); it != slist->end(); ++it, ++i) {
				Fl_Box* bb = new Fl_Box(X, Y, 64, 64);

				path = build_filename(splash_theme_path.c_str(), (*it)->icon.c_str());
				imgpath = path.c_str();
				iconimg = Fl_Shared_Image::get(imgpath);

				if(!iconimg) {
					bb->label(_("No image"));
					bb->align(FL_ALIGN_INSIDE | FL_ALIGN_WRAP);
				} else 
					bb->image(iconimg);

				bb->hide();

				X += bb->w() + ioffset;
				icons[i] = bb;
			}
		icon_group->end();

		/* see X as width of all icons */
		int gx = w()/2 - X/2;
		/* gx can be negative */
		gx = (gx > 10) ? gx : 10;
		icon_group->position(gx, Y);
	end();

	clear_border();

	/* If set_override() is used, message boxes will be popped behind splash. Using it or not ??? */
	set_override();

	/* make sure window is centered */
	int sw = DisplayWidth(fl_display, fl_screen);
	int sh = DisplayHeight(fl_display, fl_screen);
	position(sw / 2 - w() / 2, sh / 2 - h() / 2);

	show();
	Fl::add_timeout(TIMEOUT_START, runner_cb, this);

	/* to keep splash at the top */
#ifndef EDEWM_HAVE_NET_SPLASH
	global_splash = this;
	XSelectInput(fl_display, RootWindow(fl_display, fl_screen), SubstructureNotifyMask);
	Fl::add_handler(splash_xmessage_handler);
#endif
	
	while(shown())
		Fl::wait();

#ifndef EDEWM_HAVE_NET_SPLASH
	Fl::remove_handler(splash_xmessage_handler);
#endif
}

/* called when splash option is on */
bool Splash::next_client(void) {
	E_RETURN_VAL_IF_FAIL(slist->size() > 0, false);

	if(counter == 0)
		slist_it = slist->begin();

	if(slist_it == slist->end()) {
		counter = 0;
		return false;
	}

	E_ASSERT(counter < slist->size() && "Internal error; 'counter' out of bounds");

	const char *msg, *cmd;
	msg = (*slist_it)->description.c_str();
	cmd = (*slist_it)->exec.c_str();

	icons[counter]->show();
	msgbox->label(msg);
	redraw();

	/* run command */
	if(!dryrun)
		run_async(cmd);

	++slist_it;
	++counter;
	return true;
}

/* called when splash option is off */
bool Splash::next_client_nosplash(void) {
	E_RETURN_VAL_IF_FAIL(slist->size() > 0, false);

	if(counter == 0)
		slist_it = slist->begin();

	if(slist_it == slist->end()) {
		counter = 0;
		return false;
	}

	E_ASSERT(counter < slist->size() && "Internal error; 'counter' out of bounds");

	const char* msg = (*slist_it)->description.c_str();
	const char* cmd = (*slist_it)->exec.c_str();

	E_DEBUG(E_STRLOC ": Starting '%s'...\n", msg);

	/* run command */
	if(!dryrun)
		run_async(cmd);

	++slist_it;
	++counter;
	return true;
}
