/* This file is part of the KDE project
   Copyright (C) 2005 by Tobi Vollebregt <tobivollebregt@gmail.com>
   Copyright (C) 2004 by Vinay Khaitan <vkhaitan@iitk.ac.in>
   Copyright (C) 2004 Arend van Beelen jr. <arend@auton.nl>

   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; see the file COPYING.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/

#include <unistd.h>

#include <dcopclient.h>
#include <tdeapplication.h>
#include <tdeaction.h>
#include <tdeconfig.h>
#include <kdebug.h>
#include <kdesktopfile.h>
#include <kgenericfactory.h>
#include <tdeglobal.h>
#include <tdehtml_part.h>
#include <kiconloader.h>
#include <klineedit.h>
#include <tdelocale.h>
#include <kmimetype.h>
#include <kprocess.h>
#include <kprotocolinfo.h>
#include <tdeprotocolmanager.h>
#include <kstandarddirs.h>
#include <kurifilter.h>

#include <tdeio/job.h>

#include <tdeparts/mainwindow.h>
#include <tdeparts/partmanager.h>

#include <tqpainter.h>
#include <tqpopupmenu.h>
#include <tqtimer.h>
#include <tqstyle.h>
#include <tqwhatsthis.h>
#include "searchbar.h"

typedef KGenericFactory<SearchBarPlugin> SearchBarPluginFactory;
K_EXPORT_COMPONENT_FACTORY(libsearchbarplugin,
                           SearchBarPluginFactory("searchbarplugin"))


SearchBarPlugin::SearchBarPlugin(TQObject *parent, const char *name,
                                 const TQStringList &) :
  KParts::Plugin(parent, name),
  m_searchCombo(0),
  m_searchMode(UseSearchProvider),
  m_urlEnterLock(false),
  m_gsTimer(this),
  m_googleMode(GoogleOnly)
{
	m_searchCombo = new SearchBarCombo(0L, "search combo");
	m_searchCombo->setDuplicatesEnabled(false);
	m_searchCombo->setMaxCount(5);
	m_searchCombo->setFixedWidth(180);
	m_searchCombo->setLineEdit(new KLineEdit(m_searchCombo));
	m_searchCombo->lineEdit()->installEventFilter(this);
	m_searchCombo->listBox()->setFocusProxy(m_searchCombo);

	m_popupMenu = 0;
	m_googleMenu = 0;

	m_searchComboAction = new KWidgetAction(m_searchCombo, i18n("Search Bar"), 0,
	                                                     0, 0, actionCollection(), "toolbar_search_bar");
	m_searchComboAction->setShortcutConfigurable(false);

	connect(m_searchCombo, TQ_SIGNAL(activated(const TQString &)),
	                       TQ_SLOT(startSearch(const TQString &)));
	connect(m_searchCombo, TQ_SIGNAL(iconClicked()), TQ_SLOT(showSelectionMenu()));

	TQWhatsThis::add(m_searchCombo, i18n("Search Bar<p>"
	                                    "Enter a search term. Click on the icon to change search mode or provider."));

	new TDEAction( i18n( "Focus Searchbar" ), CTRL+Key_S,
			       this, TQ_SLOT(focusSearchbar()),
			       actionCollection(), "focus_search_bar");

	configurationChanged();

	KParts::MainWindow *mainWin = static_cast<KParts::MainWindow*>(parent);

	//Grab the part manager. Don't know of any other way, and neither does Tronical, so..
	KParts::PartManager *partMan = static_cast<KParts::PartManager*>(mainWin->child(0, "KParts::PartManager"));
	if (partMan)
	{
		connect(partMan, TQ_SIGNAL(activePartChanged(KParts::Part*)),
		                 TQ_SLOT  (partChanged      (KParts::Part*)));
		partChanged(partMan->activePart());
	}

	connect(this, TQ_SIGNAL(gsCompleteDelayed()), TQ_SLOT(gsStartDelay()));
	connect(&m_gsTimer, TQ_SIGNAL(timeout()), TQ_SLOT(gsMakeCompletionList()));
	connect(m_searchCombo->listBox(), TQ_SIGNAL(highlighted(const TQString&)), TQ_SLOT(gsSetCompletedText(const TQString&)));
	connect(m_searchCombo, TQ_SIGNAL(activated(const TQString&)), TQ_SLOT(gsPutTextInBox(const TQString&)));
}

SearchBarPlugin::~SearchBarPlugin()
{
	TDEConfig *config = kapp->config();
	config->setGroup("SearchBar");
	config->writeEntry("Mode", (int) m_searchMode);
	config->writeEntry("CurrentEngine", m_currentEngine);
	config->writeEntry("GoogleSuggestMode", m_googleMode);

	delete m_searchCombo;
	m_searchCombo = 0L;
}

TQChar delimiter()
{
        TDEConfig config( "kuriikwsfilterrc", true, false );
        config.setGroup( "General" );
        return config.readNumEntry( "KeywordDelimiter", ':' );
}

bool SearchBarPlugin::eventFilter(TQObject *o, TQEvent *e)
{
	if( o==m_searchCombo->lineEdit() && e->type() == TQEvent::KeyPress )
	{
		TQKeyEvent *k = (TQKeyEvent *)e;
		TQString text = k->text();
		if(!text.isEmpty())
		{
			if(k->key() != TQt::Key_Return && k->key() != Key_Enter && k->key() != Key_Escape)
			{
				emit gsCompleteDelayed();
			}
		}
		if(k->state() & ControlButton)
		{
			if(k->key()==Key_Down)
			{
				nextSearchEntry();
				return true;
			}
			if(k->key()==Key_Up)
			{
				previousSearchEntry();
				return true;
			}
		}
		else
		{
			if (k->key() == Key_Up || k->key() == Key_Down)
			{
				if(m_searchCombo->listBox()->isVisible())
				{
					tqApp->sendEvent(m_searchCombo->listBox(), e);
					return true;
				}
			}
		}
		if (k->key() == Key_Enter || k->key() == Key_Return)
		{
			/*- Fix a bug which caused the searchbar to search for the completed
			    input instead of the literal input when enter was pressed and
			    the listbox was visible.
			if(m_searchCombo->listBox()->isVisible())
			{
				tqApp->sendEvent(m_searchCombo->listBox(),e);
			}*/
		}
		if (k->key() == Key_Escape)
		{
			m_searchCombo->listBox()->hide();
			if (m_searchCombo->lineEdit()->hasSelectedText())
			{
				m_searchCombo->lineEdit()->setText(m_searchCombo->currentText().left(m_searchCombo->lineEdit()->selectionStart()));
			}
			m_gsTimer.stop();
		}
	}
	return false;
}

