/*
 * $Id$
 *
 * Copyright (C) 2012-2014 Sanel Zukan
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include <FL/Fl.H>
#include <FL/Fl_Button.H>
#include <edelib/Debug.h>
#include <edelib/Netwm.h>

#include "TaskButton.h"
#include "Taskbar.h"

#define DEFAULT_CHILD_W 175
#define DEFAULT_SPACING 5

EDELIB_NS_USING(netwm_callback_add)
EDELIB_NS_USING(netwm_callback_remove)
EDELIB_NS_USING(netwm_window_get_all_mapped)
EDELIB_NS_USING(netwm_window_is_manageable)
EDELIB_NS_USING(netwm_window_get_workspace)
EDELIB_NS_USING(netwm_window_get_active)
EDELIB_NS_USING(netwm_window_set_active)
EDELIB_NS_USING(netwm_window_set_state)
EDELIB_NS_USING(netwm_workspace_get_current)
EDELIB_NS_USING(wm_window_set_state)
EDELIB_NS_USING(wm_window_get_state)
EDELIB_NS_USING(NETWM_CHANGED_ACTIVE_WINDOW)
EDELIB_NS_USING(NETWM_CHANGED_CURRENT_WORKSPACE)
EDELIB_NS_USING(NETWM_CHANGED_WINDOW_LIST)
EDELIB_NS_USING(NETWM_CHANGED_WINDOW_NAME)
EDELIB_NS_USING(NETWM_CHANGED_WINDOW_ICON)
EDELIB_NS_USING(NETWM_CHANGED_WINDOW_DESKTOP)
EDELIB_NS_USING(WM_WINDOW_STATE_ICONIC)
EDELIB_NS_USING(NETWM_STATE_ACTION_TOGGLE)
EDELIB_NS_USING(NETWM_STATE_HIDDEN)

static void button_cb(TaskButton *b, void *t) {
	Taskbar *tt = (Taskbar*)t;

	if(Fl::event_button() == FL_RIGHT_MOUSE)
		b->display_menu();
	else 
		tt->activate_window(b);
}

static void net_event_cb(int action, Window xid, void *data) {
	E_RETURN_IF_FAIL(data != NULL);
	
	/* this is a message, so property is not changed and netwm_window_get_active() must be called */
	if(action == NETWM_CHANGED_ACTIVE_WINDOW) {
		Taskbar *tt = (Taskbar*)data;
		tt->update_active_button();
		return;
	}
	
	if(action == NETWM_CHANGED_CURRENT_WORKSPACE) {
		Taskbar *tt = (Taskbar*)data;
		tt->update_workspace_change();
		return;
	}

	if(action == NETWM_CHANGED_WINDOW_LIST) {
		Taskbar *tt = (Taskbar*)data;
		tt->update_task_buttons();
		return;
	}

	if(action == NETWM_CHANGED_WINDOW_NAME) {
		Taskbar *tt = (Taskbar*)data;
		tt->update_child_title(xid);
		return;
	}

	if(action == NETWM_CHANGED_WINDOW_ICON) {
		Taskbar *tt = (Taskbar*)data;
		tt->update_child_icon(xid);
		return;
	}

	if(action == NETWM_CHANGED_WINDOW_DESKTOP) {
		Taskbar *tt = (Taskbar*)data;
		tt->update_child_workspace(xid);
		return;
	}
}

Taskbar::Taskbar() : WidgetGroup(0, 0, 40, 25), curr_active(NULL), prev_active(NULL) {
	end();
	fixed_layout = false;
	ignore_workspace_value = false;

	current_workspace = netwm_workspace_get_current();

	update_task_buttons();
	netwm_callback_add(net_event_cb, this);
}

Taskbar::~Taskbar() {
	netwm_callback_remove(net_event_cb);
}

void Taskbar::configure(Resource *res) {
	E_RETURN_IF_FAIL(res != NULL);
	
	res->get("Taskbar", "fixed_layout", fixed_layout, false);
	res->get("Taskbar", "all_desktops", ignore_workspace_value, false);

	if(visible()) update_task_buttons();
}

