// Parser.cpp: implementation of the CParser class.
//
//////////////////////////////////////////////////////////////////////
#ifdef _DEBUG
#pragma warning(disable : 4786)	// disable truncation warning (Only used by debugger)
#endif

#include <algorithm>			// std::transform
#include <map>					// std::map
#include <cassert>				// assert
#include <stdlib.h>
#include <iostream>				// std::cout std::cerr
#include "Utils.h"
#include <stdio.h>
#include "Parser.h"
#include "PHRQ_io.h"

#if defined(PHREEQCI_GUI)
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
#endif

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CParser::CParser(PHRQ_io *io):
PHRQ_base(io),
m_input_stream(std::cin), 
m_input_error(0),
m_next_keyword(Keywords::KEY_NONE)
{
	if (!io)
	{
		error_msg("This parser constructor requires non-null phrq_io", PHRQ_io::OT_STOP);
		echo_file = EO_ALL;
		echo_stream = EO_NONE;
		accumulate = false;
		phrq_io_only = true;
		m_line_type = PHRQ_io::LT_EMPTY;
	}
	else
	{
		m_line_save = io->Get_m_line();
		m_line = io->Get_m_line();
		m_line_type = io->Get_m_line_type();
		m_line_iss.str(m_line);
		m_line_iss.seekg(0, std::ios_base::beg);
		m_line_iss.clear();
		echo_file = EO_ALL;
		echo_stream = EO_NONE;
		accumulate = false;
		phrq_io_only = true;
	}
}

CParser::CParser(std::istream & input, PHRQ_io *io):
PHRQ_base(io),
m_input_stream(input), 
m_input_error(0),
m_next_keyword(Keywords::KEY_NONE)
{
	m_line_save.reserve(80);
	m_line.reserve(80);
	echo_file = EO_ALL;
	echo_stream = EO_NONE;
	accumulate = false;
	phrq_io_only = false;
	m_line_type = PHRQ_io::LT_EMPTY;
}

CParser::~CParser()
{
}

PHRQ_io::LINE_TYPE CParser::check_line(const std::string & str,
									   bool allow_empty, bool allow_eof,
									   bool allow_keyword, bool print)
{
	PHRQ_io::LINE_TYPE
		i;

	// Get line
	do
	{
		i = get_line();
		// reset iss
		m_line_iss.str(m_line);
		m_line_iss.seekg(0, std::ios_base::beg);
		m_line_iss.clear();

		// output for stream
		switch (this->echo_stream)
		{
		case EO_NONE:
			break;
		case EO_ALL:
			if (i != PHRQ_io::LT_EOF)
			{
				std::ostringstream msg;
				msg << "\t" << m_line_save << "\n";
				//get_output() << msg;
				io->output_msg(msg.str().c_str());
			}
			break;
		case EO_KEYWORDS:
			if (i == PHRQ_io::LT_KEYWORD)
			{
				std::ostringstream msg;
				msg << "\t" << m_line_save << "\n";
				//get_output() << msg;
				io->output_msg(msg.str().c_str());
			}
			break;
		case EO_NOKEYWORDS:
			if (i != PHRQ_io::LT_KEYWORD && i != PHRQ_io::LT_EOF)
			{
				std::ostringstream msg;
				msg << "\t" << m_line_save << "\n";
				//get_output() << msg;
				io->output_msg(msg.str().c_str());
			}
			break;
		}
		// output for file
		switch (this->echo_file)
		{
		case EO_NONE:
			break;
		case EO_ALL:
			if (i != PHRQ_io::LT_EOF)
			{
				std::ostringstream msg;
				msg << "\t" << m_line_save << "\n";
				this->echo_msg(msg.str());
			}
			break;
		case EO_KEYWORDS:
			if (i == PHRQ_io::LT_KEYWORD)
			{
				std::ostringstream msg;
				msg << "\t" << m_line_save << "\n";
				this->echo_msg(msg.str());
			}
			break;

		case EO_NOKEYWORDS:
			if (i != PHRQ_io::LT_KEYWORD && i != PHRQ_io::LT_EOF)
			{
				std::ostringstream msg;
				msg << "\t" << m_line_save << "\n";
				this->echo_msg(msg.str().c_str());
			}
			break;
		}
	}
	while (i == PHRQ_io::LT_EMPTY && allow_empty == false);

	// Check eof
	if (i == PHRQ_io::LT_EOF && allow_eof == false)
	{
		std::ostringstream msg;
		msg << "Unexpected eof while reading " << str <<
			"\nExecution terminated.\n";
		error_msg(msg.str().c_str(), PHRQ_io::OT_STOP);
	}

	// Check keyword
	if (i == PHRQ_io::LT_KEYWORD && allow_keyword == false)
	{
		std::ostringstream msg;
		msg << "Expected data for " << str <<
			", but got a keyword ending data block.";
		error_msg(msg.str().c_str(), PHRQ_io::OT_CONTINUE);
		incr_input_error();
	}
	m_line_type = i;
	return i;
}
PHRQ_io::LINE_TYPE CParser::get_line_phrq_io()
{
	m_line_type = io->get_line();
	m_line_save = io->Get_m_line_save();
	m_line = io->Get_m_line();
	m_next_keyword = io->Get_m_next_keyword();
	if (accumulate)
	{
		this->accumulated.append(m_line_save);
		this->accumulated.append("\n");
	}
	return m_line_type;
}

