//
// SearchDialog.cc for pekwm
// Copyright (C) 2009-2020 Claes Nästén <pekdon@gmail.com>
//
// This program is licensed under the GNU GPL.
// See the LICENSE file for more information.
//

#include "config.h"

#include "SearchDialog.hh"

#include "Client.hh"
#include "RegexString.hh"

#include <iostream>
#include <list>

/**
 * SearchDialog constructor.
 */
SearchDialog::SearchDialog()
	: InputDialog("Search"),
	  _result_menu(0)
{
	_type = PWinObj::WO_SEARCH_DIALOG;

	// Set up ActionEvent
	ae().action_list.back().setAction(ACTION_GOTO_CLIENT);

	// Set up menu for displaying results
	_result_menu = new PMenu("", "");
	_result_menu->reparent(this,
			       bdLeft(this),
			       bdTop(this)
				   + titleHeight(this)
				   + _text_wo->getHeight());
	_result_menu->setLayer(LAYER_DESKTOP); // Ignore when placing
	_result_menu->setSticky(STATE_SET);
	_result_menu->setBorder(STATE_UNSET);
	_result_menu->setTitlebar(STATE_UNSET);
	_result_menu->setFocusable(false);
	_result_menu->mapWindow();
}

/**
 * SearchDialog destructor.
 */
SearchDialog::~SearchDialog(void)
{
	delete _result_menu;
}

/**
 * Run when INPUT_EXEC is entered in the dialog giving the selected
 * Client focus if any.
 */
ActionEvent*
SearchDialog::exec(void)
{
	// InputDialog::close() may have overwritten our action.
	ae().action_list.back().setAction(ACTION_GOTO_CLIENT);

	PWinObj *wo_ref = 0;
	if (_result_menu->getItemCurr()) {
		wo_ref = _result_menu->getItemCurr()->getWORef();
	}
	setWORef(wo_ref);

	return &(ae());
}

/**
 * Called whenever the buffer has changed. Updates the displayed clients.
 */
void
SearchDialog::bufChanged(void)
{
	InputDialog::bufChanged();
	findClients(str());
}

/**
 * Focus next item in result menu.
 */
void
SearchDialog::histNext(void)
{
	_result_menu->selectItemRel(1);
}

/**
 * Focus previous item in result menu.
 */
void
SearchDialog::histPrev(void)
{
	_result_menu->selectItemRel(-1);
}

/**
 * Update size making sure result menu fits.
 */
void
SearchDialog::updateSize(const Geometry &head)
{
	InputDialog::updateSize(head);
	_result_menu->setMenuWidth(_text_wo->getWidth());
}

/**
 * Search list of clients for matching titles.
 *
 * @param search Regexp to search, case insensitive
 * @return Number of matches
 */
uint
SearchDialog::findClients(const std::string &search)
{
	// Do nothing if search has not changed.
	if (_previous_search == search) {
		return _result_menu->size();
	}
	_previous_search = search;

	_result_menu->removeAll();
	if (search.size() > 0) {
		RegexString search_re("/" + search + "/i");
		if (! search_re.is_match_ok()) {
			return 0;
		}

		std::vector<Client*> matches;
		Client::client_cit it(Client::client_begin());
		for (; it != Client::client_end(); ++it) {
			if ((*it)->isFocusable()
			    && ! (*it)->isSkip(SKIP_FOCUS_TOGGLE)
			    && search_re == (*it)->getTitle()->getReal()) {
				matches.push_back(*it);
			}
		}

		for (it = matches.begin(); it != matches.end(); ++it) {
			_result_menu->insert((*it)->getTitle()->getVisible(),
					     *it, (*it)->getIcon());
		}
	}

	// Rebuild menu and make room for it
	_result_menu->buildMenu();

	Geometry head;
	X11::getHeadInfo(getHead(), head);

	unsigned int width, height;
	getInputSize(head, width, height);

	if (_result_menu->size()) {
		resizeChild(_text_wo->getWidth(),
			    height + _result_menu->getHeight());
		X11::raiseWindow(_result_menu->getWindow());
		// Render first item as selected, needs to be done after
		// map/raise.
		_result_menu->selectItemNum(0);
	} else {
		resizeChild(_text_wo->getWidth(), height);
		X11::lowerWindow(_result_menu->getWindow());
	}

	return 0;
}

/**
 * Unmap window and clear buffer, result menu and window reference.
 */
void
SearchDialog::unmapWindow(void)
{
	if (_mapped) {
		InputDialog::unmapWindow();
		setWORef(0);
		bufClear();

		// Clear the menu and hide it.
		X11::clearWindow(_result_menu->getWindow());
		_previous_search = "";
		X11::lowerWindow(_result_menu->getWindow());
	}
}
