/*
 * knewsticker.cpp
 *
 * Copyright (c) 2000, 2001 Frerich Raabe <raabe@kde.org>
 *
 * 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. For licensing and distribution details, check the
 * accompanying file 'COPYING'.
 */
#include "knewsticker.h"
#include "newsengine.h"
#include "newsscroller.h"
#include "configaccess.h"
#include "newsiconmgr.h"
#include "knewstickerconfig.h"

#include <tdeaboutapplication.h>
#include <tdeapplication.h>
#include <karrowbutton.h>
#include <kbugreport.h>
#include <kdebug.h>
#include <kiconloader.h>
#include <knotifyclient.h>
#include <kprocess.h>
#include <tdeprotocolmanager.h>
#include <kstandarddirs.h>
#include <tdestartupinfo.h>

#include <tqcursor.h>
#include <tqlayout.h>
#include <tqtimer.h>
#include <tqtooltip.h>

#include <dcopclient.h>

KNewsTicker::KNewsTicker(const TQString &cfgFile, Type t, int actions, TQWidget *parent, const char *name)
	: ConfigIface(), DCOPObject("KNewsTicker"),
	KPanelApplet(cfgFile, t, actions, parent, name),
	m_instance(new TDEInstance("knewsticker")),
	m_dcopClient(new DCOPClient()),
	m_cfg(new ConfigAccess(config())),
	m_newsTimer(new TQTimer(this)),
	m_updateTimer(new TQTimer(this)),
	m_newsIconMgr(NewsIconMgr::self()),
	m_aboutData(new TDEAboutData("knewsticker", I18N_NOOP("KNewsTicker"), "v0.2",
			I18N_NOOP("A news ticker applet."), TDEAboutData::License_BSD,
			I18N_NOOP("(c) 2000, 2001 The KNewsTicker developers")))
{
	TQHBoxLayout *layout = new TQHBoxLayout(this);

	m_contextMenu = new KNewsTickerMenu(this);
	connect(m_contextMenu, TQ_SIGNAL(aboutToHide()),
			TQ_SLOT(slotContextMenuAboutToHide()));
	setCustomMenu(m_contextMenu);

	m_arrowButton = new KArrowButton(this);
	TQToolTip::add(m_arrowButton, i18n("Show menu"));
	connect(m_arrowButton, TQ_SIGNAL(clicked()), TQ_SLOT(slotArrowButtonPressed()));
	m_arrowButton->setFocusPolicy(TQWidget::NoFocus);
	setupArrowButton();
	layout->addWidget(m_arrowButton);

	m_scroller = new NewsScroller(this, m_cfg);
	layout->addWidget(m_scroller);

	m_dcopClient->registerAs("knewsticker", false);

	TQToolTip::add(m_scroller, TQString());
	connect(m_scroller, TQ_SIGNAL(contextMenu()), TQ_SLOT(slotOpenContextMenu()));

	connect(m_newsTimer, TQ_SIGNAL(timeout()), TQ_SLOT(slotUpdateNews()));

	connect(m_updateTimer, TQ_SIGNAL(timeout()), TQ_SLOT(slotNotifyOfFailures()));

	m_aboutData->addAuthor("Frerich Raabe", I18N_NOOP("Original author"),
			"raabe@kde.org");
	m_aboutData->addAuthor("Malte Starostik", I18N_NOOP("Hypertext headlines"
				" and much more"), "malte@kde.org");
	m_aboutData->addAuthor("Wilco Greven", I18N_NOOP("Mouse wheel support"),
			"greven@kde.org");
	m_aboutData->addAuthor("Adriaan de Groot", I18N_NOOP("Rotated scrolling text"
			" modes"), "adridg@sci.kun.nl");

	reparseConfig();

	TDEStartupInfo::appStarted();
}

KNewsTicker::~KNewsTicker()
{
	delete m_cfg;
	delete m_dcopClient;
}

int KNewsTicker::heightForWidth(int) const
{
	return m_scroller->sizeHint().height() + m_arrowButton->height();
}