PHRQ_io::LINE_TYPE CParser::get_line()
{
	if (this->phrq_io_only)
	{
		return get_line_phrq_io();
	}
	PHRQ_io::LINE_TYPE return_value = PHRQ_io::LT_EMPTY;
	while (return_value == PHRQ_io::LT_EMPTY)
	{
		//
		// Eliminate all characters after # sign as a comment
		//

		//
		// Get line, check for eof
		//
		if (get_logical_line() == PHRQ_io::LT_EOF)
		{
			if (!m_input_stream.eof())
			{
				error_msg("Reading input file.", PHRQ_io::OT_CONTINUE);
				error_msg("istream::get() returned an error.", PHRQ_io::OT_STOP);
			}
			else
			{
				//{{MOD
				m_line.erase(m_line.begin(), m_line.end());	// m_line.clear();
				//}}MOD
				m_next_keyword = Keywords::KEY_END;
				return PHRQ_io::LT_EOF;
			}
		}

		//
		// Get long lines
		//
		bool
			empty = true;
		m_line = m_line_save.substr(0, m_line_save.find_first_of('#'));
		for (unsigned int i = 0; i < m_line.size(); ++i)
		{
			if (!::isspace(m_line[i]))
			{
				empty = false;
				break;
			}
		}

		if (this->accumulate)
		{
			this->accumulated.append(m_line_save);
			this->accumulated.append("\n");
		}
		//
		// New line character encountered 
		//
		return_value = (empty ? PHRQ_io::LT_EMPTY : PHRQ_io::LT_OK);
	}

	//
	// Determine return_value
	// 
	if (return_value == PHRQ_io::LT_OK)
	{
		if (check_key(m_line.begin(), m_line.end()))
		{
			return_value = PHRQ_io::LT_KEYWORD;
		}
		else
		{
			std::string::iterator beg = m_line.begin();
			std::string::iterator end = m_line.end();
			std::string token;
			copy_token(token, beg, end);

			if (token.size() > 1 && token[0] == '-' &&::isalpha(token[1]))
			{
				return_value = PHRQ_io::LT_OPTION;
			}
		}
	}
	return return_value;
}

/**
        Reads input stream until end of line, ";", or eof
        stores characters in line_save

        returns:
                EOF on empty line on end of file or
                OK otherwise
*/
PHRQ_io::LINE_TYPE CParser::get_logical_line()
{
	int
		j;
	unsigned int
		pos;
	char
		c;

	m_line_save.erase(m_line_save.begin(), m_line_save.end());	// m_line_save.clear();

	while ((j = m_input_stream.get()) != std::char_traits < char >::eof())
	{
		c = (char) j;
		if (c == '#')
		{
			// ignore all chars after # until newline
			do
			{
				c = (char) j;
				if (c == '\n')
				{
					break;
				}
				m_line_save += c;
			}
			while ((j =
					m_input_stream.get()) != std::char_traits <
				   char >::eof());
		}
		if (c == ';')
			break;
		if (c == '\n')
		{
			break;
		}
		if (c == '\\')
		{
			pos = (int) m_line_save.size();
			m_line_save += c;
			while ((j =
					m_input_stream.get()) != std::char_traits < char >::eof())
			{
				c = (char) j;
				if (c == '\\')
				{
					pos = (int) m_line_save.size();
					m_line_save += c;
					continue;
				}
				if (c == '\n')
				{
					// remove '\\'
					for (; pos < m_line_save.size(); pos++)
					{
						m_line_save[pos] = m_line_save[(size_t) pos + 1];
					}
					m_line_save.erase(m_line_save.size() - 1, 1);
					break;
				}
				m_line_save += c;
				if (!::isspace(j))
					break;
			}
		}
		else
		{
			m_line_save += c;
		}
	}
	if (j == std::char_traits < char >::eof() && m_line_save.size() == 0)
	{
		return (PHRQ_io::LT_EOF);
	}
	return (PHRQ_io::LT_OK);
}