void SearchBarPlugin::nextSearchEntry()
{
	if(m_searchMode == FindInThisPage)
	{
		m_searchMode = UseSearchProvider;
		if(m_searchEngines.count())
		{
			m_currentEngine = *m_searchEngines.at(0);
		}
		else
		{
			m_currentEngine = "google";
		}
	}
	else
	{
		TQStringList::ConstIterator it = m_searchEngines.find(m_currentEngine);
		it++;
		if(it==m_searchEngines.end())
		{
			m_searchMode = FindInThisPage;
		}
		else
		{
			m_currentEngine = *it;
		}
	}
	setIcon();
}

void SearchBarPlugin::previousSearchEntry()
{
	if(m_searchMode == FindInThisPage)
	{
		m_searchMode = UseSearchProvider;
		if(m_searchEngines.count())
		{
			m_currentEngine = *m_searchEngines.fromLast();
		}
		else
		{
			m_currentEngine = "google";
		}
	}
	else
	{
		TQStringList::ConstIterator it = m_searchEngines.find(m_currentEngine);
		if(it==m_searchEngines.begin())
		{
			m_searchMode = FindInThisPage;
		}
		else
		{
			it--;
			m_currentEngine = *it;
		}
	}
	setIcon();
}

void SearchBarPlugin::startSearch(const TQString &_search)
{
	if(m_urlEnterLock || _search.isEmpty() || !m_part)
		return;

	m_gsTimer.stop();
	m_searchCombo->listBox()->hide();

	TQString search = _search.section('(', 0, 0).stripWhiteSpace();

	if(m_searchMode == FindInThisPage)
	{
		m_part->findText(search, 0);
		m_part->findTextNext();
	}
	else if(m_searchMode == UseSearchProvider)
	{
		m_urlEnterLock = true;
	        KService::Ptr service;
		KURIFilterData data;
		TQStringList list;
		list << "kurisearchfilter" << "kuriikwsfilter";

		service = KService::serviceByDesktopPath(TQString("searchproviders/%1.desktop").arg(m_currentEngine));
                if (service) {
		    const TQString searchProviderPrefix = *(service->property("Keys").toStringList().begin()) + delimiter();
		    data.setData( searchProviderPrefix + search );
                }

		if(!service || !KURIFilter::self()->filterURI(data, list))
		{
			data.setData( TQString::fromLatin1( "google" ) + delimiter() + search );
			KURIFilter::self()->filterURI( data, list );
		}

		if(TDEApplication::keyboardMouseState() & TQt::ControlButton) 
		{
			KParts::URLArgs args;
			args.setNewTab(true);
			emit m_part->browserExtension()->createNewWindow( data.uri(), args );
		}
		else
		{
		        emit m_part->browserExtension()->openURLRequest(data.uri());
		}
	}

	if(m_searchCombo->text(0).isEmpty())
	{
		m_searchCombo->changeItem(m_searchIcon, search, 0);
	}
	else
	{
		if(m_searchCombo->findHistoryItem(search) == -1)
		{
			m_searchCombo->insertItem(m_searchIcon, search, 0);
		}
	}

	m_searchCombo->setCurrentText("");
	m_urlEnterLock = false;
}