void Taskbar::update_task_buttons(void) {
	Window *wins;
	int ws, nwins = netwm_window_get_all_mapped(&wins);

	if(nwins < 1) {
		if(children() > 0) clear();
		return;
	}
	
	TaskButton *b;
	bool need_full_redraw = false;
	
	for(int i = 0, found; i < children(); i++) {
		found = 0;
		b = (TaskButton*)child(i);

		for(int j = 0; j < nwins; j++) {
			if(b->get_window_xid() == wins[j]) {
				/* assure this window is on current workspace */
				found = 1;
				break;
			} 
		}

		if(!found) {
			/* FLTK since 1.3 optimized Fl_Group::remove() */
#if (FL_MAJOR_VERSION >= 1) && (FL_MINOR_VERSION >= 3)
			remove(i);
#else
			remove(b);
#endif
			/* Fl_Group does not call delete on remove() */
			delete b;
			need_full_redraw = true;
		} 
	}

	/* now see which one needs to be created */
	for(int i = 0, found; i < nwins; i++) {
		found = 0;

		for(int j = 0; j < children(); j++) {
			b = (TaskButton*)child(j);

			if(b->get_window_xid() == wins[i]) {
				found = 1;
				break;
			}
		}

		if(found || !netwm_window_is_manageable(wins[i]))
			continue;

		Window transient_prop_win = None;

		/*
		 * see if it has WM_TRANSIENT_FOR hint set; transient_prop_win would point to parent window, but
		 * parent should not be root window for this screen
		 */
		if(XGetTransientForHint(fl_display, wins[i], &transient_prop_win) 
		   && (transient_prop_win != None)
		   && (transient_prop_win != RootWindow(fl_display, fl_screen)))
		{
			continue;
		}
		
		/* create button */
		ws = netwm_window_get_workspace(wins[i]);
		
		b = new TaskButton(0, 0, DEFAULT_CHILD_W, 25);
		b->set_window_xid(wins[i]);
		b->update_title_from_xid();
		b->update_image_from_xid();
		b->set_workspace(ws);
		/* mark it hidden by default */
		b->hide();

		/* catch the name changes */
		XSelectInput(fl_display, wins[i], PropertyChangeMask | StructureNotifyMask);
		b->callback((Fl_Callback*)button_cb, this);

		/* add it to our list */
		add(b);

		if(visible_on_current_workspace(ws)) {
			//need_full_redraw = true;
			b->show();
		}
	}

	XFree(wins);
	layout_children();
	update_active_button(!need_full_redraw);

	if(need_full_redraw) panel_redraw();
}

void Taskbar::update_workspace_change(void) {
	if(!children()) return;
	current_workspace = netwm_workspace_get_current();
	
	TaskButton *b;
	for(int i = 0; i < children(); i++) {
		b = (TaskButton*)child(i);
		visible_on_current_workspace(b->get_workspace()) ? b->show() : b->hide();
	}	

	layout_children();
	redraw();
}

void Taskbar::resize(int X, int Y, int W, int H) {
	Fl_Widget::resize(X, Y, W, H);
	layout_children();
}

void Taskbar::layout_children(void) {
	if(!children()) return;

	Fl_Widget *o;
	int X, Y, W, child_h, reduce = 0, sz = 0, all_buttons_w = 0;

	X = x() + Fl::box_dx(box());
	Y = y() + Fl::box_dy(box());
	W = w() - Fl::box_dw(box());
	child_h = parent()->h() - 10; /* same sizes as in panel */

	for(int i = 0; i < children(); i++) {
		o = child(i);
		if(!o->visible()) continue;

		sz++;

		/* resize every child to default size so we can calculate total length */
		o->resize(o->x(), o->y(), (fixed_layout ? DEFAULT_CHILD_W : W), child_h);
		all_buttons_w += o->w();

		/* do not include ending spaces */
		if(i != children() - 1)
			all_buttons_w += DEFAULT_SPACING;
	}

	/* find reduction size */
	if(all_buttons_w > W)
		reduce = (all_buttons_w - W) / sz;

	/* now, position each child and resize it if needed */
	for(int i = 0; i < children(); i++) {
		o = child(i);
		if(!o->visible()) continue;

		/*
		 * I'm putting -1 here to leave a small space at the end of group; this will also
		 * make children adaptive to not overflow group in case too many of them was created
		 */
		o->resize(X, Y, o->w() - reduce - 1, child_h);
		X += o->w() + DEFAULT_SPACING;
	}
}