bool
CParser::check_key(std::string::iterator begin, std::string::iterator end)
{
	std::string lowercase;
	copy_token(lowercase, begin, end);
	std::transform(lowercase.begin(), lowercase.end(), lowercase.begin(),
				   tolower);
	m_next_keyword = Keywords::Keyword_search(lowercase);
	if (m_next_keyword == Keywords::KEY_NONE)
	{
		return false;
	}
	return true;
}
CParser::STATUS_TYPE CParser::check_units(std::string & tot_units,
										  bool alkalinity,
										  bool check_compatibility,
										  const std::string & default_units,
										  bool print)
{
/*
 *   Check if legitimate units
 *   Input:
 *           tot_units           character string to check,
 *           alkalinity          true if alkalinity, false if any other total,
 *           check_compatibility true check alk and default units, false otherwise
 *           default_units       character string of default units (check /L, /kg, etc)
 *           print               true print warning messages
 *   Output:
 *           tot_units           standard form for unit
 */
	using
		Utilities::str_tolower;
	using
		Utilities::replace;
	using
		Utilities::squeeze_white;

	static const char *
		units[] = {
		"Mol/l",				/* 0 */
		"mMol/l",				/* 1 */
		"uMol/l",				/* 2 */
		"g/l",					/* 3 */
		"mg/l",					/* 4 */
		"ug/l",					/* 5 */
		"Mol/kgs",				/* 6 */
		"mMol/kgs",				/* 7 */
		"uMol/kgs",				/* 8 */
		"g/kgs",				/* 9 = ppt */
		"mg/kgs",				/* 10 = ppm */
		"ug/kgs",				/* 11 = ppb */
		"Mol/kgw",				/* 12 = mol/kg H2O */
		"mMol/kgw",				/* 13 = mmol/kg H2O */
		"uMol/kgw",				/* 14 = umol/kg H2O */
		"g/kgw",				/* 15 = mol/kg H2O */
		"mg/kgw",				/* 16 = mmol/kg H2O */
		"ug/kgw",				/* 17 = umol/kg H2O */
		"eq/l",					/* 18 */
		"meq/l",				/* 19 */
		"ueq/l",				/* 20 */
		"eq/kgs",				/* 21 */
		"meq/kgs",				/* 22 */
		"ueq/kgs",				/* 23 */
		"eq/kgw",				/* 24 */
		"meq/kgw",				/* 25 */
		"ueq/kgw",				/* 26 */
	};

	squeeze_white(tot_units);
	str_tolower(tot_units);
	replace("milli", "m", tot_units);
	replace("micro", "u", tot_units);
	replace("grams", "g", tot_units);
	replace("gram", "g", tot_units);
	replace("moles", "Mol", tot_units);
	replace("mole", "Mol", tot_units);
	replace("mol", "Mol", tot_units);
	replace("liter", "l", tot_units);
	replace("kgh", "kgw", tot_units);
	replace("ppt", "g/kgs", tot_units);
	replace("ppm", "mg/kgs", tot_units);
	replace("ppb", "ug/kgs", tot_units);
	replace("equivalents", "eq", tot_units);
	replace("equivalent", "eq", tot_units);
	replace("equiv", "eq", tot_units);

	std::string::size_type end;
	if ((end = tot_units.find("/l")) != std::string::npos)
	{
		tot_units.resize(end + 2);
	}
	if ((end = tot_units.find("/kgs")) != std::string::npos)
	{
		tot_units.resize(end + 4);
	}
	if ((end = tot_units.find("/kgw")) != std::string::npos)
	{
		tot_units.resize(end + 4);
	}

	//
	//  Check if unit in list
	//
	bool
		found = false;
	for (unsigned int i = 0; i < sizeof(units) / sizeof(char *); ++i)
	{
		if (tot_units.compare(units[i]) == 0)
		{
			found = true;
			break;
		}
	}
	if (!found)
	{
		if (print)
		{
			std::ostringstream err;
			err << "Unknown unit, " << tot_units;
			this->error_msg(err.str().c_str(), PHRQ_io::OT_CONTINUE);
		}
		return PARSER_ERROR;
	}

	//
	//   Check if units are compatible with default_units
	//
	if (check_compatibility == false)
		return PARSER_OK;

	//
	//   Special cases for alkalinity
	//
	if (alkalinity == true && tot_units.find("Mol") != std::string::npos)
	{
		if (print)
		{
			this->warning_msg
				("Alkalinity given in moles, assumed to be equivalents.");
		}
		replace("Mol", "eq", tot_units);
	}
	if (alkalinity == false && tot_units.find("eq") != std::string::npos)
	{
		if (print)
		{
			this->error_msg("Only alkalinity can be entered in equivalents.",
					  PHRQ_io::OT_CONTINUE);
		}
		return PARSER_ERROR;
	}

	//
	//  See if default_units are compatible with tot_units
	//
	if (default_units.find("/l") != std::string::npos
		&& tot_units.find("/l") != std::string::npos)
		return PARSER_OK;
	if (default_units.find("/kgs") != std::string::npos
		&& tot_units.find("/kgs") != std::string::npos)
		return PARSER_OK;
	if (default_units.find("/kgw") != std::string::npos
		&& tot_units.find("/kgw") != std::string::npos)
		return PARSER_OK;

	std::string str = default_units;
	replace("kgs", "kg solution", str);
	replace("kgs", "kg solution", tot_units);
	replace("kgw", "kg water", str);
	replace("kgw", "kg water", tot_units);
	replace("/l", "/L", str);
	replace("Mol", "mol", str);
	replace("/l", "/L", tot_units);
	replace("Mol", "mol", tot_units);

	if (print)
	{
		std::ostringstream err;
		err << "Units for master species, " << tot_units <<
			", are not compatible with default units, " << str << ".";
		this->error_msg(err.str().c_str(), PHRQ_io::OT_CONTINUE);
	}
	return PARSER_ERROR;
}
CParser::TOKEN_TYPE CParser::token_type(const std::string & token)
{
	if (!token.empty())
	{
		if (::isupper(token[0]))
		{
			return CParser::TT_UPPER;
		}
		else if (::islower(token[0]))
		{
			return CParser::TT_LOWER;
		}
		else if (::isdigit(token[0]) || token[0] == '.' || token[0] == '-')
		{
			return CParser::TT_DIGIT;
		}
		else
		{
			assert(!::isspace(token[0]));
			return CParser::TT_UNKNOWN;
		}
	}
	else
	{
		return CParser::TT_EMPTY;
	}
}