void SearchBarPlugin::setIcon()
{
	TQString hinttext;
	if(m_searchMode == FindInThisPage)
	{
		m_searchIcon = SmallIcon("edit-find");
		hinttext = i18n("Find in This Page");
	}
	else
	{
		TQString providername;
		KService::Ptr service;
		KURIFilterData data;
		TQStringList list;
		list << "kurisearchfilter" << "kuriikwsfilter";

		service = KService::serviceByDesktopPath(TQString("searchproviders/%1.desktop").arg(m_currentEngine));
                if (service) {
		    const TQString searchProviderPrefix = *(service->property("Keys").toStringList().begin()) + delimiter();
		    data.setData( searchProviderPrefix + "some keyword" );
                }

		if (service && KURIFilter::self()->filterURI(data, list))
		{
			TQString iconPath = locate("cache", KMimeType::favIconForURL(data.uri()) + ".png");
			if(iconPath.isEmpty())
			{
				m_searchIcon = SmallIcon("enhanced_browsing");
			}
			else
			{
				m_searchIcon = TQPixmap(iconPath);
			}
			providername = service->name();
		}
		else
		{
			m_searchIcon = SmallIcon("google");
			providername = "Google";
		}
		hinttext = i18n("%1 Search").arg(providername);;
	}
	static_cast<KLineEdit*>(m_searchCombo->lineEdit())->setClickMessage(hinttext);

        // Create a bit wider icon with arrow
	TQPixmap arrowmap = TQPixmap(m_searchIcon.width()+5,m_searchIcon.height()+5);
	arrowmap.fill(m_searchCombo->lineEdit()->backgroundColor());
	TQPainter p( &arrowmap );
	p.drawPixmap(0, 2, m_searchIcon);
	TQStyle::SFlags arrowFlags = TQStyle::Style_Default;
	m_searchCombo->style().drawPrimitive(TQStyle::PE_ArrowDown, &p, TQRect(arrowmap.width()-6, 
	    arrowmap.height()-6, 6, 5), m_searchCombo->colorGroup(), arrowFlags, TQStyleOption() );
	p.end();
	m_searchIcon = arrowmap;

	m_searchCombo->setIcon(m_searchIcon);
}

