/* This file is part of the KDE Linux Kernel Configurator
   Copyright (c) 2001 Malte Starostik <malte@kde.org>

   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., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.
*/

// $Id: configparser.cpp,v 1.30 2003/05/16 23:47:27 mhunter Exp $

#include <sys/utsname.h>

#include <qdir.h>
#include <qfile.h>
#include <qtextstream.h>
#include <qregexp.h>

#include <kdebug.h>
#include <kstandarddirs.h>
#include <klocale.h>

#include "configparser.h"

using namespace Config;

const QString &VariableNode::value() const
{
	if (m_value.isEmpty() || m_value[0].latin1() != '$')
		return m_value;
	return Parser::self()->symbol(m_value.mid(1));
}

bool DependencyListNode::hasValue(const QString &value) const
{
	for (QPtrListIterator<VariableNode> it(*m_values); it.current(); ++it)
		if (m_values->current()->value() == value)
			return true;
	return false;
}

void DefineNode::write(QTextStream &str) const
{
	QString val = m_value->value();
	if (val.isEmpty() || ((m_type == Bool || m_type == Tristate) && val == "n"))
		str << "# " << m_symbol << " is not set" << endl;
	else if (m_type == String) str << m_symbol << "=\"" << val << "\"" << endl;
	else str << m_symbol << "=" << val << endl;
}

void DefineNode::writeHeader(QTextStream &str) const
{
	QString val = m_value->value();
	if (val.isEmpty())
		str << "#undef  " << m_symbol << endl;
	else if (m_type == Bool || m_type == Tristate)
	{
		if (val == "y")
			str << "#define " << m_symbol << " 1" << endl;
		else if (val == "m")
		{
			str << "#undef  " << m_symbol << endl;
			str << "#define " << m_symbol << "_MODULE 1" << endl;
		}
		else str << "#undef  " << m_symbol << endl;
	}
	else if (m_type == Hex) str << "#define " << m_symbol << " 0x" << val << endl;
	else if (m_type == String) str << "#define " << m_symbol << "\"" << val << "\"" << endl;
	else str << "#define " << m_symbol << " (" << val << ")" << endl;
}

void UnsetNode::apply() const
{
	for (QStringList::ConstIterator it = m_symbols.begin();
		it != m_symbols.end(); ++it)
		Parser::self()->unsetSymbol(*it);
}

void InputNode::initialize()
{
	if (m_dependencies)
		m_dependencies->initialize();
	setValue(Parser::self()->symbol(m_symbol));
}

void InputNode::write(QTextStream &str) const
{
	QString val = value();
	if (val.isEmpty() || val == "n")
		str << "# " << m_symbol << " is not set" << endl;
	else
		str << m_symbol << "=" << val << endl;
}

void InputNode::setValue(const QString &value)
{
	if (value.isEmpty() && m_default)
		internalSetValue(m_default->value());
	else
		internalSetValue(value);
}

bool InputNode::isAvailable() const
{
	return !m_dependencies || !m_dependencies->hasValue("n");
}

void BoolInputNode::writeHeader(QTextStream &str) const
{
	if (m_value)
		str << "#define " << symbol() << " 1" << endl;
	else
		str << "#undef  " << symbol() << endl;
}

QString BoolInputNode::value() const
{
	if (isAvailable())
		return m_value ? "y" : "n";
	return "n";
}

void BoolInputNode::internalSetValue(const QString &value)
{
	if (isAvailable() && value == "y")
		m_value = true;
	else
		m_value = false;
}

void BoolInputNode::toggle()
{
	if (isAvailable())
		m_value = !m_value;
	else
		m_value = false;
}

bool RestricedBoolInputNode::isAvailable() const
{
	return !m_dependencies ||
		(!m_dependencies->hasValue("n") && !m_dependencies->hasValue("m"));
}

void IntInputNode::writeHeader(QTextStream &str) const
{
	if (m_value)
		str << "#define " << symbol() << " (" << m_value << ")" << endl;
	else
		str << "#undef  " << symbol() << endl;
}

QString IntInputNode::value() const
{
	return isAvailable() ? QString::number(m_value) : "0";
}

void IntInputNode::internalSetValue(const QString &value)
{
	if (isAvailable())
		m_value = value.toInt();
	else
		m_value = 0;
}

void HexInputNode::writeHeader(QTextStream &str) const
{
	if (m_value)
		str << "#define " << symbol() << " 0x" << value() << endl;
	else
		str << "#undef  " << symbol() << endl;
}

QString HexInputNode::value() const
{
	return isAvailable() ? QString::number(m_value, 16) : "0";
}