CParser::TOKEN_TYPE CParser::peek_token()
{
	std::istringstream::pos_type pos = m_line_iss.tellg();
	std::string token;
	m_line_iss >> token;
	m_line_iss.seekg(pos);
	return token_type(token);
}

CParser::TOKEN_TYPE CParser::copy_token(std::string & token,
										std::string::iterator & begin,
										std::string::iterator & end)
{
	if (begin != end)
	{
		std::string::iterator b = begin;
		for (; b < end &&::isspace(*b); ++b);

		begin = b;
		for (; begin < end && !::isspace(*begin); ++begin);

		token.assign(b, begin);
	}
	else
	{
		token.resize(0);
	}

	return token_type(token);
}

CParser::TOKEN_TYPE CParser::copy_token(std::string & token,
										std::istream & is)
{
	is >> token;
	return token_type(token);
}

CParser::TOKEN_TYPE CParser::copy_token(std::string & token,
										std::istream::pos_type & pos)
{
	m_line_iss.seekg(pos);
	if (!(m_line_iss >> token))
	{
		token.erase(token.begin(), token.end());	// token.clear();
	}
	pos = m_line_iss.tellg();
	return token_type(token);
}

CParser::FIND_TYPE CParser::find_option(const std::string & item, int *n,
										const std::vector < std::string >
										&list, bool exact)
{
	std::string token(item);
	std::transform(token.begin(), token.end(), token.begin(), tolower);
	for (unsigned int i = 0; i < list.size(); i++)
	{
		if (exact == true)
		{
			if (list[i].compare(token) == 0)
			{
				*n = i;
				return FT_OK;
			}
		}
		else
		{
			if (list[i].find(token) == 0)
			{
				*n = i;
				return FT_OK;
			}
		}
	}

	*n = -1;
	return FT_ERROR;
}

