#include "Image.hh"
#include "bbsmount.hh"
#include "tooltip.hh"
#include "i18n.hh"

#define MAX_EXTENT	max_logical_extent
//#define MAX_EXTENT	max_ink_extent

int
timeval_subtract (const struct timeval *time1, const struct timeval *time0, struct timeval *result) {
    result->tv_sec = time1->tv_sec - time0->tv_sec;
    result->tv_usec = time1->tv_usec - time0->tv_usec;
    if (result->tv_usec < 0) {
	result->tv_sec -= 1 - result->tv_usec / 1000000;
	result->tv_usec = 1000000 - (-result->tv_usec) % 1000000;
    }
    return (result->tv_sec < 0 || (result->tv_sec == 0 && result->tv_usec < 0));
}

#ifdef DELAYED_TOOLTIPS
void *
waiter_thread_helper_function(void *waiter_object) {
    Waiter *waiter = (Waiter *)waiter_object;
    waiter->waiter_loop();
    return NULL;
}

Waiter::Waiter() {
    int result;
    end = fire = false;
    wait_secs = 0;
    wait_from.tv_sec = 0;
    wait_from.tv_usec = 0;
    func = NULL;
    param = NULL;
    result = sem_init(&run_waiter, 0, 0);
    if (result != 0) {
	perror("bbsmount: Can't initialize semaphore!");
	exit(EXIT_FAILURE);
    }
    result = pthread_create(&wait_thread, NULL, waiter_thread_helper_function, (void *)this);
    if (result != 0) {
	perror("bbsmount: Can't create thread!");
	exit(EXIT_FAILURE);
    }
}

Waiter::~Waiter() {
    int result;

    fire = false;
    end = true;
    sem_post(&run_waiter);
    result = pthread_join(wait_thread, NULL);
    if (result != 0) {
	perror("bbsmount: Can't join thread!");
	exit(EXIT_FAILURE);
    }
    sem_destroy(&run_waiter);
}

void
Waiter::waiter_loop(void) {
    struct timeval current_time, difference, wait_to;
    struct timespec sleep_time;
    
    while (true) {
	if (end)
	    pthread_exit(NULL);
	if (fire) {
	    if (gettimeofday(&current_time, NULL) != 0) {
		perror("bbsmount: Can't get current time!");
		return;
	    }
	    wait_to.tv_sec = wait_from.tv_sec + wait_secs;
	    wait_to.tv_usec = wait_from.tv_usec;
	    if (timeval_subtract(&wait_to, &current_time, &difference)) {
		fire = false;
		((*param).*func)();
	    } else {
		sleep_time.tv_sec = difference.tv_sec;
		sleep_time.tv_nsec = difference.tv_usec * 1000;
		nanosleep(&sleep_time, NULL);
	    }
	}
	else
	    sem_wait(&run_waiter);
    }
}

void
Waiter::wait(const int _secs, void (BToolTip::*_func)(), BToolTip *_param) {
    if (gettimeofday(&wait_from, NULL) != 0) {
	perror("bbsmount: Can't get current time!");
	return;
    }
    wait_secs = _secs;
    func = _func;
    param = _param;
    fire = true;
    sem_post(&run_waiter);
}

void
Waiter::cancel(void) {
    fire = false;
}
#endif /* DELAYED_TOOLTIPS */

BToolTip::BToolTip() {
    is_visible = is_created = false;
    real_x = real_y = x = y = timeout = 0;
    real_w = real_h = 0;
    direction = bottom_right;
    tooltip_window = None;
    tooltip_pixmap = None;
    pthread_mutex_init(&show_hide_syn, (pthread_mutexattr_t *)NULL);
}

BToolTip::~BToolTip() {
    hide();
    if (tooltip_pixmap != None)
	AppWindow->getImageControl()->removeImage(tooltip_pixmap);
    if (tooltip_window != None)
	XDestroyWindow(AppWindow->getXDisplay(), tooltip_window);
    pthread_mutex_destroy(&show_hide_syn);
}