void HexInputNode::internalSetValue(const QString &value)
{
	if (isAvailable())
		m_value = value.toInt(0, 16);
	else
		m_value = 0;
}

void StringInputNode::write(QTextStream &str) const
{
	if (m_value.isEmpty())
		InputNode::write(str);
	else
		str << symbol() << "=\"" << m_value << "\"" << endl;
}

void StringInputNode::writeHeader(QTextStream &str) const
{
	if (m_value.isEmpty())
		str << "#undef  " << symbol() << endl;
	else
		str << "#define " << symbol() << " \"" << m_value << "\"" << endl;
}

QString StringInputNode::value() const
{
	return isAvailable() ? m_value : QString::null;
}

void StringInputNode::internalSetValue(const QString &value)
{
	if (isAvailable())
		m_value = value;
	else
		m_value = QString::null;
}

void TristateInputNode::writeHeader(QTextStream &str) const
{
	switch (m_value)
	{
		case No:
			str << "#undef  " << symbol() << endl;
			break;
		case Yes:
			str << "#define " << symbol() << " 1" << endl;
			break;
		case Module:
			str << "#undef  " << symbol() << endl;
			str << "#define " << symbol() << "_MODULE 1" << endl;
			break;
	}
}

QString TristateInputNode::value() const
{
	if (isAvailable())
		switch (m_value)
		{
			case No:
				return "n";
			case Yes:
				if (m_dependencies && m_dependencies->hasValue("m"))
					return "m";
				return "y";
			case Module:
				return "m";
		}
	return "n";
}

void TristateInputNode::internalSetValue(const QString &value)
{
	if (isAvailable())
	{
		if (value == "y")
		{
			if (m_dependencies && m_dependencies->hasValue("m"))
				m_value = Module;
			else
				m_value = Yes;
		}
		else if (value == "m")
			m_value = Module;
		else
			m_value = No;
	}
	else
		m_value = No;
	if (m_value == Module && Parser::self()->symbol("CONFIG_MODULES") != "y")
		m_value = Yes;
}

void TristateInputNode::advance()
{
	if (isAvailable())
	{
		switch (m_value)
		{
			case No:
				m_value = Module;
				break;
			case Yes:
				m_value = No;
				break;
			case Module:
				if (m_dependencies && m_dependencies->hasValue("m"))
					m_value = No;
				else
					m_value = Yes;
				break;
		}
	}
}

void ChoiceNode::initialize()
{
	m_index = m_defaultIndex;
	int i = 0;
	for (QStringList::ConstIterator it = m_symbols.begin();
		it != m_symbols.end();
		++it, ++i)
		if (Parser::self()->symbol(*it) == "y")
			m_index = i;
}

void ChoiceNode::apply() const
{
	int i = 0;
	for (QStringList::ConstIterator it = m_symbols.begin();
		it != m_symbols.end();
		++it, ++i)
		Parser::self()->setSymbol(*it, i == m_index ? "y" : "n");
}

void ChoiceNode::write(QTextStream &str) const
{
	int i = 0;
	for (QStringList::ConstIterator it = m_symbols.begin();
		it != m_symbols.end();
		++it, ++i)
		if (i == m_index)
			str << *it << "=y" << endl;
		else
			str << "# " << *it << " is not set" << endl;
}

void ChoiceNode::writeHeader(QTextStream &str) const
{
	int i = 0;
	for (QStringList::ConstIterator it = m_symbols.begin();
		it != m_symbols.end();
		++it, ++i)
		if (i == m_index)
			str << "#define " << *it << " 1" << endl;
		else
			str << "#undef  " << *it << endl;
}

void BranchNodeBase::initialize()
{
	NodeList *kids = children();
	if (kids)
		for (kids->first(); kids->current(); kids->next())
			kids->current()->initialize();
}

void BranchNodeBase::apply() const
{
	NodeList *kids = children();
	if (kids)
		for (kids->first(); kids->current(); kids->next())
			kids->current()->apply();
}

void BranchNodeBase::write(QTextStream &str) const
{
	NodeList *kids = children();
	if (kids)
		for (kids->first(); kids->current(); kids->next())
			kids->current()->write(str);
}

void BranchNodeBase::writeHeader(QTextStream &str) const
{
	NodeList *kids = children();
	if (kids)
		for (kids->first(); kids->current(); kids->next())
			kids->current()->writeHeader(str);
}

void RootNode::write(QTextStream &str) const
{
	str << "#" << endl;
	str << "# Automatically generated by kcmlinuz: don't edit" << endl;
	str << "#" << endl;
	BranchNodeBase::write(str);
}