void Taskbar::update_active_button(bool do_redraw, int xid) {
	if(!children())
		return;

	if(xid == -1) {
		xid = netwm_window_get_active();
		/* TODO: make sure panel does not get 'active', or all buttons will be FL_UP_BOX */
	}

	TaskButton *o;
	for(int i = 0; i < children(); i++) {
		o = (TaskButton*)child(i);
		if(!o->visible()) continue;

		if(o->get_window_xid() == (Window)xid) {
			o->box(FL_DOWN_BOX);
			curr_active = o;
		} else {
			o->box(FL_UP_BOX);
		}
	}

	if(do_redraw) redraw();
}

void Taskbar::activate_window(TaskButton *b) {
	E_RETURN_IF_FAIL(b != NULL);

	Window xid = b->get_window_xid();

	/* if clicked on activated button, it will be minimized, then next one will be activated */
	if(b == curr_active) {
		if(wm_window_get_state(xid) != WM_WINDOW_STATE_ICONIC) {
			/* minimize if not so */
			wm_window_set_state(xid, WM_WINDOW_STATE_ICONIC);

			if(prev_active && 
			   prev_active != b && 
			   prev_active->get_workspace() == b->get_workspace() &&
			   wm_window_get_state(prev_active->get_window_xid()) != WM_WINDOW_STATE_ICONIC) 
			{
				xid = prev_active->get_window_xid();
				b   = prev_active;
			} else {
				return;
			}
		}
	} 

	/* active or restore minimized */
	netwm_window_set_active(xid, 1);
	update_active_button(false, xid);

	prev_active = curr_active;
	curr_active = b;
}

void Taskbar::update_child_title(Window xid) {
	E_RETURN_IF_FAIL(xid >= 0);

	TaskButton *o;
	for(int i = 0; i < children(); i++) {
		o = (TaskButton*)child(i);

		if(o->visible() && o->get_window_xid() == xid) {
			o->update_title_from_xid();
			break;
		}
	}
}

void Taskbar::update_child_icon(Window xid) {
	E_RETURN_IF_FAIL(xid >= 0);

	TaskButton *o;
	for(int i = 0; i < children(); i++) {
		o = (TaskButton*)child(i);

		if(o->visible() && o->get_window_xid() == xid) {
			o->update_image_from_xid();
			o->redraw();
			break;
		}
	}
}

void Taskbar::update_child_workspace(Window xid) {
	if(!children()) return;

	TaskButton *o;
	for(int i = 0; i < children(); i++) {
		o = (TaskButton*)child(i);

		if(o->get_window_xid() == xid) {
			int ws = netwm_window_get_workspace(xid);
			o->set_workspace(ws);
			visible_on_current_workspace(ws) ? o->show() : o->hide();
			break;
		}
	}

	layout_children();
	redraw();
}

void Taskbar::panel_redraw(void) {
	parent()->redraw();
}

void Taskbar::try_dnd(TaskButton *b, int xpos, int ypos) {
	if(!children()) return;
	
	/*
	 * try to find position of incoming child, as the child, when is moved
	 * forward, has to go on 'target_pos + 1' instead only 'target_pos'
	 */
	int child_pos = find(b);
	E_RETURN_IF_FAIL(child_pos != children());

	TaskButton *o;
	for(int i = 0; i < children(); i++) {
		o = (TaskButton*)child(i);
		if(o == b) continue;

		if((xpos > b->x() && xpos < b->x() + b->w()) &&
		   (ypos > b->y() && ypos < b->y() + b->h()))
		{
			/* not moved outside child range */
			return;
		}

		if((xpos > o->x() && xpos < o->x() + o->w()) &&
		   (ypos > o->y() && ypos < o->y() + o->h())) 
		{
			/* Try to find widget position in array. Widgets in array are not sorted in any order. */
			int pos = find(o);
			if(pos == children()) {
				/* not found, proceed with the search */
				continue;
			}
		
			/* increase just in case target position is in front */
			pos += (pos > child_pos) ? 1 : 0;
			insert(*b, pos);
			break;
		}
	}
	
	layout_children();
	redraw();
}

EDE_PANEL_APPLET_EXPORT (
 Taskbar, 
 EDE_PANEL_APPLET_OPTION_ALIGN_LEFT | EDE_PANEL_APPLET_OPTION_RESIZABLE_H,
 "Taskbar",
 "0.1",
 "empty",
 "Sanel Zukan"
)
