// Implementation of the methods of the LexNontermList class.
//
// Copyright 2001, KUN
//
// 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 Library General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

// $Id: lexnontermlist.cc,v 1.14 2006/03/22 15:25:08 marcs Exp $

using namespace std;
#include <string>
#include <iostream>
#include <lexicon.h>
#include "globals.h"
#include "lexfileio.h"
#include "globlexafflists.h"
#include "lexnontermlist.h"

void
LexNontermList::dump(void) const
{
    unsigned nont_list_size = get_max_idx() + 1;
    cout << "---- LexNontermList::dump(" << nont_list_size << ")" << endl;

    for (unsigned idx = 0; idx < nont_list_size; ++idx) {
        LexNontMap::const_iterator i = get_entry_by_idx(idx);

        if (i == nonterms.end()) {
            cout << "\tindex " << idx << " is a grammar nonterminal" << endl;
        } else {
            cout << "\t" << (i->second).first;
            cout << ":\tname " << (i->first).first << endl;
            cout << "\t\tarity " << (i->first).second << endl;

            if ((i->second).second) {
                pParam p = (i->second).second;

                cout << "\t\taffix indexes";

                while (p) {
                    cout << " ";
                    cout << get_param_idx(p->elem);

                    p = p->next;
                }

                cout << endl;
            }
        }
    }

    cout << "----" << endl;
}

int
LexNontermList::get_param_idx(Param p) const
{
    ParamType type = p.type;
    
    switch(type) {
        case IdType:
            return lex_affixname_list.get_index(p.value.id);
        case SetType: {
                string set_str(convert_to_string(p.value.set));
                return lex_affixname_list.get_index_of_set(set_str);
            }
        case TextType:
            return lex_textaffix_list.get_index(p.value.id);
        case IntType:
            return lex_intaffix_list.get_index(p.value.number);
        case AnyText:
            return -2;
        case AnyInt:
            return -3;
        default:
            cout << "Internal error in LexNontermList::get_param_idx: Any{Text,Int}" << endl;
            exit(1);
    }
    
    // Never reached:
    return 0;
}

enum LexParamTypes {
    LexParamLattice = 1,
    LexParamText = 2,
    LexParamInt = 3
};

LexNontMap::const_iterator
LexNontermList::get_entry_by_idx(unsigned idx) const
{
    LexNontMap::const_iterator i = nonterms.begin();

    while (i != nonterms.end()) {
        if ((i->second).first == idx) {
            return i;
        }

        ++i;
    }

    return nonterms.end();
}

unsigned
LexNontermList::get_max_idx() const
{
    unsigned nont_list_size = 0;

    for (LexNontMap::const_iterator i = nonterms.begin(); i != nonterms.end(); ++i) {
        if (i->second.first > nont_list_size) {
            nont_list_size = i->second.first;
        }
    }

    return nont_list_size;
}

void
LexNontermList::write_output(ostream& os) const
{
    // first calculate the max index, the array is this index+1
    unsigned nont_list_size = get_max_idx() + 1;
    write_lex_size_and_log(os, nont_list_size, " nonterminals to file");

    for (unsigned idx = 0; idx < nont_list_size; ++idx) {
        LexNontMap::const_iterator i = get_entry_by_idx(idx);
        if (i == nonterms.end()) {
            write_lex_byte(os, '\0');
        } else {
            write_lex_string(os, i->first.first.c_str());
            write_lex_word(os, i->first.second);

            if ((i->second).second) {
                for (pParam p = (i->second).second; p; p = p->next) {
                    Param par = p->elem;
                    
                    switch(par.type) {
                        case IdType:
                        case SetType:
                            write_lex_byte(os, LexParamLattice);
                            write_lex_index(os, get_param_idx(par));
                            break;
                        case TextType:
                        case AnyText:
                            write_lex_byte(os, LexParamText);
                            break;
                        case IntType:
                        case AnyInt:
                            write_lex_byte(os, LexParamInt);
                            break;
                        default:
                            cout << "Internal error in LexNontermList::<<: unknown parameter type" << endl;
                            exit(1);
                    }
                } //for
            }
        }
    } //for
}