void RootNode::writeHeader(QTextStream &str) const
{
	str << "/*" << endl;
	str << " * Automatically generated by kcmlinuz: don't edit" << endl;
	str << " */" << endl;
	str << "#define AUTOCONF_INCLUDED" << endl;
	BranchNodeBase::writeHeader(str);
}

void MenuNode::write(QTextStream &str) const
{
	str << endl;
	str << "#" << endl;
	str << "# " << m_comment->text() << endl;
	str << "#" << endl;
	BranchNodeBase::write(str);
}

void MenuNode::writeHeader(QTextStream &str) const
{
	str << endl;
	str << "/*" << endl;
	str << " * " << m_comment->text() << endl;
	str << " */" << endl;
	BranchNodeBase::writeHeader(str);
}

void IfNode::initialize()
{
	NodeList *kids = m_trueChildren;
	if (kids)
		for (kids->first(); kids->current(); kids->next())
			kids->current()->initialize();
	kids = m_falseChildren;
	if (kids)
		for (kids->first(); kids->current(); kids->next())
			kids->current()->initialize();
}

extern int linuzparse();

Parser *Parser::s_self = 0;

Parser::Parser()
	: m_root(0)
{
	s_self = this;
	m_ruleStack.setAutoDelete(true);

	m_kernelRoot = "/usr/src/linux"; // Default kernel source tree

	struct utsname info;
	uname(&info); // Determine architecture default from uname()
	m_arch = info.machine;
	if (QRegExp("i.86").search(m_arch) != -1)
		m_arch = "i386";
	else if (m_arch == "sun4u")
		m_arch = "sparc64";
	else if (QRegExp("arm.*").search(m_arch) != -1)
		m_arch = "arm";
	else if (m_arch == "sa110")
		m_arch = "arm";
}

Parser::~Parser()
{
	delete m_root;
	s_self = 0;
}

QStringList Parser::availableArchs(const QString &root) const
{
	QDir dir(QString::fromLatin1("%1/arch").arg(root));
	QStringList archs = dir.entryList(QDir::Dirs);
	for (QStringList::Iterator it = archs.begin(); it != archs.end(); )
	{
		if (*it == "." || *it == "..")
			archs.remove(it++);
		else
			++it;
	}
	return archs;
}

bool Parser::parseConfig(const QString &kernelRoot, const QString &arch)
{
	delete m_root;
	m_root = 0;
	m_symbols.clear();
	m_errors.clear();
	m_kernelRoot = kernelRoot;
	m_arch = arch;
	setSymbol("ARCH", arch);
	if (m_arch.isNull())
	{
		addError(i18n("No hardware architecture was specified! "
					"Perhaps the kernel source code is not installed on this system, "
					"or the path to the kernel sources is entered incorrectly."));
	}
	else if (pushInclude(QString::fromLatin1("arch/%1/config.in").arg(m_arch)))
	{
		linuzparse();
	}
	else if (QFileInfo(kernelRoot+QString::fromLatin1("/arch/%1/Kconfig").arg(m_arch)).exists())
	{
		addError(i18n("Linux kernel versions 2.5 and above are not yet"
					" supported."));
	}
	m_ruleStack.clear();
	return m_errors.count() == 0;
}

void Parser::setRoot(RootNode *root)
{
	delete m_root;
	m_root = root;
}

const QString &Parser::symbol(const QString &name) const
{
	SymbolTable::ConstIterator found = m_symbols.find(name);
	return (found == m_symbols.end()) ? QString::null : *found;
}

void Parser::setSymbol(const QString &name, const QString &value)
{
	m_symbols[name] = value;
}

void Parser::unsetSymbol(const QString &name)
{
	m_symbols.remove(name);
}

bool Parser::readConfig(const QString &file)
{
	m_symbols.clear();
	setSymbol("ARCH", m_arch);
	QFile f(file);
	if (!f.open(IO_ReadOnly))
		return false;
	QTextStream str(&f);
	while (!str.atEnd())
	{
		QString line = str.readLine().simplifyWhiteSpace();
		if (line.isEmpty() || line[0].latin1() == '#')
		{
			if (line.right(11) == " is not set")
				setSymbol(line.mid(2, line.length() - 13), "n");
			continue;
		}
		int p = line.find("=");
		if (p == -1)
			continue;
		QString name = line.left(p);
		QString value = line.mid(p + 1);
		if (value[0] == '"' && value[value.length() - 1] == '"')
			value = value.mid(1, value.length() - 2);
		setSymbol(name, value);
	}
	if (m_root)
		m_root->initialize();
	return true;
}