int
CParser::get_option(const std::vector < std::string > &opt_list,
					std::string::iterator & next_char)
{
	//
	// Read a line and check for options
	//
	int j;
	int /*  opt_l,  */ opt;
	std::string::iterator opt_ptr;
	std::string option;

#if !defined(R_SO)
	fprintf(stderr, "Did not think this get_option was called\n");
#endif
	//
	// Read line
	//
	PHRQ_io::LINE_TYPE lt = check_line("get_option", false, true, true, true);
	if (lt == PHRQ_io::LT_EOF)
	{
		j = OPT_EOF;
	}
	else if (lt == PHRQ_io::LT_KEYWORD)
	{
		j = OPT_KEYWORD;
	}
	else if (lt == PHRQ_io::LT_OPTION)
	{
		opt_ptr = m_line.begin();
		std::string::iterator end = m_line.end();
		copy_token(option, opt_ptr, end);
		if (find_option(option, &opt, opt_list, false) == CParser::FT_OK)
		{
			j = opt;
			m_line_save.replace(m_line_save.find(option), option.size(),
								opt_list[opt]);
			m_line.replace(m_line.find(option), option.size(), opt_list[opt]);
			opt_ptr = m_line.begin();
			std::string::iterator end = m_line.end();
			copy_token(option, opt_ptr, end);
			next_char = opt_ptr;
			if (true)			// pr.echo_input == TRUE
			{
				if (true)		// database_file == NULL
				{
					//get_output() << "\t" << m_line_save << "\n";
					std::ostringstream msg;
					msg << "\t" << m_line_save << "\n";
					io->output_msg(msg.str().c_str());
				}
			}
		}
		else
		{
			if (true)			// (database_file == NULL)
			{
				//get_output() << "\t" << m_line_save << "\n";
				std::ostringstream msg;
				msg << "\t" << m_line_save << "\n";
				io->output_msg(msg.str().c_str());
			}

			std::ostringstream err;
			err << "Unknown option." << "\n";
			err << m_line_save << "\n";
			error_msg(err.str().c_str());

			j = OPT_ERROR;
			next_char = m_line.begin();
		}
	}
	else
	{
		opt_ptr = m_line.begin();
		std::string::iterator end = m_line.end();
		copy_token(option, opt_ptr, end);
		if (find_option(option, &opt, opt_list, true) == FT_OK)
		{
			j = opt;
			next_char = opt_ptr;
		}
		else
		{
			j = OPT_DEFAULT;
			next_char = m_line.begin();
		}
		if (true)				// pr.echo_input == TRUE
		{
			if (true)			// database_file == NULL
			{
#if !defined(R_SO)
				std::cout << "\t" << m_line_save << "\n";
#endif
			}
		}
	}
	return (j);
}

int
CParser::get_option(const std::vector < std::string > &opt_list,
					std::istream::pos_type & next_pos)
{
	//
	// Read a line and check for options
	//
	int j;
	int opt;
	std::istream::pos_type pos_ptr;
	std::string option;

	//
	// Read line
	//
	PHRQ_io::LINE_TYPE lt = check_line("get_option", false, true, true, true);
	if (lt == PHRQ_io::LT_EOF)
	{
		j = OPT_EOF;
	}
	else if (lt == PHRQ_io::LT_KEYWORD)
	{
		j = OPT_KEYWORD;
	}
	else if (lt == PHRQ_io::LT_OPTION)
	{
		std::string::iterator opt_ptr = m_line.begin();
		std::string::iterator end = m_line.end();
		copy_token(option, opt_ptr, end);
		if (find_option(option.substr(1), &opt, opt_list, false) == FT_OK)
		{
			// replace -option with option
			j = opt;
			m_line_save.replace(m_line_save.find(option), option.size(),
								opt_list[opt]);
			m_line.replace(m_line.find(option), option.size(), opt_list[opt]);

			// reset iss
			m_line_iss.str(m_line);
			m_line_iss.seekg(0, std::ios_base::beg);
			m_line_iss.clear();

			pos_ptr = 0;
			copy_token(option, pos_ptr);
			next_pos = pos_ptr;
		}
		else
		{
			j = OPT_ERROR;
			next_pos = pos_ptr;
		}
	}
	else
	{
		pos_ptr = m_line_iss.tellg();
		m_line_iss >> option;
		if (find_option(option, &opt, opt_list, true) == FT_OK)
		{
			j = opt;
			next_pos = m_line_iss.tellg();
		}
		else
		{
			j = OPT_DEFAULT;
			m_line_iss.seekg(pos_ptr);
			m_line_iss.clear();
			next_pos = pos_ptr;
		}
	}
	return (j);
}
CParser::STATUS_TYPE CParser::get_elt(std::string::iterator & begin,
									  const std::string::iterator end,
									  std::string & element)
{
	element.erase(element.begin(), element.end());	// element.clear();

	if (begin == end)
	{
		error_msg("Empty string in get_elt.  Expected an element name.",
				  PHRQ_io::OT_CONTINUE);
		return PARSER_ERROR;
	}

	//
	// Load name into char array element
	//
	char
		c = *begin;
	++begin;
	element.insert(element.end(), c);	// element.push_back(c);
	if (c == '[')
	{
		while ((c = *begin) != ']')
		{
			element.insert(element.end(), c);	// element.push_back(c);
			++begin;
			if ((c = *begin) == ']')
			{
				element.insert(element.end(), c);	// element.push_back(c);
				++begin;
				break;
			}
			else if (begin == end)
			{
				error_msg("No ending bracket (]) for element name",
						  PHRQ_io::OT_CONTINUE);
				incr_input_error();
				return PARSER_ERROR;
			}
		}
		while (::islower(c = *begin) || c == '_')
		{
			element.insert(element.end(), c);	// element.push_back(c);
			++begin;
			if (begin == end)
				break;
		}
	}
	else
	{
		while (::islower(c = *begin) || c == '_')
		{
			element.insert(element.end(), c);	// element.push_back(c);
			++begin;
			if (begin == end)
				break;
		}
	}
	return PARSER_OK;
}