void
BToolTip::createToolTipWindow(const int _x, const int _y, const unsigned int _w, const unsigned int _h) {
    pthread_mutex_lock(&show_hide_syn);
    if (!is_created) {
	XSetWindowAttributes attrib;
	unsigned long mask = CWBorderPixel | CWColormap | CWSaveUnder | CWOverrideRedirect | CWEventMask;

	attrib.border_pixel = AppWindow->GetResources()->getTooltipBorderColor().getPixel();
	attrib.colormap = AppWindow->getImageControl()->getColormap();
	attrib.override_redirect = True;
	attrib.save_under = True;
	attrib.event_mask = ExposureMask;
	tooltip_window = XCreateWindow(AppWindow->getXDisplay(), DefaultRootWindow(AppWindow->getXDisplay()),
					_x, _y, _w, _h, AppWindow->GetResources()->getTooltipBorderWidth(),
					AppWindow->getImageControl()->getDepth(), InputOutput, AppWindow->getCurrentScreenInfo()->getVisual(),
					mask, &attrib);
    } else
	XMoveResizeWindow(AppWindow->getXDisplay(), tooltip_window, _x, _y, _w, _h);

    XMapWindow(AppWindow->getXDisplay(), tooltip_window);
    XRaiseWindow(AppWindow->getXDisplay(), tooltip_window);
    is_visible = true;

    if (!is_created || _w != real_w || _h != real_h) {
	BTexture *texture = AppWindow->GetResources()->getTooltipTexture();
	if (tooltip_pixmap != None)
	    AppWindow->getImageControl()->removeImage(tooltip_pixmap);
	tooltip_pixmap = AppWindow->getImageControl()->renderImage(_w, _h, texture);
//        if (tooltip_pixmap == ParentRelative) {
//        texture = &(resource.wstyle.t_focus);
//            geom_pixmap = texture->render(geom_w, geom_h, geom_pixmap);
//        }
	if (!tooltip_pixmap)
	    XSetWindowBackground(AppWindow->getXDisplay(), tooltip_window, AppWindow->GetResources()->getTooltipBorderColor().getPixel());
	else
	    XSetWindowBackgroundPixmap(AppWindow->getXDisplay(), tooltip_window, tooltip_pixmap);
    }

    real_w = _x;
    real_y = _y;
    real_w = _w;
    real_h = _h;
    is_created = true;
    pthread_mutex_unlock(&show_hide_syn);
}

void
BToolTip::setText(const string &tooltip_text) {
    unsigned int found, last_found = 0;
    
    current_tooltip_text.clear();
    while ((found = tooltip_text.find('\n', last_found)) != string::npos) {
	current_tooltip_text.push_back(tooltip_text.substr(last_found, (found - last_found)));
	last_found = found + 1;
    }
    if (last_found < tooltip_text.length())
	current_tooltip_text.push_back(tooltip_text.substr(last_found));
    if (is_visible)
	showNow();
}

void
BToolTip::setTimeout(const int seconds) {
#ifdef DELAYED_TOOLTIPS
    timeout = seconds;
#else /* !DELAYED_TOOLTIPS */
    timeout = 0;
#endif /* !DELAYED_TOOLTIPS */
}

void
BToolTip::setPosition(const int _x, const int _y, const direction_type _direction) {
    x = _x;
    y = _y;
    direction = _direction;
    if (is_visible)
	show();
}

void
BToolTip::redraw(const int _x, const int _y, const unsigned int _w, const unsigned int _h) {
    int line = 0, y_start, y_end;
    vector<string>::const_iterator pointer;

    XGCValues values;
    if (!i18n.multibyte())
	values.font = AppWindow->GetResources()->getTooltipFont()->fid;
    GC gctt = XCreateGC(AppWindow->getXDisplay(), tooltip_window, (i18n.multibyte() ? 0 : GCFont), &values);
    XClearArea(AppWindow->getXDisplay(), tooltip_window, _x, _y, _w, _h, False);
    for (pointer = current_tooltip_text.begin(); pointer != current_tooltip_text.end(); pointer++) {
	if (i18n.multibyte()) {
	    y_start = AppWindow->GetResources()->getTooltipBevelWidth() -
	    AppWindow->GetResources()->getTooltipFontSetExtents()->MAX_EXTENT.y +
		AppWindow->GetResources()->getTooltipFontSetExtents()->MAX_EXTENT.height * line++;
	    y_end = y_start + AppWindow->GetResources()->getTooltipFontSetExtents()->MAX_EXTENT.height;
	}
	else {
	    y_start = AppWindow->GetResources()->getTooltipFont()->ascent +
	    AppWindow->GetResources()->getTooltipBevelWidth() +
		(AppWindow->GetResources()->getTooltipFont()->ascent +
		 AppWindow->GetResources()->getTooltipFont()->descent) * line++;
	    y_end = y_start + (AppWindow->GetResources()->getTooltipFont()->ascent +
		 AppWindow->GetResources()->getTooltipFont()->descent);
	}
	if (_y >= y_end)
	    continue;
	if (_y + (int)_h < y_start)
	    break;
	if (i18n.multibyte()) {
	    XmbDrawString(AppWindow->getXDisplay(), tooltip_window,
	    AppWindow->GetResources()->getTooltipFontSet(), gctt,
	    AppWindow->GetResources()->getTooltipBevelWidth(), y_start,
	    pointer->c_str(), pointer->length());
	} else {
	    XDrawString(AppWindow->getXDisplay(), tooltip_window,
	    gctt, AppWindow->GetResources()->getTooltipBevelWidth(), y_start,
	    pointer->c_str(), pointer->length());
	}
    }
    XFlush(AppWindow->getXDisplay());
    XFreeGC(AppWindow->getXDisplay(), gctt);
}