void SearchBarPlugin::showSelectionMenu()
{
	if(!m_popupMenu)
	{
		KService::Ptr service;
		TQPixmap icon;
		KURIFilterData data;
		TQStringList list;
		list << "kurisearchfilter" << "kuriikwsfilter";

		m_popupMenu = new TQPopupMenu(m_searchCombo, "search selection menu");
		m_popupMenu->insertItem(SmallIcon("edit-find"), i18n("Find in This Page"), this, TQ_SLOT(useFindInThisPage()),  0, 999);
		m_popupMenu->insertSeparator();

		int i=-1;
		for (TQStringList::ConstIterator it = m_searchEngines.begin(); it != m_searchEngines.end(); ++it )
		{
			i++;
			service = KService::serviceByDesktopPath(TQString("searchproviders/%1.desktop").arg(*it));
			if(!service)
			{
				continue;
			}
			const TQString searchProviderPrefix = *(service->property("Keys").toStringList().begin()) + delimiter();
			data.setData( searchProviderPrefix + "some keyword" );

			if(KURIFilter::self()->filterURI(data, list))
			{
				TQString iconPath = locate("cache", KMimeType::favIconForURL(data.uri()) + ".png");
				if(iconPath.isEmpty())
				{
					icon = SmallIcon("enhanced_browsing");
				}
				else
				{
					icon = TQPixmap( iconPath );
				}
			m_popupMenu->insertItem(icon, service->name(), i);
			}
		}

		m_popupMenu->insertSeparator();
		m_googleMenu = new TDESelectAction(i18n("Use Google Suggest"), SmallIconSet("ktip"), 0, this, TQ_SLOT(selectGoogleSuggestMode()), m_popupMenu);
		TQStringList google_modes;
                google_modes << i18n("For Google Only") << i18n("For All Searches") << i18n("Never");
		m_googleMenu->setItems(google_modes);
		m_googleMenu->plug(m_popupMenu);
		m_popupMenu->insertItem(SmallIcon("enhanced_browsing"), i18n("Select Search Engines..."),
			this, TQ_SLOT(selectSearchEngines()), 0, 1000);
		connect(m_popupMenu, TQ_SIGNAL(activated(int)), TQ_SLOT(useSearchProvider(int)));
	}
	m_googleMenu->setCurrentItem(m_googleMode);
	m_popupMenu->popup(m_searchCombo->mapToGlobal(TQPoint(0, m_searchCombo->height() + 1)), 0);
}

void SearchBarPlugin::useFindInThisPage()
{
	m_searchMode = FindInThisPage;
	setIcon();
}

void SearchBarPlugin::useSearchProvider(int id)
{
	if(id>900)
	{
		// Not a search engine entry selected
		return;
	}
	m_searchMode = UseSearchProvider;
	m_currentEngine = *m_searchEngines.at(id);
	setIcon();
}

void SearchBarPlugin::selectSearchEngines()
{
	TDEProcess *process = new TDEProcess;

	*process << "tdecmshell" << "ebrowsing";

	connect(process, TQ_SIGNAL(processExited(TDEProcess *)), TQ_SLOT(searchEnginesSelected(TDEProcess *)));

	if(!process->start())
	{
		kdDebug(1202) << "Couldn't invoke tdecmshell." << endl;
		delete process;
	}
}

void SearchBarPlugin::searchEnginesSelected(TDEProcess *process)
{
	if(!process || process->exitStatus() == 0)
	{
		TDEConfig *config = kapp->config();
		config->setGroup("SearchBar");
		config->writeEntry("CurrentEngine", m_currentEngine);
		config->sync();
		configurationChanged();
	}
	delete process;
}