CParser::STATUS_TYPE CParser::parse_couple(std::string & token)
{
	// Parse couple puts redox couples in standard form
	// "+" is removed and couples are rewritten in sort
	// order.

	if (Utilities::strcmp_nocase_arg1(token.c_str(), "pe") == 0)
	{
		Utilities::str_tolower(token);
		return PARSER_OK;
	}

	while (Utilities::replace("(+", "(", token));

	std::string::iterator ptr = token.begin();
	std::string elt1;
	get_elt(ptr, token.end(), elt1);

	if (*ptr != '(')
	{
		std::ostringstream err_msg;
		err_msg << "Element name must be followed by " <<
			"parentheses in redox couple, " << token << ".";
		error_msg(err_msg.str().c_str(), PHRQ_io::OT_CONTINUE);
		incr_input_error();
		return PARSER_ERROR;
	}

	int
		paren_count = 1;
	std::string paren1 = "(";
	while (ptr != token.end())
	{
		++ptr;
		if (*ptr == '/' || ptr == token.end())
		{
			std::ostringstream err_msg;
			err_msg << "End of line or  " "/"
				" encountered before end of parentheses, " << token << ".";
			error_msg(err_msg.str().c_str(), PHRQ_io::OT_CONTINUE);
			return PARSER_ERROR;
		}
		paren1.insert(paren1.end(), *ptr);	// element.push_back(c);
		if (*ptr == '(')
			++paren_count;
		if (*ptr == ')')
			--paren_count;
		if (paren_count == 0)
			break;
	}

	++ptr;
	if (ptr == token.end() || *ptr != '/')
	{
		std::ostringstream err_msg;
		err_msg << " " "/" " must follow parentheses " <<
			"ending first half of redox couple, " << token << ".";
		error_msg(err_msg.str().c_str(), PHRQ_io::OT_CONTINUE);
		return PARSER_ERROR;
	}
	++ptr;
	std::string elt2;
	get_elt(ptr, token.end(), elt2);
	if (elt1.compare(elt2) != 0)
	{
		std::ostringstream err_msg;
		err_msg << "Redox couple must be two redox states " <<
			"of the same element, " << token << ".";
		error_msg(err_msg.str().c_str(), PHRQ_io::OT_CONTINUE);
		return PARSER_ERROR;
	}
	if (*ptr != '(')
	{
		std::ostringstream err_msg;
		err_msg << "Element name must be followed by "
			"parentheses in redox couple, " << token << ".";
		error_msg(err_msg.str().c_str(), PHRQ_io::OT_CONTINUE);
		incr_input_error();
		return PARSER_ERROR;
	}
	std::string paren2 = "(";
	paren_count = 1;

	while (ptr != token.end())
	{
		++ptr;
		if (*ptr == '/' || ptr == token.end())
		{
			std::ostringstream err_msg;
			err_msg << "End of line or  " "/"
				" encountered before end of parentheses, " << token << ".";
			error_msg(err_msg.str().c_str(), PHRQ_io::OT_CONTINUE);
			return PARSER_ERROR;
		}
		paren2.insert(paren2.end(), *ptr);	// element.push_back(c);
		if (*ptr == '(')
			++paren_count;
		if (*ptr == ')')
			--paren_count;
		if (paren_count == 0)
			break;
	}
	if (paren1.compare(paren2) < 0)
	{
		token = elt1 + paren1 + std::string("/") + elt2 + paren2;
	}
	else if (paren1.compare(paren2) > 0)
	{
		token = elt2 + paren2 + std::string("/") + elt1 + paren1;
	}
	else
	{
		std::ostringstream err_msg;
		err_msg << "Both parts of redox couple are the same, " <<
			token << ".";
		error_msg(err_msg.str().c_str(), PHRQ_io::OT_CONTINUE);
		return PARSER_ERROR;
	}
	return PARSER_OK;
}

