/**
 * \file lyxlex.C
 * This file is part of LyX, the document processor.
 * Licence details can be found in the file COPYING.
 *
 * \author Alejandro Aguilar Sierra
 * \author Lars Gullik Bjønnes
 * \author Jean-Marc Lasgouttes
 * \author John Levon
 *
 * Full author contact details are available in file CREDITS.
 */

#include <config.h>

#include "lyxlex.h"

#include "debug.h"
#include "lyxlex_pimpl.h"

#include "support/convert.h"
#include "support/lstrings.h"

#include <sstream>

using lyx::support::compare_ascii_no_case;
using lyx::support::isStrDbl;
using lyx::support::isStrInt;
using lyx::support::ltrim;
using lyx::support::prefixIs;
using lyx::support::subst;
using lyx::support::trim;

using std::endl;
using std::string;
using std::istream;
using std::ostream;


LyXLex::LyXLex(keyword_item * tab, int num)
	: pimpl_(new Pimpl(tab, num))
{}


LyXLex::~LyXLex()
{
	delete pimpl_;
}


bool LyXLex::isOK() const
{
	return pimpl_->is.good();
}


void LyXLex::setLineNo(int l)
{
	pimpl_->lineno = l;
}


int LyXLex::getLineNo() const
{
	return pimpl_->lineno;
}


istream & LyXLex::getStream()
{
	return pimpl_->is;
}


void LyXLex::pushTable(keyword_item * tab, int num)
{
	pimpl_->pushTable(tab, num);
}


void LyXLex::popTable()
{
	pimpl_->popTable();
}


void LyXLex::printTable(ostream & os)
{
	pimpl_->printTable(os);
}


void LyXLex::printError(string const & message) const
{
	pimpl_->printError(message);
}


bool LyXLex::setFile(string const & filename)
{
	return pimpl_->setFile(filename);
}


void LyXLex::setStream(istream & i)
{
	pimpl_->setStream(i);
}


void LyXLex::setCommentChar(char c)
{
	pimpl_->setCommentChar(c);
}

int LyXLex::lex()
{
	return pimpl_->lex();
}


int LyXLex::getInteger() const
{
	if (isStrInt(pimpl_->getString()))
		return convert<int>(pimpl_->getString());
	pimpl_->printError("Bad integer `$$Token'");
	return -1;
}


double LyXLex::getFloat() const
{
	// replace comma with dot in case the file was written with
	// the wrong locale (should be rare, but is easy enough to
	// avoid).
	string const str = subst(pimpl_->getString(), ",", ".");
	if (isStrDbl(str))
		return convert<double>(str);
	pimpl_->printError("Bad float `$$Token'");
	return -1;
}


string const LyXLex::getString() const
{
	return pimpl_->getString();
}


// I would prefer to give a tag number instead of an explicit token
// here, but it is not possible because Buffer::readDocument uses
// explicit tokens (JMarc)
string const LyXLex::getLongString(string const & endtoken)
{
	string str, prefix;
	bool firstline = true;

	while (isOK()) {
		if (!eatLine())
			// blank line in the file being read
			continue;

		string const token = trim(getString(), " \t");

		lyxerr[Debug::PARSER] << "LongString: `"
				      << getString() << '\'' << endl;

		// We do a case independent comparison, like search_kw does.
		if (compare_ascii_no_case(token, endtoken) == 0)
			break;

		string tmpstr = getString();
		if (firstline) {
			string::size_type i(tmpstr.find_first_not_of(' '));
			if (i != string::npos)
				prefix = tmpstr.substr(0, i);
			firstline = false;
			lyxerr[Debug::PARSER]
				<< "Prefix = `" << prefix << "\'" << endl;
		}

		// further lines in long strings may have the same
		// whitespace prefix as the first line. Remove it.
		if (prefix.length() && prefixIs(tmpstr, prefix)) {
			tmpstr.erase(0, prefix.length() - 1);
		}

		str += ltrim(tmpstr, "\t") + '\n';
	}

	if (!isOK()) {
		printError("Long string not ended by `" + endtoken + '\'');
	}

	return str;
}


bool LyXLex::getBool() const
{
	if (pimpl_->getString() == "true") {
		return true;
	} else if (pimpl_->getString() != "false") {
		pimpl_->printError("Bad boolean `$$Token'. "
				   "Use \"false\" or \"true\"");
	}
	return false;
}


bool LyXLex::eatLine()
{
	return pimpl_->eatLine();
}


bool LyXLex::next(bool esc)
{
	return pimpl_->next(esc);
}


bool LyXLex::nextToken()
{
	return pimpl_->nextToken();
}


void LyXLex::pushToken(string const & pt)
{
	pimpl_->pushToken(pt);
}

LyXLex::operator void const *() const
{
	// This behaviour is NOT the same as the std::streams which would
	// use fail() here. However, our implementation of getString() et al.
	// can cause the eof() and fail() bits to be set, even though we
	// haven't tried to read 'em.
	return pimpl_->is.bad() ? 0 : this;
}


bool LyXLex::operator!() const
{
	return pimpl_->is.bad();
}


LyXLex & LyXLex::operator>>(std::string & s)
{
	if (isOK()) {
		next();
		s = getString();
	}
	return *this;
}


LyXLex & LyXLex::operator>>(double & s)
{
	if (isOK()) {
		next();
		s = getFloat();
	}
	return *this;
}


LyXLex & LyXLex::operator>>(int & s)
{
	if (isOK()) {
		next();
		s = getInteger();
	}
	return *this;
}


LyXLex & LyXLex::operator>>(unsigned int & s)
{
	if (isOK()) {
		next();
		s = getInteger();
	}
	return *this;
}


LyXLex & LyXLex::operator>>(bool & s)
{
	if (isOK()) {
		next();
		s = getBool();
	}
	return *this;
}


/// quotes a string, e.g. for use in preferences files or as an argument of the "log" dialog
string const LyXLex::quoteString(string const & arg)
{
	std::ostringstream os;
	os << '"' << subst(subst(arg, "\\", "\\\\"), "\"", "\\\"") << '"';
	return os.str();
}