int KNewsTicker::widthForHeight(int) const
{
	return m_scroller->sizeHint().width() + m_arrowButton->width();
}

void KNewsTicker::preferences()
{
	KNewsTickerConfig dlg(m_cfg, this);
	if (dlg.exec() == TQDialog::Accepted) {
		reparseConfig();
	}
}

void KNewsTicker::about()
{
	TDEAboutApplication aboutDlg(m_aboutData);
	aboutDlg.exec();
}

void KNewsTicker::help()
{
	kapp->invokeHelp(TQString(), TQString::fromLatin1("knewsticker"));
}

void KNewsTicker::reportBug()
{
	KBugReport bugReport(this, true, m_aboutData);
	bugReport.exec();
}

void KNewsTicker::reparseConfig()
{
	m_cfg->reparseConfiguration();
	m_newsSources.clear();

	TQStringList newsSources = m_cfg->newsSources();
	TQStringList::ConstIterator it = newsSources.begin();
	TQStringList::ConstIterator end = newsSources.end();
	for (; it != end; ++it) {
		NewsSourceBase::Ptr ns = m_cfg->newsSource((*it));
		if (!ns->data().enabled)
			continue;

		connect(ns, TQ_SIGNAL(newNewsAvailable(const NewsSourceBase::Ptr &, bool)),
				TQ_SLOT(slotNewsSourceUpdated(const NewsSourceBase::Ptr &, bool)));
		connect(ns, TQ_SIGNAL(invalidInput(const NewsSourceBase::Ptr &)),
				TQ_SLOT(slotNewsSourceFailed(const NewsSourceBase::Ptr &)));
		m_newsSources.append(ns);
	}

	setOfflineMode(m_cfg->offlineMode());
	if (!m_cfg->offlineMode())
		slotUpdateNews();
}

void KNewsTicker::slotUpdateNews()
{
	kdDebug(5005) << "slotUpdateNews()" << endl;
	m_newNews = false;

	m_updateTimer->start(KProtocolManager::responseTimeout(), true);

	m_failedNewsUpdates.clear();
	m_pendingNewsUpdates.clear();

	m_scroller->clear();

	NewsSourceBase::List::Iterator it = m_newsSources.begin();
	NewsSourceBase::List::Iterator end = m_newsSources.end();
	for (; it != end; ++it) {
		m_pendingNewsUpdates += (*it)->data().name;
		(*it)->retrieveNews();
		(*it)->getIcon();
	}
	kdDebug(5005) << "m_pendingNewsUpdates = " << m_pendingNewsUpdates.join(",")
	              << endl;
}

void KNewsTicker::slotNewsSourceUpdated(const NewsSourceBase::Ptr &ns,
		bool newNews)
{
	kdDebug(5005) << "slotNewsSourceUpdate()" << endl;
	if (newNews)
		m_newNews = true;

	if (!ns->articles().isEmpty())
		if (m_cfg->scrollMostRecentOnly())
			m_scroller->addHeadline(ns->articles().first());
		else {
			Article::List articles = ns->articles();
			Article::List::ConstIterator artIt = articles.begin();
			Article::List::ConstIterator artEnd = articles.end();
			for (; artIt != artEnd; ++artIt)
				m_scroller->addHeadline(*artIt);
		}

	m_scroller->reset(true);

	m_pendingNewsUpdates.remove(ns->data().name);
	kdDebug(5005) << "Updated news source: '" << ns->data().name << "'" << "\n"
	              << "m_pendingNewsUpdates = " << m_pendingNewsUpdates.join(",") << "\n"
	              << "m_failedNewsUpdates = " << m_failedNewsUpdates.join(",")
	              << endl;

	if (!m_pendingNewsUpdates.isEmpty())
		return;

	m_updateTimer->stop();

	if (!m_failedNewsUpdates.isEmpty())
		slotNotifyOfFailures();

	if (m_newNews) {
		KNotifyClient::Instance instance(m_instance);
		KNotifyClient::event(winId(), TQString::fromLatin1("NewNews"));
	}
}