template <class T>
CParser::STATUS_TYPE CParser::addPair(std::map < std::string, T >&totals,
									  std::istream::pos_type & pos)
{
	std::string token;
	T d;
	CParser::TOKEN_TYPE j;

	m_line_iss.seekg(pos);

	j = copy_token(token, pos);

	if (j == CParser::TT_EMPTY)
		return PARSER_OK;

	if (!(m_line_iss >> d))
	{
		return PARSER_ERROR;
	}
	totals[token] = d;
	return PARSER_OK;
}

int
CParser::getOptionFromLastLine(const std::vector < std::string > &opt_list,
							   std::string::iterator & next_char, bool flag_error)
{
	//
	// Read a line and check for options
	//
	int j;
	int /*  opt_l,  */ opt;
	std::string::iterator opt_ptr;
	std::string option;

	//
	// Read line
	//
	PHRQ_io::LINE_TYPE lt = m_line_type;
	if (lt == PHRQ_io::LT_EOF)
	{
		j = OPT_EOF;
	}
	else if (lt == PHRQ_io::LT_KEYWORD)
	{
		j = OPT_KEYWORD;
	}
	else if (lt == PHRQ_io::LT_OPTION)
	{
		opt_ptr = m_line.begin();
		std::string::iterator end = m_line.end();
		copy_token(option, opt_ptr, end);
		if (find_option(option, &opt, opt_list, false) == CParser::FT_OK)
		{
			j = opt;
			m_line_save.replace(m_line_save.find(option), option.size(),
								opt_list[opt]);
			m_line.replace(m_line.find(option), option.size(), opt_list[opt]);
			opt_ptr = m_line.begin();
			std::string::iterator end = m_line.end();
			copy_token(option, opt_ptr, end);
			next_char = opt_ptr;
			if (true)			// pr.echo_input == TRUE
			{
				if (true)		// database_file == NULL
				{
					std::ostringstream msg;
					msg << "\t" << m_line_save << "\n";
					io->output_msg(msg.str().c_str());
				}
			}
		}
		else
		{
			if (flag_error)
			{
				if (true)			// (database_file == NULL)
				{
					std::ostringstream msg;
					msg << "\t" << m_line_save << "\n";
					io->output_msg(msg.str().c_str());
				}
				std::ostringstream err;
				err << "Unknown option." << "\n";
				err << m_line_save << "\n";
				error_msg(err.str().c_str());
			}
			j = OPT_ERROR;
			next_char = m_line.begin();
		}
	}
	else
	{
		opt_ptr = m_line.begin();
		std::string::iterator end = m_line.end();
		copy_token(option, opt_ptr, end);
		if (find_option(option, &opt, opt_list, true) == FT_OK)
		{
			j = opt;
			next_char = opt_ptr;
		}
		else
		{
			j = OPT_DEFAULT;
			next_char = m_line.begin();
		}
		if (true)				// pr.echo_input == TRUE
		{
			if (true)			// database_file == NULL
			{
#if !defined(R_SO)
				std::cout << "\t" << m_line_save << "\n";
#endif
			}
		}
	}
	return (j);
}