void SearchBarPlugin::configurationChanged()
{
	TDEConfig *config = new TDEConfig("kuriikwsfilterrc");
	config->setGroup("General");
	TQString engine = config->readEntry("DefaultSearchEngine", "google");

	TQStringList favoriteEngines;
	favoriteEngines << "google" << "google_groups" << "google_news" << "webster" << "dmoz" << "wikipedia";
	favoriteEngines = config->readListEntry("FavoriteSearchEngines", favoriteEngines);

	delete m_popupMenu;
	m_popupMenu = 0;
	m_searchEngines.clear();
	m_searchEngines << engine;
	for (TQStringList::ConstIterator it = favoriteEngines.begin(); it != favoriteEngines.end(); ++it )
		if(*it!=engine)
			m_searchEngines << *it;

	delete config;
	if(engine.isEmpty())
	{
		m_providerName = "Google";
	}
	else
	{
		KDesktopFile file("searchproviders/" + engine + ".desktop", true, "services");
		m_providerName = file.readName();
	}

	config = kapp->config();
	config->setGroup("SearchBar");
	m_searchMode = (SearchModes) config->readNumEntry("Mode", (int) UseSearchProvider);
	m_currentEngine = config->readEntry("CurrentEngine", engine);
	m_googleMode=(GoogleMode)config->readNumEntry("GoogleSuggestMode", GoogleOnly);

	if ( m_currentEngine.isEmpty() )
	    m_currentEngine = "google";

	setIcon();
}

void SearchBarPlugin::partChanged(KParts::Part *newPart)
{
	m_part = ::tqt_cast<TDEHTMLPart*>(newPart);

	//Delay since when destroying tabs part 0 gets activated for a bit, before the proper part
	TQTimer::singleShot(0, this, TQ_SLOT(updateComboVisibility()));
}

void SearchBarPlugin::updateComboVisibility()
{
	if (m_part.isNull() || !m_searchComboAction->isPlugged())
	{
		m_searchCombo->setPluginActive(false);
		m_searchCombo->hide();
	}
	else
	{
		m_searchCombo->setPluginActive(true);
		m_searchCombo->show();
	}
}

void SearchBarPlugin::focusSearchbar()
{
	TQFocusEvent::setReason( TQFocusEvent::Shortcut );
	m_searchCombo->setFocus();
	TQFocusEvent::resetReason();
}

SearchBarCombo::SearchBarCombo(TQWidget *parent, const char *name) :
  KHistoryCombo(parent, name),
  m_pluginActive(true)
{
	connect(this, TQ_SIGNAL(cleared()), TQ_SLOT(historyCleared()));
}

const TQPixmap &SearchBarCombo::icon() const
{
	return m_icon;
}

void SearchBarCombo::setIcon(const TQPixmap &icon)
{
	m_icon = icon;

	if(count() == 0)
	{
		insertItem(m_icon, 0);
	}
	else
	{
		for(int i = 0; i < count(); i++)
		{
			changeItem(m_icon, text(i), i);
		}
	}
}

int SearchBarCombo::findHistoryItem(const TQString &searchText)
{
	for(int i = 0; i < count(); i++)
	{
		if(text(i) == searchText)
		{
			return i;
		}
	}

	return -1;
}

void SearchBarCombo::mousePressEvent(TQMouseEvent *e)
{
	int x0 = TQStyle::visualRect( style().querySubControlMetrics( TQStyle::CC_ComboBox, this, TQStyle::SC_ComboBoxEditField ), this ).x();

	if(e->x() > x0 + 2 && e->x() < lineEdit()->x())
	{
		emit iconClicked();

		e->accept();
	}
	else
	{
		KHistoryCombo::mousePressEvent(e);
	}
}

void SearchBarCombo::historyCleared()
{
	setIcon(m_icon);
}

void SearchBarCombo::setPluginActive(bool pluginActive)
{
	m_pluginActive = pluginActive;
}

void SearchBarCombo::show()
{
	if(m_pluginActive)
	{
		KHistoryCombo::show();
	}
}

// Google Suggest code

void SearchBarPlugin::selectGoogleSuggestMode()
{
	m_googleMode = (GoogleMode)m_googleMenu->currentItem();
	TDEConfig *config = kapp->config();
	config->setGroup("SearchBar");
	config->writeEntry("GoogleSuggestMode", m_googleMode);
	config->sync();
}

// adapted and modified by Tobi Vollebregt
// original code from Googlebar by Vinay Khaitan

void SearchBarPlugin::gsStartDelay()
{
	m_gsTimer.stop();
	m_searchCombo->listBox()->hide();
	// FIXME: make configurable
	m_gsTimer.start(500, true);
}

