/**
 *  @file CKParser.cpp
 *
 */

// Copyright 2001  California Institute of Technology

#include <numeric>
#include <algorithm>
#include <fstream>
#include <iomanip>
#include <math.h>

#include "CKParser.h"
#include "ckr_utils.h"
#include "writelog.h"
#include <cstdio>
#include <cstdlib>

using namespace std;

namespace ckr
{


static string int2s(int n, std::string fmt="%d")
{
    char buf[30];
    sprintf(buf, fmt.c_str(), n);
    return string(buf);
}

/// Exception class for syntax errors.
CK_SyntaxError::CK_SyntaxError(std::ostream& f,
                               const std::string& s, int linenum)
    : m_out(f)
{
    m_msg += "Syntax error: " + s;
    if (linenum > 0) {
        m_msg += "  (line " + int2s(linenum) + ")\n";
    }
}


static int parseGroupString(std::string str, std::vector<std::string>& esyms,
                            vector_int& result);

/**
 *  Throw an exception if one of the four lines that must have
 *  1, 2, 3, or 4 in column 80 do not.
 */
static void illegalThermoLine(std::ostream& f,
                              char n, int linenum = -1)
{
    throw CK_SyntaxError(f, "column 80 must "
                         "contain an integer", linenum);
};


/**
 *  Throw an exception if number string is bad
 */
static void illegalNumber(std::ostream& f,
                          std::string s, int linenum = -1)
{
    string msg = "illegal number: "+s;
    throw CK_SyntaxError(f, msg, linenum);
};


extern void getDefaultAtomicWeights(std::map<std::string,double>& weights);

static string d2e(string s)
{
    size_t n;
    size_t sz = s.size();
    string r = s;
    char ch;
    for (n = 0; n < sz; n++) {
        ch = s[n];
        if (ch == 'D') {
            r[n] = 'E';
        } else if (ch == 'd') {
            r[n] = 'e';
        }
    }
    return r;
}

static double de_atof(std::string s)
{
    string r = d2e(s);
    //double rval = Cantera::atofCheck(r.c_str());
    double rval = atof(r.c_str());
    return rval;
}

/**
 *  Check validity of the temperatures defining the
 *  temperature ranges for the NASA9 polynomial species thermodynamic
 *  property fits.
 *  @param log     log file output stream
 *  @param temp    Vector of temperatures
 */
static void checkNASA9Temps(std::ostream& log, vector_fp& temp)
{
    int i;
    for (i = 1; i < (int) temp.size(); i++) {
        double tlow = temp[i-1];
        double thigh = temp[i];
        if (thigh <= tlow) {
            string sss =  "error reading temperature";
            throw CK_SyntaxError(log, sss);
        }
    }
}


static double getNumberFromString(std::string s)
{
    bool inexp = false;
    removeWhiteSpace(s);
    int sz = static_cast<int>(s.size());
    char ch;
    for (int n = 0; n < sz; n++) {
        ch = s[n];
        if (!inexp && (ch == 'E' || ch == 'e' || ch == 'D' || ch == 'd')) {
            inexp = true;
        } else if (ch == '+' || ch == '-') {
            if (n > 0 && (s[n-1] != 'E' && s[n-1]
                          != 'e' && s[n-1] != 'd' && s[n-1] != 'D')) {
                return UNDEF;
            }
        } else if (ch != '.' && (ch < '0' || ch > '9')) {
            return UNDEF;
        }
    }
    return de_atof(s);
}

/**
 *  Add an element to a species.
 *  @param symbol  element symbol
 *  @param atoms   number of atoms of this element in the
 *                 species (may be non-integral)
 *  @param sp      Species object to add element to
 *  @param log     log file output stream
 */
static void addElement(std::string symbol, double atoms,
                       Species& sp, std::ostream& log)
{

    if (atoms != 0.0) {
        Constituent e;
        e.name = symbol;
        e.number = atoms;
        sp.elements.push_back(e);
        sp.comp[symbol] = atoms;
    }
}


/**
 *  Check validity of the three temperatures defining the two
 *  temperature ranges for the NASA polynomial species thermodynamic
 *  property fits.
 *  @param log     log file output stream
 *  @param tmin    minimum temperature
 *  @param tmid    intermediate temperature
 *  @param tmax    maximum temperature
 */
static void checkTemps(std::ostream& log, double tmin,
                       double tmid, double tmax)
{
    if (tmin == 0.0 || tmid == 0.0 || tmax == 0.0) {
        throw CK_SyntaxError(log,
                             "error reading Tmin, Tmid, or Tmax");
    }
}

static void getSpecies(std::string s,
                       int n, vector<RxnSpecies>& species, bool debug,
                       std::ostream& log)
{
    removeWhiteSpace(s);
    // break string into substrings at the '+' characters separating
    // species symbols

    bool inplus = true;
    vector<int> pluses;
    vector<string> sp;
    for (int i = n-1; i >= 0; i--) {
        if (!inplus && s[i] == '+') {
            pluses.push_back(i);
            inplus = true;
        } else if (inplus && s[i] != '+') {
            inplus = false;
        }
    }
    pluses.push_back(-1);
    size_t np = pluses.size();
    size_t loc, nxt;
    for (size_t nn = 0; nn < np; nn++) {
        loc = pluses.back();
        pluses.pop_back();
        if (nn == np-1) {
            nxt = s.size();
        } else {
            nxt = pluses.back();
        }
        sp.push_back(s.substr(loc+1,nxt-loc-1));
    }

    string r, num;
    size_t sz, j, strt=0;
    RxnSpecies ss;
    for (size_t nn = 0; nn < sp.size(); nn++) {
        r = sp[nn];
        sz = r.size();
        for (j = 0; j < sz; j++) {
            if (!((r[j] >= '0' && r[j] <= '9') || r[j] == '.')) {
                strt = j;
                break;
            }
        }
        ss.name = r.substr(strt,sz);
        if (strt == 0) {
            ss.number = 1.0;
        } else {
            ss.number = atof(r.substr(0,strt).c_str());
        }
        species.push_back(ss);
        if (debug) {
            log << ss.number << "  " << ss.name << endl;
        }
    }
}


/**
 *  given a string specifying either the reactant or product side of a
 *  reaction equation, construct a list of Constituent objects
 *  containing the species symbols and stoichiometric coefficients.
 *  @todo allow non-integral stoichiometric coefficients
 */
int getGroups(std::string::const_iterator begin,
              std::string::const_iterator end, std::vector<std::string>& esyms,
              std::vector<grouplist_t>& rxngroups)
{
    bool ingroup = false;
    rxngroups.clear();
    string g = "";
    group_t igrp;
    grouplist_t groups;

    for (; begin != end; ++begin) {
        if (*begin == '(') {
            ingroup = true;
            g = "";
        } else if (*begin == ')') {
            ingroup = false;
            igrp.clear();
            if (parseGroupString(g, esyms, igrp) >= 0) {
                groups.push_back(igrp);
            } else {
                return -1;
            }
        } else if (*begin == '+') {
            rxngroups.push_back(groups);
            groups.clear();
        } else if (ingroup && *begin != ' ') {
            g += *begin;
        }
    }
    rxngroups.push_back(groups);
    return 1;
}


/**
 * Constructor. Construct a parser for the specified input file.
 */
CKParser::CKParser(std::istream* infile, const std::string& fname,
                   std::ostream* log) :
    verbose(true),
    debug(false),
    m_line(0),
    m_nasafmt(false),
    m_nasa9fmt(false)
{
    m_ckfile = infile;
    m_ckfilename = fname;
    m_log = log;
    m_last_eol = '\n';
}



//    Get a line from the input file, and return it in string s.
/*
 *  If the line contains a comment character (!), then return only the
 *  portion preceding it.  Non-printing characters are replaced by
 *  spaces.
 *
 *  The input file is m_ckfile, an istream.
 *
 *  @param s        On return, s contains the line read from the
 *                  input file.
 *  @param comment  On return, comment contains the text following the
 *                  comment character on the line, if any.
 */
void CKParser::getCKLine(std::string& s, std::string& comment)
{

    // Chemkin comment character
    const char commentChar = '!';

    // Cantera anti-comment character
    const char undoCommentChar = '%';

    // carriage return
    const char char13 = char(13);

    // linefeed
    const char char10 = char(10);

    istream& f = *m_ckfile;

    // if putCKLine was called to 'put back' a line, then return this
    // line, instead of reading a new one

    if (!m_buf.empty()) {
        s = m_buf;
        m_buf = "";
        comment = m_comment;
        m_comment = "";
        return;
    }

    // read a line, convert non-printing characters to ' ',
    // and remove comments

    comment = "";
    string line;

    line = "";
    char ch = ' ';
    while (1 > 0) {
        f.get(ch);
        if (!f || f.eof()) {
            break;
        }

        // convert tabs to spaces
        if (ch == '\t') {
            ch = ' ';
        }

        // if an end-of-line character is seen, then break.
        // Check for all common end-of-line characters.
        if (ch == char13 || (ch == char10 && (m_last_eol != char13)))  {
            m_last_eol = ch;
            break;
        }
        if (isprint(ch)) {
            line += ch;
        }
    }

    string::size_type icom = line.find(commentChar);

    // lines that begin with !% are not comments for Cantera
    if (icom == 0 && line.size() > 1 && line[1] == undoCommentChar) {
        line[0] = '%';
        line[1] = ' ';
        icom = line.find(commentChar);
    }
    int len = static_cast<int>(line.size());

    for (int i = 0; i < len; i++) if (!isprint(line[i])) {
            line[i] = ' ';
        }
    if (icom != string::npos) {
        s = line.substr(0, icom);
        comment = line.substr(icom+1,len-icom-1);
    } else {
        s = line;
    }

    if (!f || f.eof()) {
        s = "<EOF>";
        comment = "";
        return;
    }
    m_line++;
}


/**
 *
 *   Put back a line read from the input file. The next call to
 *   getCKLine will return this line.
 *
 */

void CKParser::putCKLine(std::string& s, std::string& comment)
{
    m_buf = s;
    m_comment = comment;
}


bool CKParser::advanceToKeyword(const std::string& kw, const std::string& stop)
{
    string s, c;
    do {
        getCKLine(s,c);
        if (match(s,"<EOF>")) {
            return false;
        }
        if (match(s,kw)) {
            putCKLine(s,c);
            return true;
        }
    } while (!match(s,stop));
    putCKLine(s,c);
    return false;
}



/**
 *
 *  Read the element section of the input file, and return
 *  a list of the elements found.
 *
 */

bool CKParser::readElementSection(elementList& elements)
{
    string s, comment;
    int firsttok;
    vector<string> toks;

    map<string,double> defaultWeights;
    //ct::ctmap_sd defaultWeights;
    getDefaultAtomicWeights(defaultWeights);

    //istream& f = m_ckfile;
    int ntok;

    elements.clear();
    while (1 > 0) {

next:
        if (advanceToKeyword("ELEM", "SPEC")) {
            firsttok = 1;
            while (1 > 0) {
                do {
                    getCKLine(s, comment);
                    getTokens(s, static_cast<int>(s.size()), toks);
                    ntok = static_cast<int>(toks.size());
                } while (ntok == 0);

                if (firsttok == 0 && isKeyword(toks[0])) {
                    putCKLine(s,comment);
                    goto next;
                }
                for (int i = firsttok; i < ntok; i++) {
                    if (match(toks[i],"END")) {
                        goto next;
                    } else {
                        Element el;
                        string wtstr;
                        el.comment = comment;
                        el.index = static_cast<int>(elements.size());
                        if (extractSlashData(toks[i], el.name, wtstr)) {
                            el.atomicWeight = de_atof(wtstr);
                            el.weightFromDB = false;
                        } else {
                            el.atomicWeight = defaultWeights[capitalize(el.name)];
                            el.weightFromDB = true;
                        }
                        if (el.atomicWeight > 0.0) {
                            el.valid = 1;
                        } else {
                            el.valid = 0;
                        }
                        if (find(elements.begin(),
                                 elements.end(), el) < elements.end()) {
                            if (m_log)
                                *m_log << "warning... duplicate element "
                                       << el.name << " (ignored)." << endl;
                        } else {
                            elements.push_back(el);
                        }
                    }
                }
                firsttok = 0;
            }
        } else {
            if (elements.size() == 0) {
                *m_log << "no elements found." << endl;
                return false;
            } else {
                return valid(elements);
            }
        }
    }
    return false;
}



/**
 *
 *  Read the SPECIES section of the input file, and return
 *  a list of species names.
 *
 */
bool CKParser::readSpeciesSection(speciesList& species)
{
    string s, comment;
    int firsttok;
    vector<string> toks;

    int ntok;
    int nsp = 0;

    while (1 > 0) {

next:
        if (advanceToKeyword("SPEC", "THER")) {
            firsttok = 1;
            while (1 > 0) {
                do {
                    getCKLine(s, comment);
                    getTokens(s, static_cast<int>(s.size()), toks);
                    ntok = static_cast<int>(toks.size());
                } while (ntok == 0);

                if (firsttok == 0 && isKeyword(toks[0])) {
                    putCKLine(s,comment);
                    goto next;
                }
                for (int i = firsttok; i < ntok; i++) {
                    if (match(toks[i],"END")) {
                        goto next;
                    } else {
                        Species sp;
                        sp.name = toks[i];
                        if (find(species.begin(), species.end(), sp)
                                < species.end()) {
                            if (m_log)
                                *m_log << "warning... duplicate species "
                                       << sp.name << " (ignored)." << endl;
                        } else {
                            nsp++;
                            sp.index = nsp;
                            species.push_back(sp);
                        }
                    }
                }
                firsttok = 0;
            }
        } else {
            if (species.size() == 0) {
                return false;
            } else {
                return true;
            }
        }
    }
    return false;
}



/**
 *
 * Read species data from THERMO section records.
 *
 * @param names List of species names (input).
 * @param species  Table of species objects holding data from records
 * in THERMO section (output).
 * @param allowExtThermoData True if 'THERMO' specified, false if
 * 'THERMO ALL' specified.
 *
 */

bool CKParser::readThermoSection(std::vector<std::string>& names,
                                 speciesTable& species, vector_fp& temp,
                                 int& optionFlag, std::ostream& log)
{
    string s;
    vector<string> toks;

    double tmin = -1.0, tmid = -1.0, tmax = -1.0;
    if (temp.size() == 3) {
        tmin = temp[0];
        tmid = temp[1];
        tmax = temp[2];
    }

    int nsp = static_cast<int>(names.size());

    string comment;

    // read lines until THERMO section is found. But if EOF reached or
    // start of REACTIONS section, then there is no THERMO section.
    do {
        getCKLine(s,comment);
        if (match(s,"<EOF>")) {
            return false;
        }
        if (match(s,"REAC")) {
            putCKLine(s,comment);
            return false;
        }
    } while (!match(s,"THER"));

    // read the tokens on the THERMO line
    getTokens(s, static_cast<int>(s.size()), toks);
    m_nasafmt = false;
    if (toks.size() >= 2) {
        unsigned int itt;
        for (itt = 1; itt < toks.size(); itt++) {
            if (match(toks[itt],"ALL")) {
                optionFlag = NoThermoDatabase;
            } else if (match(toks[itt],"NO_TMID")) {
                m_nasafmt = true;
                log << "\nOption 'NO_TMID' specified. Default "
                    "midpoint temperature\n";
                log << "will be used for all species.\n\n";
            } else if (match(toks[itt], "NASA9")) {
                m_nasa9fmt = true;
                log << "Option NASA9 specified: Use new "
                    "nasa input file format\n\n";
            } else if (match(toks[itt], "NASA")) {
                m_nasa9fmt = false;
                log << "Option NASA specified: Use old "
                    "nasa input file format\n\n";
            } else throw CK_SyntaxError(log,
                                            "unrecognized THERMO option.", m_line);
        }
    }

    // if "THERMO ALL" specified, or if optionFlag is set to HasTempRange,
    // then the next line must contain the default temperatures
    // for the database.

    if (optionFlag == NoThermoDatabase || optionFlag == HasTempRange) {
        getCKLine(s, comment);
        getTokens(s, static_cast<int>(s.size()), toks);
        if (m_nasa9fmt) {
            //
            // For NASA9 polynomials, the format is
            //   t1  t2 t3 t4 date
            // when there are 3 temperature regions
            //
            size_t nreg = toks.size() - 2;
            if (nreg >= 1) {
                temp.resize(nreg+1);
                for (size_t i = 0; i <= nreg; i++) {
                    temp[i] = de_atof(toks[i]);
                }
                string defaultDate = toks[nreg+1];
            } else {
                throw CK_SyntaxError(log, "Default temp region card is bad", m_line);
            }
            if (verbose) {
                log.flags(ios::showpoint | ios::fixed);
                log.precision(2);
                log << endl << " Default # of temperature regions: "
                    << nreg << endl;
                log << "          ";
                for (size_t i = 0; i <= nreg; i++) {
                    log << temp[i] << "  ";
                }
                log << endl;
            }
            checkNASA9Temps(log, temp);
        } else {
            //
            // For NASA polynomials, the format is
            //   tlow tmid thigh
            // There are always 2 temperature regions
            //
            if (toks.size() >= 3) {
                tmin = de_atof(toks[0]);
                tmid = de_atof(toks[1]);
                tmax = de_atof(toks[2]);
            }

            if (verbose) {
                log.flags(ios::showpoint | ios::fixed);
                log.precision(2);
                log << endl << " default Tlow, Tmid, Thigh: " << tmin << "  "
                    << tmid << "  " << tmax << endl;
            }
            checkTemps(log, tmin, tmid, tmax);
            temp.clear();
            temp.push_back(tmin);
            temp.push_back(tmid);
            temp.push_back(tmax);
        }
    }

    /// XXXX BRANCH TO THE DIFFERENT THERMO READERS HERE

    // Check to see that we expect to be reading a NASA formatted file
    if (m_nasa9fmt) {
        bool ok =  readNASA9ThermoSection(names, species, temp,
                                          optionFlag, log);
        if (!ok) {
            throw CK_SyntaxError(log,
                                 "In NASA parser. However, we expect a NASA9 file format",
                                 -1);
        }
        return ok;
    }

    // now read in all species records that have names in list 'names'

    bool getAllSpecies = (nsp > 0 && match(names[0],"<ALL>"));
    if (getAllSpecies) {
        names.clear();
    }

    map<string, int> dup; // used to check for duplicate THERMO records
    bool already_read;

    while (1 > 0) {
        if (nsp == 0) {
            break;
        }
        already_read = false;

        Species spec;
        readThermoRecord(spec);

        if (spec.name == "<END>") {
            break;
        }

        // check for duplicate thermo data
        if (dup[spec.name] == 2) {
            log << "Warning: more than one THERMO record for "
                << "species " << spec.name << endl;
            log << "Record at line " << m_line
                << " of " << m_ckfilename << " ignored." << endl;
            already_read = true;
        }
        dup[spec.name] = 2;

        if (!already_read && (getAllSpecies
                              || (find(names.begin(), names.end(), spec.name)
                                  < names.end()))) {

            if (spec.tmid == 0.0) {
                spec.tmid = tmid;
                log << "Warning: default Tmid used for species "
                    << spec.name << endl;
                if (spec.tmid < 0.0) {
                    log << "Error: no default Tmid has been entered!"
                        << endl;
                }
            }
            species[spec.name] = spec;

            if (verbose) {
                log << endl << "found species " << spec.name;
                log << " at line " << m_line
                    << " of " << m_ckfilename;
                writeSpeciesData(log, spec);
            }
            checkTemps(log, spec.tlow, spec.tmid, spec.thigh);
            if (getAllSpecies) {
                names.push_back(spec.name);
                nsp = static_cast<int>(names.size());
            } else {
                nsp--;
            }
        }
    }
    return true;
}


/**
 *
 * Read one 4-line species definition record in NASA format.
 *
 */

void CKParser::readThermoRecord(Species& sp)
{
    string s;
    string numstr;
    double cf;

    // look for line 1, but if a keyword is found first or the end of
    // the file is reached, return "<END>" as the species name
    string comment;
    do {
        getCKLine(s, comment);
        if (isKeyword(s) || match(s, "<EOF>")) {
            sp.name = "<END>";
            putCKLine(s, comment);
            return;
        }
    } while ((s.size() < 80) || (s[79] != '1'));

    // next 4 lines must be the NASA-format lines without intervening
    // comments.

    //------------- line 1 ---------------------------

    if (s[79] != '1') {
        illegalThermoLine(*m_log, s[79], m_line);
    }

    // extract the species name and the id string (date)
    string nameid = s.substr(0,24);
    vector<string> toks;
    getTokens(nameid, static_cast<int>(nameid.size()), toks);
    sp.name = toks[0];
    sp.id = "";
    unsigned int j;
    for (j = 1; j < toks.size(); j++) {
        if (j > 1) {
            sp.id += ' ';
        }
        sp.id += toks[j];
    }
    int iloc;
    string elementSym;
    double atoms;
    int i;

    // elemental composition (first 4 elements)
    for (i = 0; i < 4; i++) {
        elementSym = "";
        iloc = 24 + 5*i;
        if (s[iloc] != ' ') {
            if (s[iloc+1] != ' ') {
                elementSym = s.substr(iloc,2);
            } else {
                elementSym = s.substr(iloc,1);
            }
        } else if (s[iloc+1] != ' ') {
            elementSym = s.substr(iloc+1,1);
        }
        atoms = de_atof(s.substr(iloc+2,3));
        addElement(elementSym, atoms, sp, *m_log);
    }

    // single-character phase descriptor
    sp.phase = s[44];

    // low, high, and mid temperatures
    sp.tlow = de_atof(s.substr(45,10));
    sp.thigh = de_atof(s.substr(55,10));

    if (!m_nasafmt) {
        sp.tmid = de_atof(s.substr(65,8));

        // fifth element, if any
        elementSym = "";
        if (s[73] != ' ') {
            elementSym += s[73];
        }
        if (s[74] != ' ') {
            elementSym += s[74];
        }
        atoms = de_atof(s.substr(75,3));
        addElement(elementSym, atoms, sp, *m_log);

        // additional elements, if any
        elementSym = "";
        int loc = 80;
        while (loc < (int)(s.size()-9)) {
            elementSym = "";
            if (s[loc] != ' ') {
                elementSym += s[loc];
            }
            if (s[loc+1] != ' ') {
                elementSym += s[loc+1];
            }
            atoms = de_atof(s.substr(loc+2,8));
            addElement(elementSym, atoms, sp, *m_log);
            loc += 10;
        }
    }

    //-------------- line 2 ----------------------------

    getCKLine(s, comment);
    if (s[79] != '2') {
        illegalThermoLine(*m_log, s[79], m_line);
    }
    for (i = 0; i < 5; i++) {
        numstr = s.substr(i*15, 15);
        cf = getNumberFromString(numstr);
        if (cf == UNDEF) {
            illegalNumber(*m_log, numstr, m_line);
        }
        sp.highCoeffs.push_back(cf);
    }

    //-------------- line 3 ----------------------------

    getCKLine(s, comment);
    if (s[79] != '3') {
        illegalThermoLine(*m_log, s[79], m_line);
    }
    for (i = 0; i < 2; i++) {
        numstr = s.substr(i*15, 15);
        cf = getNumberFromString(numstr);
        if (cf == UNDEF) {
            illegalNumber(*m_log, numstr, m_line);
        }
        sp.highCoeffs.push_back(cf);
    }
    for (i = 2; i < 5; i++) {
        numstr = s.substr(i*15, 15);
        cf = getNumberFromString(numstr);
        if (cf == UNDEF) {
            illegalNumber(*m_log, numstr, m_line);
        }
        sp.lowCoeffs.push_back(cf);
    }

    //--------------- line 4 ----------------------------

    getCKLine(s, comment);
    if (s[79] != '4') {
        illegalThermoLine(*m_log, s[79], m_line);
    }
    for (i = 0; i < 4; i++) {
        numstr = s.substr(i*15, 15);
        cf = getNumberFromString(numstr);
        if (cf == UNDEF) {
            illegalNumber(*m_log, numstr, m_line);
        }
        sp.lowCoeffs.push_back(cf);
    }
    sp.valid = 1;
}



void CKParser::missingAuxData(const std::string& kw)
{
    throw CK_SyntaxError(*m_log, kw +
                         " keyword must be followed by slash-delimited data.", m_line);
}


/**
 *  Parse the REACTION section of the input file, and return
 *  a list of Reaction objects and the units.
 */
bool CKParser::readReactionSection(const std::vector<std::string>& speciesNames,
                                   vector<string>& elementNames, reactionList& reactions,
                                   ReactionUnits& units)
{
    string s, comment;
    vector<string> toks;
    int nRxns = 0;

    vector<string> rc, pr;
    vector_int c;

    // advance to the beginning of the REACTION section
    do {
        getCKLine(s, comment);
        if (match(s, "<EOF>")) {
            return false;
        }
    } while (!match(s,"REAC"));


    // look for units specifications

    getTokens(s, static_cast<int>(s.size()), toks);
    string tok;
    units.ActEnergy = Cal_per_Mole;
    units.Quantity = Moles;
    unsigned int ir;
    for (ir = 1; ir < toks.size(); ir++) {
        tok = toks[ir];
        if (match(tok,"CAL/MOLE")) {
            units.ActEnergy = Cal_per_Mole;
        } else if (match(tok,"KCAL/MOLE")) {
            units.ActEnergy = Kcal_per_Mole;
        } else if (match(tok,"JOULES/MOLE")) {
            units.ActEnergy = Joules_per_Mole;
        } else if (match(tok,"KJOULES/MOLE")) {
            units.ActEnergy = Kjoules_per_Mole;
        } else if (match(tok,"KELVINS")) {
            units.ActEnergy = Kelvin;
        } else if (match(tok,"EVOLTS")) {
            units.ActEnergy = Electron_Volts;
        } else if (match(tok,"MOLES")) {
            units.Quantity = Moles;
        } else if (match(tok,"MOLECULES")) {
            units.Quantity = Molecules;
        }
    }

    Reaction rxn;

    vector<string> cm;
    bool ok = true;

    if (debug) {
        *m_log << "CKParser::readReactions  ---> DEBUG MODE" << endl;
    }

    while (1 > 0) {

        // skip blank or comment lines
        do {
            getCKLine(s, comment);
            cm.push_back(comment);
        } while (s == "" && comment[0] != '%');

#undef DEBUG_LINE
#ifdef DEBUG_LINE
        *m_log << "Line: " << s << endl;
#endif
        // end of REACTION section or EOF
        /// @todo does this handle case of 1 reaction correctly?
        if (isKeyword(s) || s == "<EOF>") {
            if (nRxns > 0) {
                rxn.number = nRxns;
                reactions.push_back(rxn);
                //rxn.comment.clear();
            }
            if (nRxns > 0) {
                return ok;
            }
            return false;
        }

        // rxn line
        //string::size_type eqloc;
        string sleft, sright;
        bool auxDataLine, metaDataLine;


        // if the line contains an '=', it is the start of a new reaction.
        // In this case, add the previous reaction to the output list,
        // increment the number of reactions, and start processing the
        // new reaction.

        size_t eqloc = s.find_first_of("=");
        metaDataLine = false;
        auxDataLine = false;

        // look for a metadata line
        if (s[0] == '%') {
            metaDataLine = true;
            if (eqloc > 0 && eqloc < s.size()) {
                int ierr, ierp;
                vector<grouplist_t> rg, pg;
                s[eqloc] = ' ';
                ierr = getGroups(s.begin(), s.begin() + eqloc,
                                 elementNames, rg);
                ierp = getGroups(s.begin() + eqloc, s.end(),
                                 elementNames, pg);
                unsigned int nr =
                    static_cast<unsigned int>(rxn.reactants.size());
                unsigned int nratoms = 0;
                for (unsigned int ij = 0; ij < nr; ij++) {
                    nratoms += int(rxn.reactants[ij].number);
                }
                if (rg.size() != nratoms)
                    throw CK_SyntaxError(*m_log,
                                         " groups not specified for all reactants", m_line);
                else if (ierr < 0)
                    throw CK_SyntaxError(*m_log,
                                         " error in reactant group specification", m_line);
                for (unsigned int ir = 0; ir < nr; ir++) {
                    rxn.reactants[ir].groups = rg[ir];
                }
                unsigned int np =
                    static_cast<unsigned int>(rxn.products.size());
                unsigned int npatoms = 0;
                for (unsigned int ik = 0; ik < np; ik++) {
                    npatoms += int(rxn.products[ik].number);
                }
                if (pg.size() != npatoms)
                    throw CK_SyntaxError(*m_log,
                                         " groups not specified for all products", m_line);
                else if (ierp < 0)
                    throw CK_SyntaxError(*m_log,
                                         " error in product group specification", m_line);
                for (unsigned int ip = 0; ip < np; ip++) {
                    rxn.products[ip].groups = pg[ip];
                }
            }
        }

        else if (eqloc != string::npos && eqloc < s.size()) {
            if (nRxns > 0) {
                rxn.number = nRxns;
                reactions.push_back(rxn);
            }
            nRxns++;
            rxn = Reaction();
            rxn.comment = cm;
            cm.clear();
            if (debug) {
                *m_log << "Parsing reaction " << nRxns << endl;
            }
        } else {
            auxDataLine = true;
        }
        if (comment != "") {
            rxn.lines.push_back(s+'!'+comment);
        } else {
            rxn.lines.push_back(s);
        }

        if (!auxDataLine && !metaDataLine) {

            // depending on the form of the 'equals' symbol,
            // determine whether the reaction is reversible or
            // irreversible, and separate it into strings for
            // each side.

            if (eqloc = s.find("<=>"), eqloc != string::npos) {
                rxn.isReversible = true;
                sleft = s.substr(0, eqloc);
                sright = s.substr(eqloc+3,1000);
            } else if (eqloc = s.find("=>"), eqloc != string::npos) {
                rxn.isReversible = false;
                sleft = s.substr(0, eqloc);
                sright = s.substr(eqloc+2,1000);
            } else if (eqloc = s.find("="), eqloc != string::npos) {
                rxn.isReversible = true;
                sleft = s.substr(0, eqloc);
                sright = s.substr(eqloc+1,1000);
            } else throw CK_SyntaxError(*m_log,
                                            "expected <=>, =>, or =", m_line);

            if (debug) {
                *m_log << s << endl;
                if (rxn.isReversible) {
                    *m_log << "Reaction is reversible." << endl;
                } else {
                    *m_log << "Reaction is irreversible." << endl;
                }
            }

            string::size_type mloc, mloc2;

            // process reactants
            if (debug) {
                *m_log << "Processing reactants..." << sleft << endl;
            }
            removeWhiteSpace(sleft);
            if (debug) *m_log << "After removing white space: "
                                  << sleft << endl;
            rxn.isFalloffRxn = false;

            string sm, mspecies;

            mloc = sleft.find("(+");
            if (mloc != string::npos) {
                sm = sleft.substr(mloc+2, 1000);
                mloc2 = sm.find(")");
                if (mloc2 != string::npos) {
                    mspecies = sm.substr(0,mloc2);
                    rxn.isFalloffRxn = true;
                    rxn.type = Falloff;
                    sleft = sleft.substr(0, mloc);
                    if (mspecies == "M" || mspecies == "m") {
                        rxn.thirdBody = "M";
                    } else {
                        rxn.thirdBody = mspecies;
                    }
                    if (debug) {
                        *m_log << "Falloff reaction. Third body = "
                               << rxn.thirdBody << endl;
                    }
                } else throw CK_SyntaxError(*m_log,
                                                "missing )", m_line);
            }

            else if ((mloc = sleft.find("+M"), mloc != string::npos) ||
                     (mloc = sleft.find("+m"), mloc != string::npos)) {

                if (static_cast<int>(mloc) ==
                        static_cast<int>(sleft.size()) - 2) {
                    rxn.isThreeBodyRxn = true;
                    rxn.type = ThreeBody;
                    sleft = sleft.substr(0, mloc);
                    rxn.thirdBody = "M";
                    if (debug) {
                        *m_log << "Three-body reaction." << endl;
                    }
                } else if (debug) {
                    *m_log << "Reactant string contains +M or +m, but \n"
                           << "not last two characters of string: "
                           << "\"" << sleft << "\"\n"
                           << "NOT a three-body reaction." << endl;
                }
            }

            getSpecies(sleft.c_str(),static_cast<int>(sleft.size()),
                       rxn.reactants, debug, *m_log);
            int ir = static_cast<int>(rxn.reactants.size());
            for (int iir = 0; iir < ir; iir++) {
                if (find(speciesNames.begin(), speciesNames.end(),
                         rxn.reactants[iir].name) >= speciesNames.end())
                    throw CK_SyntaxError(*m_log,
                                         "undeclared reactant species "
                                         +rxn.reactants[iir].name, m_line);
            }


            // process Arrhenius coefficients
            getTokens(sright, static_cast<int>(sright.size()), toks);
            int ntoks = static_cast<int>(toks.size());
            if (ntoks < 3) {
                throw CK_SyntaxError(*m_log,
                                     "expected 3 Arrhenius parameters", m_line);
            }
            rxn.kf.A = de_atof(toks[ntoks - 3]);
            rxn.kf.n = de_atof(toks[ntoks - 2]);
            rxn.kf.E = de_atof(toks[ntoks - 1]);

            // 2/10/03: allow negative prefactor but print a warning
            if (rxn.kf.A < 0.0)
                *m_log << "Warning: negative prefactor at line "
                       << m_line << endl;
            //throw CK_SyntaxError(*m_log, "negative prefactor", m_line);

            if (debug) {
                *m_log << "Processing products..." << sright << endl;
            }
            sright = sright.substr(0, sright.find(toks[ntoks - 3]) - 1);
            if (debug) *m_log << "After removing Arrhenius parameters, "
                                  << "\nproduct string = " << sright << endl;

            removeWhiteSpace(sright);
            if (debug) *m_log << "After removing white space: "
                                  << sright << endl;
            mloc = sright.find("(+");
            if (mloc != string::npos) {
                sm = sright.substr(mloc+2, 1000);
                mloc2 = sm.find(")");
                if (mloc2 != string::npos) {
                    mspecies = sm.substr(0,mloc2);

                    if (rxn.type == ThreeBody)
                        throw CK_SyntaxError(*m_log,
                                             "mismatched +M or (+M)", m_line);

                    rxn.isFalloffRxn = true;
                    rxn.type = Falloff;
                    if (debug) {
                        *m_log << "Falloff reaction. Third body = "
                               << rxn.thirdBody << endl;
                    }
                } else throw CK_SyntaxError(*m_log,
                                                "missing )", m_line);

                sright = sright.substr(0, mloc);

                if (mspecies == "M" || mspecies == "m") {
                    rxn.thirdBody = "M";
                } else {
                    if (rxn.thirdBody != mspecies)
                        throw CK_SyntaxError(*m_log,
                                             "mismatched third body", m_line);
                    rxn.thirdBody = mspecies;
                }
            }

            else if ((mloc = sright.find("+M"), mloc != string::npos) ||
                     (mloc = sright.find("+m"), mloc != string::npos)) {

                if (static_cast<int>(mloc) ==
                        static_cast<int>(sright.size()) - 2) {

                    if (rxn.type == Falloff)
                        throw CK_SyntaxError(*m_log,
                                             "mismatched +M or (+M)", m_line);
                    rxn.isThreeBodyRxn = true;
                    rxn.thirdBody = "M";
                    sright = sright.substr(0, mloc);
                    if (debug) {
                        *m_log << "Three-body reaction." << endl;
                    }
                } else if (debug) {
                    *m_log << "Product string contains +M or +m, but \n"
                           << "not last two characters of string: "
                           << "\"" << sright << "\"\n"
                           << "NOT a three-body reaction." << endl;
                }
            }
            getSpecies(sright.c_str(),static_cast<int>(sright.size()),
                       rxn.products, debug, *m_log);
            int ip = static_cast<int>(rxn.products.size());
            for (int iip = 0; iip < ip; iip++) {
                if (find(speciesNames.begin(), speciesNames.end(),
                         rxn.products[iip].name) >= speciesNames.end())
                    throw CK_SyntaxError(*m_log,
                                         "undeclared product species "+rxn.products[iip].name, m_line);
            }
        }

        // auxiliary data line
        else if (auxDataLine) {

            bool hasAuxData;
            string name, data;
            map<string, int> kwindex;
            while (1 > 0) {

                hasAuxData = extractSlashData(s, name, data);
                if (!hasAuxData && name == "") {
                    break;
                }

                // check for duplicate keyword
                if (kwindex[name]) {
                    throw CK_SyntaxError(*m_log,
                                         "duplicate auxiliary data keyword "
                                         + name, m_line);
                } else {
                    kwindex[name] = 1;
                }


                // low-pressure rate coefficient for falloff rxn

                if (match(name,"LOW")) {
                    vector<string> klow;
                    rxn.type = Falloff;
                    if (hasAuxData) {
                        getTokens(data, static_cast<int>(data.size()), klow);
                        if (klow.size() != 3) {
                            throw CK_SyntaxError(*m_log,
                                                 "expected 3 low-pressure Arrhenius parameters", m_line);
                        }
                        rxn.kf_aux.A = de_atof(klow[0]);
                        rxn.kf_aux.n = de_atof(klow[1]);
                        rxn.kf_aux.E = de_atof(klow[2]);
                    } else {
                        missingAuxData("LOW");
                    }
                }


                // falloff parameters

                else if (match(name,"TROE")) {
                    vector<string> falloff;
                    if (kwindex["SRI"] > 0) {
                        throw CK_SyntaxError(*m_log,
                                             "cannot specify both SRI and TROE", m_line);
                    }

                    if (hasAuxData) {
                        getTokens(data, static_cast<int>(data.size()), falloff);
                        int nf = static_cast<int>(falloff.size());
                        double ff;
                        rxn.falloffType = Troe;
                        for (int jf = 0; jf < nf; jf++) {
                            ff = de_atof(falloff[jf]);
                            rxn.falloffParameters.push_back(ff);
                        }
                    } else {
                        missingAuxData("TROE");
                    }
                }

                else if (match(name,"SRI")) {
                    vector<string> falloff;
                    if (kwindex["TROE"] > 0) {
                        throw CK_SyntaxError(*m_log,
                                             "cannot specify both SRI and TROE", m_line);
                    }
                    if (hasAuxData) {
                        getTokens(data, static_cast<int>(data.size()), falloff);
                        int nf = static_cast<int>(falloff.size());
                        rxn.falloffType = SRI;
                        double ff;
                        for (int jf = 0; jf < nf; jf++) {
                            ff = de_atof(falloff[jf]);
                            rxn.falloffParameters.push_back(ff);
                        }
                    } else {
                        missingAuxData("SRI");
                    }
                }



                // reverse rate coefficient

                else if (match(name,"REV")) {
                    vector<string> krev;
                    if (!rxn.isReversible) {
                        throw CK_SyntaxError(*m_log,
                                             "reverse rate parameters can only be "
                                             "specified for reversible reactions", m_line);
                    }
                    if (hasAuxData) {
                        getTokens(data, static_cast<int>(data.size()), krev);
                        if (krev.size() != 3) {
                            throw CK_SyntaxError(*m_log,
                                                 "expected 3 Arrhenius parameters", m_line);
                        }
                        rxn.krev.A = de_atof(krev[0]);
                        rxn.krev.n = de_atof(krev[1]);
                        rxn.krev.E = de_atof(krev[2]);
                    } else {
                        missingAuxData("REV");
                    }
                }


                else if (match(name,"DUP")) {
                    rxn.isDuplicate = true;
                }

                else if (match(name,"END")) {
                    string c = "";
                    putCKLine(name,c);
                    break;
                }


                // Landau-Teller reaction rate parameters

                else if (match(name,"LT")) {
                    vector<string> bc;
                    rxn.kf.type = LandauTeller;
                    if (hasAuxData) {
                        getTokens(data, static_cast<int>(data.size()), bc);
                        rxn.kf.B = de_atof(bc[0]);
                        rxn.kf.C = de_atof(bc[1]);
                    } else {
                        missingAuxData("LT");
                    }
                }

                else if (match(name,"RLT")) {
                    vector<string> bc;
                    rxn.krev.type = LandauTeller;
                    if (hasAuxData) {
                        getTokens(data, static_cast<int>(data.size()), bc);
                        rxn.krev.B = de_atof(bc[0]);
                        rxn.krev.C = de_atof(bc[1]);
                    } else {
                        missingAuxData("RLT");
                    }
                }


                // chem activation reactions
                else if (match(name,"HIGH")) {
                    vector<string> khigh;
                    rxn.type = ChemAct;
                    if (hasAuxData) {
                        getTokens(data, static_cast<int>(data.size()), khigh);
                        rxn.kf_aux.A = de_atof(khigh[0]);
                        rxn.kf_aux.n = de_atof(khigh[1]);
                        rxn.kf_aux.E = de_atof(khigh[2]);
                    } else {
                        missingAuxData("HIGH");
                    }
                }

                else if (match(name,"FORD")) {
                    vector<string> nmord;
                    if (hasAuxData) {
                        getTokens(data, static_cast<int>(data.size()),
                                  nmord);
                        rxn.fwdOrder[nmord[0]] = de_atof(nmord[1]);
                    } else {
                        missingAuxData("FORD");
                    }
                }

                else if (find(speciesNames.begin(), speciesNames.end(), name)
                         < speciesNames.end()) {
                    if (hasAuxData) {
                        if (rxn.thirdBody == name || rxn.thirdBody == "M") {
                            rxn.e3b[name] = de_atof(data);
                        } else if (rxn.thirdBody == "<none>") {
                            *m_log << "Error in reaction " << nRxns
                                   << ": third-body collision efficiencies cannot be specified"
                                   << " for this reaction type." << endl;
                            throw CK_SyntaxError(*m_log,
                                                 "third-body efficiency error", m_line);
                        } else {
                            *m_log << "Reaction " << nRxns << ": illegal species in enhanced "
                                   << "efficiency specification. Species = "
                                   << name << " rxn.thirdBody = "
                                   << rxn.thirdBody << endl;
                            throw CK_SyntaxError(*m_log,
                                                 "third-body efficiency error", m_line);
                        }
                    } else {
                        missingAuxData(name);
                    }
                } else {
                    Reaction::auxdata vals;
                    vector<string> toks;
                    getTokens(data, static_cast<int>(data.size()), toks);
                    int ntoks = static_cast<int>(toks.size());
                    for (int itok = 0; itok < ntoks; itok++) {
                        vals.push_back(de_atof(toks[itok]));
                    }
                    rxn.otherAuxData[name] = vals;
                }
            }
        }
    }
    return false;
}



int parseGroupString(std::string str, std::vector<std::string>& esyms, group_t& result)
{
    bool inSymbol=true;
    string s = str + '-';
    int i;
    string num, sym;
    int eindx;
    string::const_iterator begin = s.begin();
    string::const_iterator end = s.end();
    vector<string>::iterator e;
    result.resize(static_cast<size_t>(esyms.size()),0);
    for (; begin != end; ++begin) {

        // new element
        if (*begin == '-') {
            e = find(esyms.begin(), esyms.end(), sym);
            if (e == esyms.end()) {
                return -1;
            }
            eindx = static_cast<int>(e - esyms.begin());
            if (num != "") {
                i = atoi(num.c_str());
            } else {
                i = 1;
            }
            result[eindx] = i;
            sym = "";
            num = "";
            inSymbol = true;
        } else if (isdigit(*begin)) {
            inSymbol = false;
            num += *begin;
        } else if (isalpha(*begin) && inSymbol) {
            sym += *begin;
        }
    }
    return 1;
}

}  // ckr namespace