void KNewsTicker::slotNewsSourceFailed(const NewsSourceBase::Ptr &ns)
{
	m_failedNewsUpdates += ns->newsSourceName();
	slotNewsSourceUpdated(ns);
}

void KNewsTicker::mousePressEvent(TQMouseEvent *e)
{
	if (e->button() == TQt::RightButton)
		slotOpenContextMenu();
}

void KNewsTicker::slotOpenContextMenu()
{
	m_contextMenu->setFullMenu(true);
	m_contextMenu->exec(TQCursor::pos());
}

void KNewsTicker::slotArrowButtonPressed()
{
	TQPoint pos(m_arrowButton->mapToGlobal(TQPoint(0, 0)));
	TQSize size(m_arrowButton->size());

	if (position() == KPanelApplet::pTop) {
		pos.setY(pos.y() + size.height() + 2);
	} else if (position() == KPanelApplet::pBottom) {
		const int y = pos.y() - m_contextMenu->sizeHint().height() - 2;
		pos.setY(TQMAX(0, y));
	} else if (position() == KPanelApplet::pLeft ) {
		pos.setX(pos.x() + size.width() + 2);
	} else { // position() == KPanelApplet::pRight
		const int x = pos.x() - m_contextMenu->sizeHint().width() - 2;
		pos.setX(TQMAX(0, x));
	}

	m_contextMenu->setFullMenu(true);
	m_contextMenu->exec(pos);
}

void KNewsTicker::positionChange(Position)
{
	delete layout();

	TQBoxLayout *layout;

	if (orientation() ==TQt::Horizontal)
		layout = new TQHBoxLayout(this);
	else
		layout = new TQVBoxLayout(this);

	if (m_arrowButton) {
		layout->addWidget(m_arrowButton);
		setupArrowButton();
	}

	layout->addWidget(m_scroller);
}

void KNewsTicker::slotContextMenuAboutToHide()
{
	if (m_arrowButton)
		m_arrowButton->setDown(false);
}

void KNewsTicker::slotNotifyOfFailures()
{
	KNotifyClient::Instance instance(m_instance);
	TQString notification = TQString();

	if (m_failedNewsUpdates.count() == 1)
		notification = i18n("<qt>Could not update news site '%1'.<br>"
					"The supplied resource file is probably invalid or"
					" broken.</qt>").arg(m_failedNewsUpdates.first());
	else if (m_failedNewsUpdates.count() > 1 && m_failedNewsUpdates.count() < 8) {
		notification = i18n("<qt>The following news sites had problems. Their"
				" resource files are probably invalid or broken.<ul>");
		TQStringList::ConstIterator it = m_failedNewsUpdates.begin();
		TQStringList::ConstIterator end = m_failedNewsUpdates.end();
		for (; it != end; ++it)
			notification += TQString::fromLatin1("<li>%1</li>").arg(*it);
		notification += TQString::fromLatin1("</ul></qt>");
	} else
		notification = i18n("Failed to update several news"
					" sites. The Internet connection might be cut.");

	KNotifyClient::event(winId(), TQString::fromLatin1("InvalidRDF"), notification);
}

void KNewsTicker::setInterval(const uint interval)
{
	m_cfg->setInterval(interval);
	if ( interval > 4 )
		m_newsTimer->changeInterval(interval * 60 * 1000);
}

void KNewsTicker::setScrollingSpeed(const uint scrollingSpeed)
{
	m_cfg->setScrollingSpeed(scrollingSpeed);
	m_scroller->reset(true);
}

void KNewsTicker::setMouseWheelSpeed(const uint mouseWheelSpeed)
{
	m_cfg->setMouseWheelSpeed(mouseWheelSpeed);
}

void KNewsTicker::setScrollingDirection(const uint scrollingDirection)
{
	m_cfg->setScrollingDirection(scrollingDirection);
	m_scroller->reset(true);
}