int
CParser::getOptionFromLastLine(const std::vector < std::string > &opt_list,
							   std::istream::pos_type & next_pos, bool flag_error)
{
	//
	// Read a line and check for options
	//
	int j;
	int opt;
	std::istream::pos_type pos_ptr;
	std::string option;

	//
	// Read line
	//
	PHRQ_io::LINE_TYPE lt = m_line_type;
	if (lt == PHRQ_io::LT_EOF)
	{
		j = OPT_EOF;
	}
	else if (lt == PHRQ_io::LT_KEYWORD)
	{
		j = OPT_KEYWORD;
	}
	else if (lt == PHRQ_io::LT_OPTION)
	{
		std::string::iterator opt_ptr = m_line.begin();
		std::string::iterator end = m_line.end();
		copy_token(option, opt_ptr, end);
		if (find_option(option.substr(1), &opt, opt_list, false) == FT_OK)
		{
			// replace -option with option
			j = opt;
			m_line_save.replace(m_line_save.find(option), option.size(),
								opt_list[opt]);
			m_line.replace(m_line.find(option), option.size(), opt_list[opt]);

			// reset iss
			m_line_iss.str(m_line);
			m_line_iss.seekg(0, std::ios_base::beg);
			m_line_iss.clear();

			pos_ptr = 0;
			copy_token(option, pos_ptr);
			next_pos = pos_ptr;
			//if (this->echo_file)			// pr.echo_input == TRUE
			if (false)
			{
				if (true)		// database_file == NULL
				{
					std::ostringstream msg;
					msg << "\t" << m_line_save << "\n";
					io->output_msg(msg.str().c_str());
				}
			}
		}
		else
		{
			if (flag_error) // don`t throw error
			{
				if (true)			// (database_file == NULL)
				{
					std::ostringstream msg;
					msg << "\t" << m_line_save << "\n";
					io->output_msg(msg.str().c_str());
				}
				error_msg("Unknown option.", PHRQ_io::OT_CONTINUE);
				error_msg(m_line_save.c_str(), PHRQ_io::OT_CONTINUE);
				incr_input_error();
			}
			j = OPT_ERROR;
			next_pos = pos_ptr;
		}
	}
	else
	{
		pos_ptr = 0;
		copy_token(option, pos_ptr);
		if (find_option(option, &opt, opt_list, true) == FT_OK)
		{
			j = opt;
			next_pos = pos_ptr;
		}
		else
		{
			j = OPT_DEFAULT;
			next_pos = 0;
		}
		if (true)				// pr.echo_input == TRUE
		{
			if (true)			// database_file == NULL
			{
				std::ostringstream msg;
				msg << "\t" << m_line_save << "\n";
				io->output_msg(msg.str().c_str());
			}
		}
	}
	return (j);
}
int CParser::
incr_input_error()
{
	return ++m_input_error;
}

CParser::TOKEN_TYPE CParser::copy_title(std::string & token,
										std::string::iterator & begin,
										std::string::iterator & end)
{
	if (begin != end)
	{
		std::string::iterator b = begin;
		std::string::iterator e = end;
		for (; b < end && (::isspace(*b) || (*b == ',')); ++b);
		begin = b;
		if (*begin == '"')
		{
			begin = ++b;
			for (; begin != end && !(*begin == '"'); ++begin);
			e = begin;
			if (begin != end && *begin == '"')
			{
				e = begin++;
			}
		}
		else if (*begin == '\'')
		{
			begin = ++b;
			for (; begin != end && !(*begin == '\''); ++begin);
			e = begin;
			if (begin != end && *begin == '\'')
			{
				e = begin++;
			}
		}
		else
		{
			for (; begin < end && !(*begin == ',') && !(::isspace(*begin)); ++begin);
			e = begin;
		}
		token.assign(b, e);
	}
	else
	{
		token.resize(0);
	}
	token = trim(token);
	return token_type(token);
}
bool CParser::get_true_false(std::istream::pos_type & pos, bool def)
{
	std::string token;
	this->copy_token(token, pos);
	std::string::iterator b = token.begin();
	for (; b != token.end() && (::isspace(*b)); ++b);
	if (b != token.end())
	{
		if (*b == 'f' || *b == 'F')
		{
			return false;
		}
		else if (*b == 't' || *b == 'T')
		{
			return true;
		}
	}
	return def;
}
CParser::TOKEN_TYPE CParser::get_rest_of_line(std::string &token)
{
	token.clear();
	int j;
	while ((j = m_line_iss.get()) != std::char_traits < char >::eof())
	{
		char c = (char) j;
		token += c;
	}
	token = trim(token);
	return token_type(token);
}
CParser::TOKEN_TYPE CParser::parse_delimited(std::string & source, std::string & result,
										const std::string& t = " \t")
{

	size_t pos = source.find_first_of(t);
	std::string temp;
	if (pos != std::string::npos)
	{
		result = source.substr(0, pos);
		temp = source.substr(pos+1);
		source = temp;
	}
	else
	{
		result = source;
		source.clear();
	}
	std::string str = result;

	return token_type(trim_left(str));
}
