/***************************************************************************
    begin                : Tue Nov 25 2003
    copyright            : (C) 2003 by Jeroen Wijnhout
    email                : Jeroen.Wijnhout@kdemail.net
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "kiletoolmanager.h"

#include <stdlib.h> //for getenv()
 
#include <tqstring.h>
#include <tqfileinfo.h>
#include <tqwidgetstack.h>
#include <tqtimer.h>
#include <tqregexp.h>
#include <tqthread.h>

#include <tdeaction.h>
#include <ktextedit.h>
#include <tdeconfig.h>
#include "kiledebug.h"
#include <tdelocale.h>
#include <tdeparts/partmanager.h>
#include <tdemessagebox.h>
#include <tdeapplication.h>
#include <tdesimpleconfig.h>

#include "kileinfo.h"
#include "kileconfig.h"
#include "kileproject.h"
#include "kiletool_enums.h"
#include "kilestdtools.h"
#include "kilelogwidget.h"
#include "kileoutputwidget.h"
#include "kiledocmanager.h"
#include "kilesidebar.h"

namespace KileTool
{
	QueueItem::QueueItem(Base *tool, const TQString & cfg, bool block) : m_tool(tool), m_cfg(cfg), m_bBlock(block)
	{
	}

	QueueItem::~QueueItem()
	{
		delete m_tool;
	}

	Base* Queue::tool() const
	{
		if ( head() )
			return head()->tool();
		else
			return 0;
	}

	const TQString Queue::cfg() const
	{
		if ( head() )
			return head()->cfg();
		else
			return TQString();
	}

	bool Queue::shouldBlock() const
	{
		if ( head() )
			return head()->shouldBlock();
		else
			return false;
	}

	void Queue::enqueueNext(QueueItem *item)
	{
		if ( count() < 2 )
			enqueue(item);
		else
		{
			QueueItem *headitem = dequeue();
			Queue *oldqueue = new Queue(*this);

			setAutoDelete(false); clear();
			KILE_DEBUG() << "\tenqueueing: " << headitem->tool()->name() << endl;
			enqueue(headitem);
			KILE_DEBUG() << "\tenqueueing: " << item->tool()->name() << endl;
			enqueue(item);
			while ( oldqueue->head() )
			{
				KILE_DEBUG() << "\tenqueueing: " << oldqueue->head()->tool()->name() << endl;
				enqueue(oldqueue->dequeue());
			}
		}
	}

	Manager::Manager(KileInfo *ki, TDEConfig *config, KileWidget::LogMsg *log, KileWidget::Output *output, KParts::PartManager *manager, TQWidgetStack *stack, TDEAction *stop, uint to) :
		m_ki(ki),
		m_config(config),
		m_log(log),
		m_output(output),
		m_pm(manager),
		m_stack(stack),
		m_stop(stop),
		m_bClear(true),
		m_nLastResult(Success),
		m_nTimeout(to)
	{
		m_timer = new TQTimer(this);
		connect(m_timer, TQ_SIGNAL(timeout()), this, TQ_SLOT(enableClear()));
		connect(stop, TQ_SIGNAL(activated()), this, TQ_SLOT(stop()));
	}
	
	Manager::~Manager() {}

	bool Manager::shouldBlock()
	{
		return m_queue.shouldBlock();
	}

	void Manager::enableClear()
	{
		m_bClear = true;
	}

	bool Manager::queryContinue(const TQString & question, const TQString & caption /*= TQString()*/)
	{
		return (KMessageBox::warningContinueCancel(m_stack, question, caption, KStdGuiItem::cont(), "showNotALaTeXRootDocumentWarning") == KMessageBox::Continue);
	}

	int Manager::run(const TQString &tool, const TQString & cfg, bool insertNext /*= false*/, bool block /*= false*/)
	{
		if (!m_factory)
		{
			m_log->printMsg(Error, i18n("No factory installed, contact the author of Kile."));
			return ConfigureFailed;
		}
	
		Base* pTool = m_factory->create(tool);
		if (!pTool)
		{
			m_log->printMsg(Error, i18n("Unknown tool %1.").arg(tool));
			return ConfigureFailed;
		}
		
		return run(pTool, cfg, insertNext, block);
	}

	int Manager::run(Base *tool, const TQString & cfg, bool insertNext /*= false*/, bool block /*= false*/)
	{
		KILE_DEBUG() << "==KileTool::Manager::run(Base *)============" << endl;
		if (m_bClear && (m_queue.count() == 0) )
		{
			m_log->clear();
			m_ki->setLogPresent(false);
			m_output->clear();
		}

		if ( tool->needsToBePrepared() )
			tool->prepareToRun(cfg);
    else
      tool->configure(cfg);

		//FIXME: shouldn't restart timer if a Sequence command takes longer than the 10 secs
		//restart timer, so we only clear the logs if a tool is started after 10 sec.
		m_bClear=false;
		m_timer->start(m_nTimeout);

		if ( insertNext )
			m_queue.enqueueNext(new QueueItem(tool, cfg, block));
		else
			m_queue.enqueue(new QueueItem(tool, cfg, block));

		KILE_DEBUG() << "\tin queue: " << m_queue.count() << endl;
		if ( m_queue.count() == 1 )
			return runNextInQueue();
		else if ( m_queue.count() > 1 )
			return Running;
		else
			return ConfigureFailed;
	}

	int Manager::runNext(const TQString &tool , const TQString &config, bool block /*= false*/) 
	{
		return run(tool, config, true, block);
	}

	int Manager::runNext(Base *tool, const TQString &config, bool block /*= false*/) 
	{
		return run(tool, config, true, block); 
	}

	int Manager::runBlocking(const TQString &tool, const TQString &config /*= TQString()*/, bool insertAtTop /*= false*/)
	{
		if ( run(tool, config, insertAtTop, true) == Running )
			return lastResult();
		else
			return Failed;
	}

	int Manager::runNextBlocking(const TQString &tool, const TQString &config)
	{
		return runBlocking(tool, config, true);
	}

	int Manager::runNextInQueue()
	{
		Base *head = m_queue.tool();
		if (head)
		{
			if (m_log->lines() > 1) 
				m_log->append("\n");

	        if ( ! head->isPrepared() )
    	        head->prepareToRun();

			int status;
			if ( (status=head->run()) != Running ) //tool did not even start, clear queue
			{
				stop();
				m_queue.setAutoDelete(true); m_queue.clear(); m_queue.setAutoDelete(false);
				return status;
			}

			emit(toolStarted());

			return Running;
		}

		return ConfigureFailed;
	}

	void Manager::initTool(Base *tool)
	{
		tool->setInfo(m_ki);
		tool->setConfig(m_config);

		connect(tool, TQ_SIGNAL(message(int, const TQString &, const TQString &)), m_log, TQ_SLOT(printMsg(int, const TQString &, const TQString &)));
		connect(tool, TQ_SIGNAL(output(const TQString &)), m_output, TQ_SLOT(receive(const TQString &)));
		connect(tool, TQ_SIGNAL(done(Base*,int)), this, TQ_SLOT(done(Base*, int)));
		connect(tool, TQ_SIGNAL(start(Base* )), this, TQ_SLOT(started(Base*)));
		connect(tool, TQ_SIGNAL(requestSaveAll(bool, bool)), this, TQ_SIGNAL(requestSaveAll(bool, bool)));
	}

	void Manager::started(Base *tool)
	{
		KILE_DEBUG() << "STARTING tool: " << tool->name() << endl;
		m_stop->setEnabled(true);

		if (tool->isViewer()) 
		{
			if ( tool == m_queue.tool() ) m_queue.dequeue();
			m_stop->setEnabled(false);
			TQTimer::singleShot(100, this, TQ_SLOT(runNextInQueue()));
		}
	}

	void Manager::stop()
	{
		m_stop->setEnabled(false);
		if ( m_queue.tool() )
			m_queue.tool()->stop();
	}

	void Manager::done(Base *tool, int result)
	{
		m_stop->setEnabled(false);
		m_nLastResult = result;

		if ( tool != m_queue.tool() ) //oops, tool finished async, could happen with view tools
		{
			delete tool;
			return;
		}

		delete m_queue.dequeue();

		if ( result == Aborted)
			tool->sendMessage(Error, i18n("Aborted"));

		if ( result != Success && result != Silent ) //abort execution, delete all remaining tools
		{
			m_queue.setAutoDelete(true); m_queue.clear(); m_queue.setAutoDelete(false);
			m_ki->outputView()->showPage(m_log);
		}
		else //continue
			runNextInQueue();
	}

	TQString Manager::currentGroup(const TQString &name, bool usequeue, bool useproject)
	{
		if (useproject)
		{
			KileProject *project = m_ki->docManager()->activeProject();
			if (project)
			{
				TQString cfg = configName(name, dynamic_cast<TDEConfig*>(project->config()));
				if ( cfg.length() > 0 ) return groupFor(name, cfg);
			}
		}

		if (usequeue && m_queue.tool() && (m_queue.tool()->name() == name) && (!m_queue.cfg().isNull()) )
			return groupFor(name, m_queue.cfg());
		else
			return groupFor(name, m_config);
	}

	bool Manager::retrieveEntryMap(const TQString & name, Config & map, bool usequeue, bool useproject, const TQString & cfg /*= TQString()*/)
	{
		TQString group = (cfg == TQString() )
                    ? currentGroup(name, usequeue, useproject) : groupFor(name, cfg);

		KILE_DEBUG() << "==KileTool::Manager::retrieveEntryMap=============" << endl;
		KILE_DEBUG() << "\t" << name << " => " << group << endl;
		if ( m_config->hasGroup(group) )
		{
			map = m_config->entryMap(group);

			//use project overrides
			KileProject *project = m_ki->docManager()->activeProject();
			if ( useproject && project)
			{
				TDEConfig *prjcfg = dynamic_cast<TDEConfig*>(project->config());
				if ( prjcfg )
				{
					TQString grp = groupFor(name, prjcfg);
					Config prjmap = prjcfg->entryMap(grp);
					for (Config::Iterator it  = prjmap.begin(); it != prjmap.end(); ++it)
					{
						map[it.key()] = it.data();
					}
				}
			}
		}
		else
			return false;

		return true;
	}

	void Manager::saveEntryMap(const TQString & name, Config & map, bool usequeue, bool useproject)
	{
		KILE_DEBUG() << "==KileTool::Manager::saveEntryMap=============" << endl;
		TQString group = currentGroup(name, usequeue, useproject);
		KILE_DEBUG() << "\t" << name << " => " << group << endl;
		m_config->setGroup(group);

		Config::Iterator it;
		for ( it = map.begin() ; it != map.end(); ++it)
		{
			if ( ! it.data().isEmpty() )
				m_config->writeEntry(it.key(), it.data());
		}
	}

	bool Manager::configure(Base *tool, const TQString & cfg /*=TQString()*/)
	{
		KILE_DEBUG() << "==KileTool::Manager::configure()===============" << endl;
		//configure the tool

		Config map;

		if ( ! retrieveEntryMap(tool->name(), map, true, true, cfg) )
		{
			m_log->printMsg(Error, i18n("Cannot find the tool %1 in the configuration database.").arg(tool->name()));
			return false;
		}

		tool->setEntryMap(map);

		return true;
	}

	void Manager::wantGUIState(const TQString & state)
	{
		KILE_DEBUG() << "REQUESTED state: " << state << endl;
		emit(requestGUIState(state));
	}

	TQStringList toolList(TDEConfig *config, bool menuOnly)
	{
		KILE_DEBUG() << "==KileTool::toolList()==================" << endl;

		TQStringList groups = config->groupList(), tools;
		TQRegExp re = TQRegExp("Tool/(.+)/.+");

		for ( uint i = 0; i < groups.count(); ++i )
		{
			if ( re.exactMatch(groups[i]) )
			{
				if ( ! groups[i].endsWith(configName(re.cap(1), config)) ) continue;

				if ( (! menuOnly) || ( menuFor(re.cap(1),config) != "none" ) )
				{
					tools.append(re.cap(1));
				}
			}
		}

		tools.sort();

		return tools;
	}

	TQString configName(const TQString & tool, TDEConfig *config)
	{
		config->setGroup("Tools");
		return config->readEntry(tool, "");
	}

	void setConfigName(const TQString & tool, const TQString & name, TDEConfig *config)
	{
		KILE_DEBUG() << "==KileTool::Manager::setConfigName(" << tool << "," << name << ")===============" << endl;
		config->setGroup("Tools");
		config->writeEntry(tool, name);
	}

	TQString groupFor(const TQString &tool, TDEConfig *config)
	{
		return groupFor(tool, configName(tool, config));
	}

	TQString groupFor(const TQString & tool, const TQString & cfg /* = Default */ )
	{
		return "Tool/" + tool + '/' + cfg;
	}

	void extract(const TQString &str, TQString &tool, TQString &cfg)
	{
		static TQRegExp re("([^\\(]*)\\((.*)\\)");
		TQString lcl = str;
		lcl.stripWhiteSpace();
		cfg = TQString();
		if ( re.exactMatch(lcl) )
		{
			tool = re.cap(1).stripWhiteSpace();
			cfg = re.cap(2).stripWhiteSpace();
		}
		else
			tool = lcl;
		KILE_DEBUG() << "===void extract(const TQString &str = " << str << " , TQString &tool = " << tool << ", TQString &cfg = " << cfg << " )===" << endl;
	}

	TQString format(const TQString & tool, const TQString &cfg)
	{
		if (!cfg.isNull())
			return tool + '(' + cfg + ')';
		else
			return tool;
	}

	TQStringList configNames(const TQString &tool, TDEConfig *config)
	{
		TQStringList groups = config->groupList(), configs;
		TQRegExp re = TQRegExp("Tool/"+ tool +"/(.+)");

		for ( uint i = 0; i < groups.count(); ++i )
		{
			if ( re.exactMatch(groups[i]) )
			{
				configs.append(re.cap(1));
			}
		}

		return configs;
	}

	TQString menuFor(const TQString &tool, TDEConfig *config)
	{
		config->setGroup("ToolsGUI");
		return config->readEntry(tool, "Other,gear").section(',',0,0);
	}

	TQString iconFor(const TQString &tool, TDEConfig *config)
	{
		config->setGroup("ToolsGUI");
		return config->readEntry(tool, "Other,gear").section(',',1,1);
	}

	void setGUIOptions(const TQString &tool, const TQString &menu, const TQString &icon, TDEConfig *config)
	{
		TQString entry = menu + ',' + icon;

		config->setGroup("ToolsGUI");
		config->writeEntry(tool, entry);
	}

	TQString categoryFor(const TQString &clss)
	{
		if ( clss == "Compile" || clss == "LaTeX" )
			return "Compile";
		if ( clss == "Convert" )
			return "Convert";
		if ( clss == "View" || clss == "ViewBib" || clss == "ViewHTML" || clss == "ForwardDVI" )
			return "View";
		if ( clss == "Sequence" )
			return "Sequence";
		if ( clss == "Archive")
			return "Archive";
		return "Base";
	}
}

#include "kiletoolmanager.moc"