void
BToolTip::processEvent(const XEvent &event) {
    if (event.type == Expose && event.xexpose.window == tooltip_window)
	redraw(event.xexpose.x, event.xexpose.y, event.xexpose.width, event.xexpose.height);
}

void
BToolTip::hide(void) {
#ifdef DELAYED_TOOLTIPS
    waiter.cancel();
#endif /* DELAYED_TOOLTIPS */
    pthread_mutex_lock(&show_hide_syn);
    if (is_visible) {
	is_visible = false;
	XUnmapWindow(AppWindow->getXDisplay(), tooltip_window);
	current_tooltip_text.clear();	// destroy tooltip content
	real_w = real_h = 0;
    }
    pthread_mutex_unlock(&show_hide_syn);
}

void
BToolTip::show(void) {
#ifdef DELAYED_TOOLTIPS
    if (timeout > 0)
	waiter.wait(timeout, &BToolTip::showNow, this);
    else
#endif /* DELAYED_TOOLTIPS */
	showNow();
}

void
BToolTip::showNow(void) {
    unsigned int text_width, text_height, width, height, max_width = 0;
    int new_x = x, new_y = y;
    vector<string>::const_iterator pointer;
    
    if (current_tooltip_text.empty())
	return;
    if (i18n.multibyte()) {
	for (pointer = current_tooltip_text.begin(); pointer != current_tooltip_text.end(); pointer++) {
	    text_width = XmbTextEscapement(AppWindow->GetResources()->getTooltipFontSet(),
		    pointer->c_str(), pointer->length());
	    if (text_width > max_width)
		max_width = text_width;
	}
    } else {
	for (pointer = current_tooltip_text.begin(); pointer != current_tooltip_text.end(); pointer++) {
	    text_width = XTextWidth(AppWindow->GetResources()->getTooltipFont(),
		    pointer->c_str(), pointer->length());
	    if (text_width > max_width)
		max_width = text_width;
	}
    }
    text_width = max_width;
    if (i18n.multibyte())
	text_height = AppWindow->GetResources()->getTooltipFontSetExtents()->MAX_EXTENT.height * current_tooltip_text.size();
    else
	text_height = (AppWindow->GetResources()->getTooltipFont()->ascent +
	    AppWindow->GetResources()->getTooltipFont()->descent) * current_tooltip_text.size();
    width = text_width + 2 * (AppWindow->GetResources()->getTooltipBevelWidth() + AppWindow->GetResources()->getTooltipBorderWidth());
    height = text_height + 2 * (AppWindow->GetResources()->getTooltipBevelWidth() + AppWindow->GetResources()->getTooltipBorderWidth());

    switch (direction) {
	case top_left:
	    new_x = x - width;
	    if (new_x < 0)
		new_x = 0;
	    new_y = y - height;
	    if (new_y < 0)
		new_y = 0;
	    break;
	case top_right:
	    if (new_x + width >= AppWindow->getCurrentScreenInfo()->getWidth())
		new_x = AppWindow->getCurrentScreenInfo()->getWidth() - width;
	    new_y = y - height;
	    if (new_y < 0)
		new_y = 0;
	    break;
	case bottom_left:
	    new_x = x - width;
	    if (new_x < 0)
		new_x = 0;
	    if (new_y + height >= AppWindow->getCurrentScreenInfo()->getHeight())
		new_y = AppWindow->getCurrentScreenInfo()->getHeight() - height;
	    break;
	case bottom_right:
	    if (new_x + width >= AppWindow->getCurrentScreenInfo()->getWidth())
		new_x = AppWindow->getCurrentScreenInfo()->getWidth() - width;
	    if (new_y + height >= AppWindow->getCurrentScreenInfo()->getHeight())
		new_y = AppWindow->getCurrentScreenInfo()->getHeight() - height;
	    break;
    }
    createToolTipWindow(new_x, new_y, width, height);
    redraw(0, 0, width, height);
}

bool
BToolTip::isShown(void) const {
    return is_visible;
}

int
BToolTip::getTimeout(void) const {
    return timeout;
}


syntax highlighted by Code2HTML, v. 0.9.1