bool LexNontermList::has_changed(Lexicon old_lex)
{ 
#ifdef CMP_DEBUG
    cout << "comparing nonterminals" << endl;
#endif

    for (unsigned idx = 0; idx < nonterms.size(); ++idx) {
#ifdef CMP_DEBUG
        cout << "nonterminal " << idx << ":" << endl;
#endif // CMP_DEBUG
        LexNontMap::const_iterator i = get_entry_by_idx(idx);

        if (lexicon_nont_exists(old_lex, idx) && (i == nonterms.end())) {
            return true;
        }

        if (!lexicon_nont_exists(old_lex, idx) && (i != nonterms.end())) {
            return true;
        }

        if (lexicon_nont_exists(old_lex, idx) && (i != nonterms.end())) {
            string old_name = lexicon_get_nont_name(old_lex, idx);
#ifdef CMP_DEBUG
            cout << "comparing (old,new) (" << old_name << "," << i->first.first
                << ")" << endl;
#endif // CMP_DEBUG

            if (i->first.first != old_name) {
#ifdef CMP_DEBUG
                cout << "nonterminal " << idx << " was " << old_name;
                cout << " and is " << i->first.first << endl;
#endif // CMP_DEBUG
                return true;
            }

            size_t old_arity = lexicon_get_nont_arity(old_lex, idx);
            if (i->first.second != old_arity) {
#ifdef CMP_DEBUG
                cout << "arity of nonterminal " << old_name << " has changed from ";
                cout << old_arity << " to " << i->first.second << endl;
#endif // CMP_DEBUG
                return true;
            }

            if ((i->second).second) {
                pParam p = (i->second).second;
                off_t old_param_idx = 0;

                while (p) {
                    string old_param_name = lexicon_get_nont_param_name(old_lex, idx, old_param_idx);
                    if (has_changed(p->elem, old_param_name)) {
#ifdef CMP_DEBUG
                        cout << "parameter " << old_param_idx + 1;
                        cout << " of nonterminal " << old_name;
                        cout << " with index " << idx;
                        cout << " has changed from " << old_param_name;
                        cout << " to something else" << endl;
#endif // CMP_DEBUG
                        return true;
                    }

                    p = p->next;
                    old_param_idx++;
                }
            }
        }
    }

    return false;
}

bool LexNontermList::has_changed(const Param par, string old_name)
{
    ParamType type = par.type;
    
    switch(type) {
        case IdType:
            return idtable.to_string(par.value.id) != old_name;
        case SetType:
            return convert_to_string(par.value.set) != old_name;
        case TextType:
        case AnyText:
            return old_name != "TEXT";
        case IntType:
        case AnyInt:
            return old_name != "INT";
        default:
            cout << "Internal error in LexNontermList::get_param_idx: Any{Text,Int}" << endl;
            exit(1);
	    return false;	// get rid of warning
    }
}

void
LexNontermList::add_nonterm(string name, pParam params, unsigned rule_nr)
{
    unsigned arity = calculate_arity(params);
    LexNontWithArity el = LexNontWithArity(name, arity);
    LexNontMap::iterator i = nonterms.find(el);

    if (i == nonterms.end()) {
        LexNontInfo info = LexNontInfo(rule_nr, params);
        nonterms[el] = info;
    }
}

bool
LexNontermList::is_defined(string nonterm, pParam params) const
{
    unsigned arity = calculate_arity(params);
    LexNontWithArity el = LexNontWithArity(nonterm, arity);
    LexNontMap::const_iterator position = nonterms.find(el);

    return (position != nonterms.end());
}

unsigned
LexNontermList::calculate_arity(pParam params) const
{
    register pParam p = params;
    register unsigned count = 0;

    while(p) {
        count++;
        p = p->next;
    }

    return count;
}

unsigned
LexNontermList::get_index(Entry e) const
{
    unsigned arity = calculate_arity((e.nonterm)->params);
    string head_str = idtable.to_string((e.nonterm)->head);
    LexNontWithArity el = LexNontWithArity(head_str, arity);
    LexNontMap::const_iterator i = nonterms.find(el);

    if (i == nonterms.end()) {
        cout << "internal error in LexNontermList::get_index: entry not in table" << endl;
        cout << "Entry: " << e << " (" << head_str << "/" << arity << ")" << endl;
        cout << "-- Entry dump:" << endl;
        dump();
        cout << "-- End entry dump" << endl;
        assert(0);
        exit(1);
    }

    return (i->second).first;
}