void KNewsTicker::setCustomNames(bool customNames)
{
	m_cfg->setCustomNames(customNames);
}

void KNewsTicker::setScrollMostRecentOnly(bool scrollMostRecentOnly)
{
	m_cfg->setScrollMostRecentOnly(scrollMostRecentOnly);
	m_scroller->reset(true);
}

void KNewsTicker::setOfflineMode(bool offlineMode)
{
	if (offlineMode)
		m_newsTimer->stop();
	else
		if ( m_cfg->interval() > 4 )
			m_newsTimer->start(m_cfg->interval() * 1000 * 60);

	m_cfg->setOfflineMode(offlineMode);
}

void KNewsTicker::setUnderlineHighlighted(bool underlineHighlighted)
{
	m_cfg->setUnderlineHighlighted(underlineHighlighted);
	m_scroller->reset(true);
}

void KNewsTicker::setShowIcons(bool showIcons)
{
	m_cfg->setShowIcons(showIcons);
	m_scroller->reset(true);
}

void KNewsTicker::setSlowedScrolling(bool slowedScrolling)
{
	m_cfg->setSlowedScrolling(slowedScrolling);
}

void KNewsTicker::setForegroundColor(const TQColor &foregroundColor)
{
	m_cfg->setForegroundColor(foregroundColor);
	m_scroller->reset(false);
}

void KNewsTicker::setBackgroundColor(const TQColor &backgroundColor)
{
	m_cfg->setBackgroundColor(backgroundColor);
	m_scroller->reset(false);
}

void KNewsTicker::setHighlightedColor(const TQColor &highlightedColor)
{
	m_cfg->setHighlightedColor(highlightedColor);
	m_scroller->reset(false);
}

void KNewsTicker::setupArrowButton()
{
	TQt::ArrowType at;

	if (orientation() ==TQt::Horizontal) {
		m_arrowButton->setFixedWidth(12);
		m_arrowButton->setMaximumHeight(128);
		at = (position() == KPanelApplet::pTop ? TQt::DownArrow : TQt::UpArrow);
	} else {
		m_arrowButton->setMaximumWidth(128);
		m_arrowButton->setFixedHeight(12);
		at = (position() == KPanelApplet::pLeft ? TQt::RightArrow : TQt::LeftArrow);
	}
	m_arrowButton->setArrowType(at);
}

KNewsTickerMenu::KNewsTickerMenu(KNewsTicker *parent, const char *name)
  : TDEPopupMenu(parent, name),
	m_parent(parent),
	m_fullMenu(false)
{
	populateMenu();
}