bool Parser::writeConfig(const QString &file) const
{
	QFile f(file);
	if (!f.open(IO_WriteOnly))
		return false;
	QTextStream str(&f);
	m_root->write(str);
	return true;
}

bool Parser::writeHeader(const QString &file) const
{
	QFile f(file);
	if (!f.open(IO_WriteOnly))
		return false;
	QTextStream str(&f);
	m_root->writeHeader(str);
	return true;
}

void Parser::apply() const
{
	m_root->apply();
}

void Parser::makeHTMLLinks(QString &text, const QString &regexp,
	const QString &prefix) const
{
	QRegExp urlRex(regexp.latin1());
	int match;
	for (unsigned int start = 0; (match = urlRex.search(text, start)) >= 0; )
	{
		QString link = QString::fromLatin1("<a href=\"%1%2\">%3</a>")
				.arg(prefix)
				.arg(urlRex.cap(0))
				.arg(urlRex.cap(0));
		text.replace(match, urlRex.matchedLength(), link);
		start = match + link.length();
		if (start >= text.length())
			break; // Don't crash
	}
}

QString Parser::helpText(const QString &symbol)
{
	if (!m_help.count())
	{
		QFile f(QString::fromLatin1("%1/Documentation/Configure.help")
			.arg(m_kernelRoot));
		if (f.open(IO_ReadOnly))
		{
			QTextStream str(&f);
			for (QString line = str.readLine(); !line.isNull(); line = str.readLine())
				m_help.append(line);
		}

		QFile tpl(locate("data", "kcmlinuz/data/help-template.html"));
		if (tpl.open(IO_ReadOnly))
		{
			QTextStream str(&tpl);
			m_helpTemplate = str.read();
		}
		else
			m_helpTemplate = QString::fromLatin1(
				"<html><head></head><body>"
				"<h1>{TITLE}</h1><h2>{SYMBOL}</h2>"
				"<p>{CONTENTS}</p>"
				"</body></html>");
	}
	QString title;
	QString contents;
	for (QStringList::ConstIterator it = m_help.find(symbol);
		it != m_help.end();
		++it)
	{
		if (title.isEmpty())
		{
			title = *(--it);
			it++;
		}
		else if ((*it).isEmpty())
			contents += "\n";
		else if (!(*it).startsWith("  "))
			break;
		else
			contents += *it;
	}
	if (contents.isEmpty())
		return QString::null;

	// Order is important here
	contents.replace(QRegExp("&"), "&amp;");
	contents.replace(QRegExp("<"), "&lt;");
	contents.replace(QRegExp(">"), "&gt;");

	makeHTMLLinks(contents, "(http|ftp)://[^ \t\n&]+[^ \t\n\\.,()&]");
	makeHTMLLinks(contents, "[A-Za-z0-9_\\.+-]+@[A-Za-z0-9_\\.-]+[A-Za-z0-9_-]", "mailto:");
	makeHTMLLinks(contents, "Documentation/[^ \t\n&]+[^ \t\n\\.,()&]",
		QString::fromLatin1("file:/%1/").arg(m_kernelRoot));

	contents.replace(QRegExp("\n"), "</p><p>");

	QString result = m_helpTemplate;
	result.replace(QRegExp("\\{TITLE\\}"), title);
	result.replace(QRegExp("\\{SYMBOL\\}"), symbol);
	result.replace(QRegExp("\\{CONTENTS\\}"), contents);
	return result;
}

RuleFile::RuleFile(const QString &file)
	: m_line(1),
	  m_column(0),
	  m_buffer(0)
{
	QFile f(m_name = QString::fromLatin1("%1/%2")
		.arg(Parser::self()->kernelRoot())
		.arg(file));
	if (f.open(IO_ReadOnly))
	{
		QTextStream str(&f);
		m_data = str.read();
	}
	else
		Parser::self()->addError(i18n("cannot open %1 for reading").arg(m_name));
}

QString RuleFile::currentLine() const
{
	int pos = -1;
	for (int i = 0; i < m_line - 1; ++i)
		if ((pos = m_data.find("\n", ++pos)) == -1)
			return QString::null;
	int len = m_data.find("\n", ++pos);
	return m_data.mid(pos, len != -1 ? len - pos : -1);
}

ErrorInfo::ErrorInfo(const QString &message)
	: m_message(message)
{
	RuleFile *rule = Parser::self()->currentFile();
	if (rule)
	{
		m_file = rule->name();
		m_text = rule->currentLine();
		m_line = rule->line();
		m_pos = rule->pos() - rule->tokenLength();
		m_length = rule->tokenLength();
	}
}

// vim: ts=4 sw=4 noet