void SearchBarPlugin::gsMakeCompletionList()
{
	if ((m_googleMode==GoogleOnly && m_currentEngine != "google") || m_googleMode==Never)
		return;

	if (!m_searchCombo->currentText().isEmpty())
	{
		TDEIO::TransferJob* tj =
				TDEIO::get(KURL("http://www.google.com/complete/search?hl=en&js=true&qu=" + m_searchCombo->currentText()), false, false);
		connect(tj, TQ_SIGNAL(data(TDEIO::Job*, const TQByteArray&)), this, TQ_SLOT(gsDataArrived(TDEIO::Job*, const TQByteArray&)));
		connect(tj, TQ_SIGNAL(result(TDEIO::Job*)), this, TQ_SLOT(gsJobFinished(TDEIO::Job*)));
	}
}

void SearchBarPlugin::gsDataArrived(TDEIO::Job*, const TQByteArray& data)
{
	m_gsData += TQString::fromUtf8(data.data());
}

static TQString reformatNumber(const TQString& number)
{
	static const char suffix[] = { 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' };
	TQString s = number.stripWhiteSpace();
	uint c = 0;
	for (int i = s.length() - 1; i > 0 && s[i] == '0'; --i) ++c;
	c /= 3;
	if (c >= sizeof(suffix)/sizeof(suffix[0]))
		c = sizeof(suffix)/sizeof(suffix[0]) - 1;
	s = s.left(s.length() - c * 3) + suffix[c];
	return s;
}

void SearchBarPlugin::gsJobFinished(TDEIO::Job* job)
{
	if (((TDEIO::TransferJob*)job)->error() == 0)
	{
		TQString temp;
		temp = m_gsData.mid(m_gsData.find('(') + 1, m_gsData.findRev(')') - m_gsData.find('(') - 1);
		temp = temp.mid(temp.find('(') + 1, temp.find(')') - temp.find('(') - 1);
		temp.remove('"');
		TQStringList compList1 = TQStringList::split(',', temp);
		temp = m_gsData.mid(m_gsData.find(')') + 1, m_gsData.findRev(')') - m_gsData.find('(') - 1);
		temp = temp.mid(temp.find('(') + 1, temp.find(')') - temp.find('(') - 1);
		temp.remove('"');
		temp.remove(',');
		temp.remove('s');
		TQStringList compList2 = TQStringList::split("reult", temp);
		TQStringList finalList;
		for(uint j = 0; j < compList1.count(); j++)
		{
			if (m_googleMode!=ForAll || m_currentEngine == "google")
				finalList.append(compList1[j].stripWhiteSpace() + " (" + reformatNumber(compList2[j]) + ")");
			else
				finalList.append(compList1[j].stripWhiteSpace());
		}
		//store text so that we can restore it if it gets erased after GS returns no results
		temp = m_searchCombo->currentText();
		m_searchCombo->listBox()->clear();
		m_searchCombo->listBox()->insertStringList(finalList);
		m_searchCombo->setIcon(m_searchIcon);
		//restore text
		m_searchCombo->lineEdit()->setText(temp);
		if (finalList.count() != 0 && !m_gsTimer.isActive())
		{
			m_searchCombo->popup();
		}
	}
	m_gsData = "";
}

void SearchBarPlugin::gsSetCompletedText(const TQString& text)
{
	TQString currentText;
	if (m_searchCombo->lineEdit()->hasSelectedText())
		currentText = m_searchCombo->currentText().left(m_searchCombo->lineEdit()->selectionStart());
	else
		currentText = m_searchCombo->currentText();
	if (currentText == text.left(currentText.length()))
	{
		m_searchCombo->lineEdit()->setText(text.left(text.find('(') - 1));
		m_searchCombo->lineEdit()->setCursorPosition(currentText.length());
		m_searchCombo->lineEdit()->setSelection(currentText.length(), m_searchCombo->currentText().length() - currentText.length());
	}
}

void SearchBarPlugin::gsPutTextInBox(const TQString& text)
{
	m_searchCombo->lineEdit()->setText(text.section('(', 0, 0).stripWhiteSpace());
}

#include "searchbar.moc"