void KNewsTickerMenu::populateMenu()
{
	clear();

	/*
	 * Perhaps this hardcoded stuff should be replaced by some kind of
	 * themeing functionality?
	 */
	const TQPixmap lookIcon = SmallIcon(TQString::fromLatin1("viewmag"));
	const TQPixmap newArticleIcon = SmallIcon(TQString::fromLatin1("application-vnd.tde.info"));
	const TQPixmap oldArticleIcon = SmallIcon(TQString::fromLatin1("mime_empty"));
	const TQPixmap noArticlesIcon = SmallIcon(TQString::fromLatin1("remove"));

	unsigned int articleIdx = 0;
	const NewsSourceBase::List sources = m_parent->m_newsSources;
	NewsSourceBase::List::ConstIterator nIt = sources.begin();
	for (; nIt != sources.end(); ++nIt) {
		NewsSourceBase::Ptr ns = *nIt;

		TDEPopupMenu *submenu = new TDEPopupMenu;
		int checkNewsId = submenu->insertItem(lookIcon, i18n("Check News"), this, TQ_SLOT(slotCheckNews(int)), 0, sources.findIndex(ns) + 1000);
		setItemParameter(checkNewsId, sources.findIndex(ns));

		submenu->insertSeparator();

		if (m_parent->m_pendingNewsUpdates.contains(ns->newsSourceName())) {
			submenu->insertItem(noArticlesIcon, i18n("Currently Being Updated, No Articles Available"));
		} else if (!ns->articles().isEmpty()) {
			const Article::List articles = ns->articles();
			Article::List::ConstIterator artIt = articles.begin();
			for (; artIt != articles.end(); ++artIt) {
				Article::Ptr a = *artIt;
				TQString headline = a->headline().replace('&', "&&");
				int id;
				if ( a->read() )
					id = submenu->insertItem(oldArticleIcon, headline, this, TQ_SLOT(slotOpenArticle(int)), 0, articleIdx+2000);
				else
					id = submenu->insertItem(newArticleIcon, headline, this, TQ_SLOT(slotOpenArticle(int)), 0, articleIdx+2000);
				kdDebug( 5005 ) << "Setting articles index for " << a->headline() << " to " << articleIdx << endl;
				setItemParameter( id, articleIdx++ );
			}
		} else {
			submenu->insertItem(noArticlesIcon, i18n("No Articles Available"));
		}

		insertItem(ns->icon(), ns->newsSourceName().replace('&', "&&"), submenu);
	}

	if (!m_parent->m_cfg->newsSources().isEmpty())
		insertSeparator();

	insertItem(lookIcon, i18n("Check News"), m_parent, TQ_SLOT(slotUpdateNews()));
	int i = insertItem(i18n("Offline Mode"), this, TQ_SLOT(slotToggleOfflineMode()), 0, 4711 );
	setItemChecked(i, m_parent->m_cfg->offlineMode());

	if (m_fullMenu) {
		insertSeparator();

		const TQPixmap logoIcon = SmallIcon(TQString::fromLatin1("knewsticker"));
		const TQPixmap helpIcon = SmallIcon(TQString::fromLatin1("help"));
		const TQPixmap confIcon = SmallIcon(TQString::fromLatin1("configure"));

		insertTitle(logoIcon, i18n("KNewsTicker"), 0, 0);

		insertItem(helpIcon, i18n("Help"), this, TQ_SLOT(slotShowHelp()));
		insertItem(logoIcon, i18n("About KNewsTicker"), this, TQ_SLOT(slotShowAbout()));
		insertSeparator();
		insertItem(confIcon, i18n("Configure KNewsTicker..."), this, TQ_SLOT(slotConfigure()));
	}
}

void KNewsTickerMenu::slotShowHelp()
{
	m_parent->help();
}

void KNewsTickerMenu::slotShowAbout()
{
	m_parent->about();
}

void KNewsTickerMenu::slotConfigure()
{
	m_parent->preferences();
}

void KNewsTickerMenu::slotToggleOfflineMode()
{
	m_parent->setOfflineMode(!m_parent->m_cfg->offlineMode());
	setItemChecked( indexOf( 4711 ), !m_parent->m_cfg->offlineMode() );
}

void KNewsTickerMenu::slotCheckNews(int idx)
{
	m_parent->m_newsSources[ idx - 1000 ]->retrieveNews();
}

void KNewsTickerMenu::slotOpenArticle(int idx)
{
	unsigned int i = idx - 2000;
	const NewsSourceBase::List sources = m_parent->m_newsSources;
	NewsSourceBase::List::ConstIterator it = sources.begin();
	while ( it != sources.end() ) {
		if ( ( *it )->articles().isEmpty() ) {
			++it;
			continue;
		}

		if ( i <= ( *it )->articles().count() - 1 )
			break;

		i -= ( *it )->articles().count();

		++it;
	}

	if ( it == sources.end() )
		return;

	( *it )->articles()[ i ]->open();
}

extern "C"
{
	KDE_EXPORT KPanelApplet* init(TQWidget *parent, const TQString &configFile)
	{
		TDEGlobal::locale()->insertCatalogue(TQString::fromLatin1("knewsticker"));
		return new KNewsTicker(configFile, KPanelApplet::Stretch,
				KPanelApplet::Preferences | KPanelApplet::About |
                KPanelApplet::Help | KPanelApplet::ReportBug,
			    parent, "knewsticker");
	}
}

#include "knewsticker.moc"
