#include "window.h"
#include "browser.h"
#include "page.h"
#include "settings.h"
#include "tracker.h"
#include "selection.h"
#include "group.h"
#include "host.h"

#include <KLocalizedString>
#include <QAction>
#include <QWidgetAction>
#include <KActionCollection>
#include <KStandardAction>
#include <QMenuBar>
#include <QDesktopServices>
#include <KHelpMenu>
#include <KAboutData>

#include <QUrl>
#include <QSplitter>
#include <QStackedWidget>


namespace Dict {

Window::Window()
	: KXmlGuiWindow(), settings(0)
{
	entry = new Entry(this);

	browser = new Browser(this);
	search = new Search(browser);

	databaseView = new DatabaseView(this);
	matchList = new Browser(this);
	matchList->setHistorySize(20);

	sidebar = new QStackedWidget(this);
	sidebar->addWidget(databaseView);
	sidebar->addWidget(matchList);

	splitter = new QSplitter(this);
	splitter->addWidget(browser);
	splitter->addWidget(sidebar);
	splitter->setStretchFactor(0, 1);

	QWidget *widget = new QWidget(this);
	QVBoxLayout *layout = new QVBoxLayout(widget);
	layout->setContentsMargins(0, 0, 0, 0);
	layout->addWidget(splitter);
	layout->addWidget(search);
	setCentralWidget(widget);

	status = new Statusbar(this);
	setStatusBar(status);

	dict = new Connection();
	server = new Host(this, dict);
	page = browser->currentPage();
	tracker = new Tracker(dict, page);
	selection = new Selection();

	setupActions();
	setupGUI();
	setupBrowser();
	setupDict();
	setupTracker();

	show();
	setupMenubar();
	readConfig();
}

Window::~Window()
{
	writeConfig();
	delete tracker;
	delete dict;
	delete selection;
}

void Window::newWindow()
{
	writeConfig();
	(new Window())->startup();
}

void Window::startup(const QString &location)
{
	if (location.isEmpty())
		setHost(dicthost());
	else
		define(location);
}

void Window::readConfig()
{
	conf.read();
	wconf.read();

	setSidebarSide(wconf.sidebar);
	splitter->setSizes(wconf.widths);
	sidebar->setVisible(wconf.splitter);
	sidebar->setCurrentIndex(wconf.stack);
	setSidebarMenu();

	serverConfig();
	loadConfig();
}

void Window::serverConfig()
{
	sconf.read();
	if (sconf.hosts.isEmpty()) intro();
	else entry->setFocus();

	hostConfig(sconf.current);
}

void Window::hostConfig(const QString &host)
{
	hostMenu->setup(host);
	server->set(host);
	options->setup();

	Edit groups(server->conf.groups);
	databaseMenu->setGroups(*groups.list(), edit);
	databaseMenu->setChecked(server->conf.selected);
	databaseMenu->setList(server->conf.database, false);
	tracker->setDatabase(databaseMenu->current());
	tracker->setStrategy(server->conf.strategy);

	status->setHost(false, host);
	status->setUser(false, "");
}

void Window::writeConfig()
{
	wconf.sidebar = sidebar->currentIndex();
	wconf.widths = splitter->sizes();
	wconf.write();
	server->conf.write();
	conf.write();
}

void Window::setupActions()
{
	KActionCollection *actions = actionCollection();

	QAction *text = new QAction(i18n("Page Source"), this);
	text->setCheckable(true);
	actions->addAction("text", text);

	connect(text, SIGNAL(toggled(bool)), browser, SLOT(plainText(bool)));

	QAction *format = new QAction(i18n("Reformat"), this);
	actions->addAction("format", format);

	connect(format, SIGNAL(triggered()), SLOT(reformat()));

	yank = new QAction(i18n("Yank Selections"), this);
	yank->setCheckable(true);
	actions->addAction("yank", yank);

	connect(yank, SIGNAL(toggled(bool)), selection, SLOT(set(bool)));

	hilite = new QAction(i18n("Hilite Queries"), this);
	hilite->setCheckable(true);
	actions->addAction("hilite", hilite);

	connect(hilite, SIGNAL(toggled(bool)), browser, SLOT(setHilite(bool)));

	QAction *sidebar = new QAction(i18n("Database List"), this);
	sidebar->setCheckable(true);
	actions->addAction("sidebar", sidebar);

	connect(sidebar, SIGNAL(triggered(bool)), SLOT(toggleDatabaseList()));

	QAction *matches = new QAction(i18n("Match List"), this);
	matches->setCheckable(true);
	actions->addAction("matches", matches);

	connect(matches, SIGNAL(triggered()), SLOT(toggleMatchList()));

	QAction *define = new QAction(i18n("Define"), this);
	actions->addAction("define", define);
	actions->setDefaultShortcut(define, Qt::CTRL + Qt::Key_D);

	connect(define, SIGNAL(triggered()), entry, SLOT(define()));

	QAction *match = new QAction(i18n("Match"), this);
	actions->addAction("match", match);
	actions->setDefaultShortcut(match, Qt::CTRL + Qt::Key_M);

	connect(match, SIGNAL(triggered()), entry, SLOT(match()));

	QAction *clear = new QAction(i18n("Clear History"), this);
	actions->addAction("clear", clear);
// 	clear->setEnabled(false);

	connect(clear, SIGNAL(triggered()), browser, SLOT(clearHistory()));
	connect(clear, SIGNAL(triggered()), matchList, SLOT(clearHistory()));

	QAction *prev = new QAction(i18n("Previous Match"), this);
	actions->addAction("prev", prev);
	actions->setDefaultShortcut(prev, Qt::CTRL + Qt::Key_Left);
	prev->setEnabled(false);

	connect(prev, SIGNAL(triggered()), matchList, SLOT(backward()));
	connect(matchList, SIGNAL(backwardAvailable(bool)),
		prev, SLOT(setEnabled(bool)));
	connect(prev, SIGNAL(triggered()), SLOT(openMatchList()));

	QAction *next = new QAction(i18n("Next Match"), this);
	actions->addAction("next", next);
	actions->setDefaultShortcut(next, Qt::CTRL + Qt::Key_Right);
	next->setEnabled(false);

	connect(next, SIGNAL(triggered()), matchList, SLOT(forward()));
	connect(matchList, SIGNAL(forwardAvailable(bool)),
		next, SLOT(setEnabled(bool)));
	connect(next, SIGNAL(triggered()), SLOT(openMatchList()));

	infoMenu = new ServerInfo(this);
	infoMenu->setText(i18n("Info"));
	infoMenu->setIcon(QIcon::fromTheme("dialog-information"));
	actions->addAction("info", infoMenu);

	connect(infoMenu, SIGNAL(selected(const QString &)),
		SLOT(info(const QString &)));

	hostMenu = new HostMenu(this);
	hostMenu->setText(i18n("Host"));
	hostMenu->setIcon(QIcon::fromTheme("network-server"));
	actions->addAction("host", hostMenu);

	connect(hostMenu, SIGNAL(triggered(const QString &)),
		SLOT(setHost(const QString &)));

	options = new Options(this, server);
	options->setText(i18n("Options"));
	actions->addAction("options", options);

	edit = new QAction(i18n("Edit Databases..."), this);
	actions->addAction("edit", edit);
	edit->setEnabled(false);

	connect(edit, SIGNAL(triggered()), SLOT(setGroups()));

	databaseMenu = new DatabaseMenu(this);
	databaseMenu->setText(i18n("Database"));
	databaseMenu->setIcon(QIcon::fromTheme("network-server-database"));
	actions->addAction("database", databaseMenu);
	databaseMenu->setEnabled(false);

	connect(databaseView, SIGNAL(selected(const QStringList &)),
		databaseMenu, SLOT(setList(const QStringList &)));

	strategyMenu = new Select(this);
	strategyMenu->setText(i18n("Strategy"));
	actions->addAction("strategy", strategyMenu);
	strategyMenu->setEnabled(false);

	QAction *backward = new QAction(QIcon::fromTheme("go-previous"), i18n("Backward"), this);
	actions->addAction("backward", backward);
	actions->setDefaultShortcut(backward, Qt::ALT + Qt::Key_Left);
	backward->setEnabled(false);

	connect(backward, SIGNAL(triggered()), browser, SLOT(backward()));
	connect(browser, SIGNAL(backwardAvailable(bool)),
		backward, SLOT(setEnabled(bool)));
	connect(backward, SIGNAL(triggered()), SLOT(cancel()));

	QAction *forward = new QAction(QIcon::fromTheme("go-next"), i18n("Forward"), this);
	actions->addAction("forward", forward);
	actions->setDefaultShortcut(forward, Qt::ALT + Qt::Key_Right);
	forward->setEnabled(false);

	connect(forward, SIGNAL(triggered()), browser, SLOT(forward()));
	connect(browser, SIGNAL(forwardAvailable(bool)),
		forward, SLOT(setEnabled(bool)));
	connect(forward, SIGNAL(triggered()), SLOT(cancel()));

	History *history = new History(browser);
	history->setText(i18n("History"));
	actions->addAction("history", history);
	history->setEnabled(false);

	connect(browser, SIGNAL(historyChanged(const QString &)),
		history, SLOT(setEnabled()));
	connect(history, SIGNAL(activated()), SLOT(cancel()));

	History *matchHistory = new History(matchList);
	matchHistory->setText(i18n("Match History"));
	actions->addAction("match_history", matchHistory);
	matchHistory->setEnabled(false);

	connect(matchList, SIGNAL(historyChanged(const QString &)),
		matchHistory, SLOT(setEnabled()));
	connect(matchHistory, SIGNAL(activated()), SLOT(openMatchList()));

	stop = new QAction(QIcon::fromTheme("process-stop"), i18n("Stop"), this);
	actions->addAction("stop", stop);
	actions->setDefaultShortcut(stop, Qt::Key_Escape);
	stop->setEnabled(false);

	connect(tracker, SIGNAL(loading(bool)), stop, SLOT(setEnabled(bool)));
	connect(tracker, SIGNAL(loading(bool)), matchList, SLOT(busyCursor(bool)));
	connect(tracker, SIGNAL(loading(bool)), browser, SLOT(busyCursor(bool)));
	connect(stop, SIGNAL(triggered()), SLOT(cancel()));

	QAction *frame = new QAction(i18n("Next Frame"), this);
	actions->addAction("frame", frame);

	connect(frame, SIGNAL(triggered()), SLOT(nextFrame()));

	QLabel *label = new QLabel(i18n("Look for:"), this);
	label->setIndent(10);
	QWidgetAction *lookfor = new QWidgetAction(this);
	lookfor->setText(i18n("Look for"));
	lookfor->setDefaultWidget(label);
	actions->addAction("search", lookfor);

	QWidgetAction *combo = new QWidgetAction(this);
	combo->setText(i18n("Query/Location Bar"));
	combo->setDefaultWidget(entry);
	actions->addAction("query", combo);
	actions->setDefaultShortcut(combo, Qt::CTRL + Qt::Key_L);

	connect(combo, SIGNAL(triggered()), entry, SLOT(setFocus()));
	connect(combo, SIGNAL(triggered()), entry->lineEdit(), SLOT(selectAll()));
	connect(entry, SIGNAL(textActivated(const QString &)), SLOT(define(const QString &)));
	connect(entry, SIGNAL(matchText(const QString &)), SLOT(match(const QString &)));

	QAction *intro = new QAction(i18n("Introduction"), this);
	actions->addAction("intro", intro);

	connect(intro, SIGNAL(triggered()), SLOT(intro()));

	label = new QLabel(i18n("Matching:"), this);
	label->setIndent(10);
	QWidgetAction *matching = new QWidgetAction(this);
	matching->setText(i18n("Matching Strategy"));
	matching->setDefaultWidget(label);
	actions->addAction("matching", matching);

	connect(matching, SIGNAL(triggered()), strategyMenu, SLOT(setFocus()));

	QAction *action = KStandardAction::openNew(this, SLOT(newWindow()), actions);
	action->setText(i18n("New Window"));
	action->setIcon(QIcon::fromTheme("window-new"));

	KStandardAction::saveAs(browser, SLOT(savePage()), actions);
	KStandardAction::print(browser, SLOT(printPage()), actions);
	KStandardAction::printPreview(browser, SLOT(previewPage()), actions);

	KStandardAction::close(this, SLOT(close()), actions);
	KStandardAction::quit(qApp, SLOT(closeAllWindows()), actions);

	KStandardAction::find(search, SLOT(show()), actions);
	KStandardAction::findNext(search, SLOT(next()), actions);
	KStandardAction::findPrev(search, SLOT(prev()), actions);

	KStandardAction::showMenubar(this, SLOT(toggleMenubar()), actions);
	KStandardAction::preferences(this, SLOT(configure()), actions);

	connect(selection, SIGNAL(selectionChanged(const QString &)),
		SLOT(yankClipboard(const QString &)));

	KStandardAction::helpContents(this, SLOT(help()), actions);

	KHelpMenu *helpmenu = new KHelpMenu(this, KAboutData::applicationData());
	actions->addAction(KStandardAction::SwitchApplicationLanguage, helpmenu, SLOT(switchApplicationLanguage()));
	KStandardAction::aboutApp(helpmenu, SLOT(aboutApplication()), actions);
	KStandardAction::aboutKDE(helpmenu, SLOT(aboutKDE()), actions);

	setHelpMenuEnabled(false);
}

void Window::setupBrowser()
{
	connect(browser, SIGNAL(historyChanged(const QString &)),
		entry, SLOT(setEditText(const QString &)));
	connect(browser, SIGNAL(historyChanged(const QString &)),
		SLOT(setCaption(const QString &)));

	connect(browser, SIGNAL(anchorClicked(const QUrl &)),
		SLOT(define(const QUrl &)));
	connect(browser, SIGNAL(highlighted(const QUrl &)),
		status, SLOT(showMessage(const QUrl &)));

	connect(browser, SIGNAL(historyChanged(const QString &)),
		SLOT(setQuery(const QString &)));
	connect(browser, SIGNAL(historyChanged(const QString &)),
		search, SLOT(clear()));

	connect(matchList, SIGNAL(anchorClicked(const QUrl &)),
		SLOT(define(const QUrl &)));
	connect(matchList, SIGNAL(highlighted(const QUrl &)),
		status, SLOT(showMessage(const QUrl &)));
}

void Window::setupDict()
{
	connect(dict, SIGNAL(statusMessage(const QString &)),
		status, SLOT(showMessage(const QString &)));
	connect(dict, SIGNAL(errorMessage(const QString &, int)),
		SLOT(error(const QString &, int)));
	connect(dict, SIGNAL(responseProcessed(const Response &)),
		tracker, SLOT(response(const Response &)));

	connect(dict, SIGNAL(connected(bool, const QString)), SLOT(state(bool)));

	connect(dict, SIGNAL(connected(bool, const QString)),
		status, SLOT(setHost(bool, const QString)));
	connect(dict, SIGNAL(authenticated(bool, const QString)),
		status, SLOT(setUser(bool, const QString)));
	connect(dict, SIGNAL(identified(bool, const QStringList &)),
		options, SLOT(setCapabilities(bool, const QStringList &)));
}

void Window::setupTracker()
{
	connect(tracker, SIGNAL(databases(const List &)),
		SLOT(setDatabaseMenu(const List &)));
	connect(tracker, SIGNAL(strategies(const List &)),
		SLOT(setStrategyMenu(const List &)));
	connect(tracker, SIGNAL(matches(bool)), SLOT(openMatchList(bool)));
	connect(tracker, SIGNAL(progress(int, int)),
		status->progressbar(), SLOT(set(int, int)));

	connect(databaseView, SIGNAL(selected(const QStringList &)),
		tracker, SLOT(setDatabase(const QStringList &)));
	connect(databaseMenu, SIGNAL(selected(const QStringList &)),
		tracker, SLOT(setDatabase(const QStringList &)));
	connect(strategyMenu, SIGNAL(textTriggered(const QString &)),
		tracker, SLOT(setStrategy(const QString &)));

	connect(databaseView, SIGNAL(selected(const QStringList &)),
		server, SLOT(setDatabase(const QStringList &)));
	connect(databaseMenu, SIGNAL(selected(int)),
		server, SLOT(setSelected(int)));
	connect(strategyMenu, SIGNAL(textTriggered(const QString &)),
		server, SLOT(setStrategy(const QString &)));
}

void Window::setDatabaseMenu(const List &list)
{
	QStringList selected = server->conf.database;
	databaseView->setList(list, selected);
	databaseMenu->setList(databaseView->current(), false);
	tracker->setDatabase(databaseMenu->current());
	databaseMenu->setEnabled(true);
	edit->setEnabled(true);
}

void Window::setStrategyMenu(const List &list)
{
	QString selected = server->conf.strategy;
	strategyMenu->setList(list, selected);
	tracker->setStrategy(strategyMenu->current());
}

void Window::setGroups()
{
	Edit groups(server->conf.groups);
	EditConf groupconf(this);
	groupconf.set(&groups, databaseView->list());

	if (!groupconf.exec()) return;

	groups.write();
	server->setGroups(groups.groups());
	databaseMenu->setGroups(*groups.list(), edit);
	databaseMenu->setList(databaseView->current(), false);
	tracker->setDatabase(databaseMenu->current());
}

void Window::define(const QString &text)
{
	query = text.simplified();
	query.truncate(Entry::maxLen);
	if (query.isEmpty()) return;

	if (stop->isEnabled())
		cancel();
	browser->setFocus();

	QUrl url(query);
	if (url.isRelative())
		tracker->define(query);
	else if (url.scheme() == "about")
		about(query);
	else if (url.scheme() == "dict")
		dicturl(url);
	else if (url.scheme() == "http")
		warning(text, Page::UrlRequested);
	else if (url.host().isEmpty())
		tracker->define(query);
	else
		error(i18n("Unsupported protocol %1", url.scheme()), Page::UnsupportedScheme);
}

void Window::define(const QUrl &link)
{
	tracker->setMatchList(sender() == matchList);
	if (link.scheme() == "http")
		QDesktopServices::openUrl(link);
	else
		define(link.toString());
}

void Window::match(const QString &text)
{
	query = text.simplified();
	query.truncate(Entry::maxLen);
	if (query.isEmpty()) return;

	if (stop->isEnabled())
		cancel();
	browser->setFocus();
	tracker->match(query, strategy());
}

void Window::about(const QString &query)
{
	QString text = query.mid(6);
	if (text.isEmpty())
		page->about(query, Dikt::Version);
	else if (!text.compare(Dikt::AppName, Qt::CaseInsensitive)) intro();
	else if (!text.compare("help", Qt::CaseInsensitive)) page->help(query);
	else if (infoMenu->list().contains(text))
		info(query, text);
	else if (databaseView->current(true).contains(text))
		info(query, "info " + text);
	else error(i18n("No such database %1", text), Page::UnknownDatabase);
}

void Window::info(const QString &query, const QString &text)
{
	if (stop->isEnabled())
		cancel();
	browser->setFocus();
	tracker->info(query, text);
}

void Window::warning(const QString &text, int code)
{
	page->setTitle(text.simplified());
	page->warning(text, (Page::Warning)code);
	browser->setFocus();
}

void Window::error(const QString &text, int code)
{
	page->setTitle(query);
	page->error(text, code);
	browser->setFocus();
	tracker->cancel();
}

void Window::cancel()
{
	if (stop->isEnabled())
		tracker->cancel();
	status->progressbar()->hide();
}

void Window::state(bool connected)
{
	if (!connected) cancel();
}

void Window::dicturl(const QUrl &url)
{
	QString host = url.host();
	if (host.isEmpty()) {
		error(i18n("Malformed Url"), Page::MalformedURL);
		return;
	}
	int port = url.port(Connection::DefaultPort);
	if (port != Connection::DefaultPort)
		host.append(':').append(QString::number(port));

	if (host != dicthost())
		setHost(host);

	QString path = url.path();
	if (path.size() <= 1)
		info(url.toString(), "status");
	else
		define(path.mid(1));
}

void Window::setHost(const QString &host)
{
	cancel();
	server->conf.write();

	databaseView->clear();
	strategyMenu->clear();
	databaseMenu->setEnabled(false);
	strategyMenu->setEnabled(false);
	edit->setEnabled(false);

	hostConfig(host);
	tracker->restart();
	page->textFormatter()->clear();
}

void Window::yankClipboard(const QString &text)
{
	if (QUrl(text.simplified()).scheme() == "http")
		warning(text, Page::UrlSelection);
	else if (text.length() > selection->conf().warnLen)
		warning(text, Page::LongSelection);
	else
		define(text);
}

void Window::toggleMenubar()
{
	menuBar()->setVisible(!menuBar()->isVisible());
}

void Window::setupMenubar()
{
	QAction *menubar = action(KStandardAction::name(KStandardAction::ShowMenubar));
	menubar->setChecked(menuBar()->isVisible());

	QList<QAction *> list;
	list << menubar << yank << hilite;
	list << action("text") << action("format");
	browser->addActions(list);
}

void Window::setSidebar(QWidget *widget)
{
	wconf.widths = splitter->sizes();

	sidebar->setVisible(!widget->isVisible());
	sidebar->setCurrentWidget(widget);

	if (sidebar->isVisible())
		widget->setFocus();
	else
		browser->setFocus();

	setSidebarMenu();
}

void Window::setSidebarMenu()
{
	action("sidebar")->setChecked(sidebar->isVisible() && !sidebar->currentIndex());
	action("matches")->setChecked(sidebar->isVisible() && sidebar->currentIndex());
}

void Window::setSidebarSide(int side)
{
	splitter->insertWidget(side, sidebar);
}

void Window::openMatchList(bool focus)
{
	if(!matchList->isVisible()) {
		sidebar->show();
		sidebar->setCurrentWidget(matchList);
		setSidebarMenu();
	}
	if (focus) matchList->setFocus();
}

void Window::enableMatchList(bool enable)
{
	action("matches")->setEnabled(enable);
	if (!enable) {
		((History *)action("match_history"))->setEnabled(enable);
		action("prev")->setEnabled(enable);
		action("next")->setEnabled(enable);
		action("matches")->setChecked(enable);
		if (matchList->isVisible())
			sidebar->hide();
		matchList->clearHistory();
	}
}

void Window::nextFrame()
{
	if (browser->hasFocus())
		if (sidebar->isVisible())
			sidebar->currentWidget()->setFocus();
		else if (search->isVisible())
			search->setFocus();
		else
			entry->setFocus();
	else
		browser->setFocus();
}

void Window::configure()
{
	writeConfig();
	if (!settings) {
		settings = new Settings(this);
		connect(settings, SIGNAL(accepted()), SLOT(saveConfig()));
		connect(settings, SIGNAL(applied()), SLOT(saveConfig()));
	}
	settings->read();
	settings->show();
}

void Window::saveConfig()
{
	settings->write();
	hostMenu->setup(dicthost());
	writeConfig();
	loadConfig();
}

void Window::loadConfig()
{
	browser->setup();
	matchList->setup();

	BrowseConf bconf;
	bconf.read();
	setSidebarSide(bconf.sidebarSide);

	tracker->setup();
	tracker->setMatchPage(matchList->currentPage());
	enableMatchList(tracker->matchList());

	selection->setup();
	yank->setChecked(selection->conf().yank);
	browser->setHilite(bconf.hilite);
	hilite->setChecked(bconf.hilite);

	dict->setClient(bconf.userAgent());
}

void Window::reformat()
{
	browser->reformat();
	define(query);
}

}
