/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 *   copyright (C) 2006                                                    *
 *   Umbrello UML Modeller Authors <uml-devel@uml.sf.net>                  *
 ***************************************************************************/

// own header
#include "import_rose.h"

// qt includes
#include <tqstring.h>
#include <tqtextstream.h>
#include <tqptrlist.h>
#include <tqstringlist.h>
#include <tqregexp.h>
#include <tqmessagebox.h>
#include <tdelocale.h>
#include <tdeapplication.h>
#include <kdebug.h>
// app includes
#include "petalnode.h"
#include "petaltree2uml.h"
#include "umlnamespace.h"  // only for the KDE4 compatibility macros

namespace Import_Rose {

typedef TQPtrList<PetalNode> PetalNodeList;

uint nClosures; // Multiple closing parentheses may appear on a single
                // line. The parsing is done line-by-line and using
                // recursive descent. This means that we can only handle
                // _one_ closing parenthesis at a time, i.e. the closing
                // of the currently parsed node. Since we may see more
                // closing parentheses than we can handle, we need a
                // counter indicating how many additional node closings
                // have been seen.

uint linum;  // line number
TQString g_methodName;
void methodName(const TQString& m) {
    g_methodName = m;
}
/**
 * Auxiliary function for diagnostics: Return current location.
 */
TQString loc() {
    return "Import_Rose::" + g_methodName + " line " + TQString::number(linum) + ": ";
}

/**
 * Split a line into lexemes.
 */
TQStringList scan(const TQString& lin) {
    TQStringList result;
    TQString line = lin.stripWhiteSpace();
    if (line.isEmpty())
        return result;  // empty
    TQString lexeme;
    const uint len = line.length();
    bool inString = false;
    for (uint i = 0; i < len; i++) {
        TQChar c = line[i];
        if (c == '"') {
            lexeme += c;
            if (inString) {
                result.append(lexeme);
                lexeme = TQString();
            }
            inString = !inString;
        } else if (inString ||
                   c.isLetterOrNumber() || c == '_' || c == '@') {
            lexeme += c;
        } else {
            if (!lexeme.isEmpty()) {
                result.append(lexeme);
                lexeme = TQString();
            }
            if (! c.isSpace()) {
                result.append(TQString(c));
            }
        }
    }
    if (!lexeme.isEmpty())
        result.append(lexeme);
    return result;
}

/**
 * Emulate perl shift().
 */
TQString shift(TQStringList& l) {
    TQString first = l.first();
    l.pop_front();
    return first;
}

/**
 * Check for closing of one or more scopes.
 */
bool checkClosing(TQStringList& tokens) {
    if (tokens.count() == 0)
        return false;
    if (tokens.last() == ")") {
        // For a single closing parenthesis, we just return true.
        // But if there are more closing parentheses, we need to increment
        // nClosures for each scope.
        tokens.pop_back();
        while (tokens.count() && tokens.last() == ")") {
            nClosures++;
            tokens.pop_back();
        }
        return true;
    }
    return false;
}

/**
 * Immediate values are numbers or quoted strings.
 * @return  True if the given text is a natural or negative number
 *          or a quoted string.
 */
bool isImmediateValue(TQString s) {
    return s.contains(TQRegExp("^[\\d\\-\"]"));
}

/**
 * Extract immediate values out of `l'.
 * Examples of immediate value lists:
 *   number list:     ( 123 , 456 )
 *   string list:     ( "SomeText" 888 )
 * Any enclosing parentheses are removed.
 * All extracted items are also removed from `l'.
 * For the example given above the following is returned:
 *   "123 456"
 * or
 *   "\"SomeText\" 888"
 */
TQString extractImmediateValues(TQStringList& l) {
    if (l.count() == 0)
        return TQString();
    if (l.first() == "(")
        l.pop_front();
    TQString result;
    bool start = true;
    while (l.count() && isImmediateValue(l.first())) {
        if (start)
            start = false;
        else
            result += ' ';
        result += shift(l);
        if (l.first() == ",")
            l.pop_front();
    }
    if (l.first() == ")")
        l.pop_front();
    while (l.count() && l.first() == ")") {
        nClosures++;
        l.pop_front();
    }
    return result;
}

TQString collectVerbatimText(TQTextStream& stream) {
    TQString result;
    TQString line;
    methodName("collectVerbatimText");
    while (!(line = stream.readLine()).isNull()) {
        linum++;
        line = line.stripWhiteSpace();
        if (line.isEmpty() || line.startsWith(")"))
            break;
        if (line[0] != '|') {
            kError() << loc() << "expecting '|' at start of verbatim text" << endl;
            return TQString();
        } else {
            result += line.mid(1) + '\n';
        }
    }
    if (line.isNull()) {
        kError() << loc() << "premature EOF" << endl;
        return TQString();
    }
    if (! line.isEmpty()) {
        for (uint i = 0; i < line.length(); i++) {
            const TQChar& clParenth = line[i];
            if (clParenth != ')') {
                kError() << loc() << "expected ')', found: " << clParenth << endl;
                return TQString();
            }
            nClosures++;
        }
    }
    return result;
}

/**
 * Extract the stripped down value from a (value ...) element.
 * Example: for the input
 *            (value Text "This is some text")
 *          the following is extracted:
 *            "This is some text"
 * Extracted elements and syntactic sugar of the value element are
 * removed from the input list.
 * The stream is passed into this method because it may be necessary
 * to read new lines - in the case of verbatim text.
 * The format of verbatim text in petal files is as follows:
 *
 *         (value Text
 * |This is the first line of verbatim text.
 * |This is another line of verbatim text.
 *         )
 * (The '|' character is supposed to be in the first column of the line)
 * In this case the two lines are extracted without the leading '|'.
 * The line ending '\n' of each line is preserved.
 */
TQString extractValue(TQStringList& l, TQTextStream& stream) {
    methodName("extractValue");
    if (l.count() == 0)
        return TQString();
    if (l.first() == "(")
        l.pop_front();
    if (l.first() != "value")
        return TQString();
    l.pop_front();  // remove "value"
    l.pop_front();  // remove the value type: could be e.g. "Text" or "cardinality"
    TQString result;
    if (l.count() == 0) {  // expect verbatim text to follow on subsequent lines
        TQString text = collectVerbatimText(stream);
        nClosures--;  // expect own closure
        return text;
    } else {
        result = shift(l);
        if (l.first() != ")") {
            kError() << loc() << "expecting closing parenthesis" << endl;
            return result;
        }
        l.pop_front();
    }
    while (l.count() && l.first() == ")") {
        nClosures++;
        l.pop_front();
    }
    return result;
}

/**
 * Read attributes of a node.
 * @param initialArgs  Tokens on the line of the opening "(" of the node
 *                   but with leading whitespace and the opening "(" removed.
 * @param stream     The TQTextStream from which to read following lines.
 * @return           Pointer to the created PetalNode or NULL on error.
 */
PetalNode *readAttributes(TQStringList initialArgs, TQTextStream& stream) {
    methodName("readAttributes");
    if (initialArgs.count() == 0) {
        kError() << loc() << "initialArgs is empty" << endl;
        return NULL;
    }
    PetalNode::NodeType nt;
    TQString type = shift(initialArgs);
    if (type == "object")
        nt = PetalNode::nt_object;
    else if (type == "list")
        nt = PetalNode::nt_list;
    else {
        kError() << loc() << "unknown node type " << type << endl;
        return NULL;
    }
    PetalNode *node = new PetalNode(nt);
    bool seenClosing = checkClosing(initialArgs);
    node->setInitialArgs(initialArgs);
    if (seenClosing)
        return node;
    PetalNode::NameValueList attrs;
    TQString line;
    while (!(line = stream.readLine()).isNull()) {
        linum++;
        line = line.stripWhiteSpace();
        if (line.isEmpty())
            continue;
        TQStringList tokens = scan(line);
        TQString stringOrNodeOpener = shift(tokens);
        TQString name;
        if (nt == PetalNode::nt_object && !stringOrNodeOpener.contains(TQRegExp("^[A-Za-z]"))) {
            kError() << loc() << "unexpected line " << line << endl;
            return NULL;
        }
        PetalNode::StringOrNode value;
        if (nt == PetalNode::nt_object) {
            name = stringOrNodeOpener;
            if (tokens.count() == 0) {  // expect verbatim text to follow on subsequent lines
                value.string = collectVerbatimText(stream);
                PetalNode::NameValue attr(name, value);
                attrs.append(attr);
                if (nClosures) {
                    // Decrement nClosures exactly once, namely for the own scope.
                    // Each recursion of readAttributes() is only responsible for
                    // its own scope. I.e. each further scope closing is handled by
                    // an outer recursion in case of multiple closing parentheses.
                    nClosures--;
                    break;
                }
                continue;
            }
            stringOrNodeOpener = shift(tokens);
        } else if (stringOrNodeOpener != "(") {
            value.string = stringOrNodeOpener;
            PetalNode::NameValue attr;
            attr.second = value;
            attrs.append(attr);
            if (tokens.count() && tokens.first() != ")") {
                kDebug() << loc()
                    << "NYI - immediate list entry with more than one item" << endl;
            }
            if (checkClosing(tokens))
                break;
            continue;
        }
        if (stringOrNodeOpener == "(") {
            TQString nxt = tokens.first();
            if (isImmediateValue(nxt)) {
                value.string = extractImmediateValues(tokens);
            } else if (nxt == "value" || nxt.startsWith("\"")) {
                value.string = extractValue(tokens, stream);
            } else {
                kapp->processEvents();
                value.node = readAttributes(tokens, stream);
                if (value.node == NULL)
                    return NULL;
            }
            PetalNode::NameValue attr(name, value);
            attrs.append(attr);
            if (nClosures) {
                // Decrement nClosures exactly once, namely for the own scope.
                // Each recursion of readAttributes() is only responsible for
                // its own scope. I.e. each further scope closing is handled by
                // an outer recursion in case of multiple closing parentheses.
                nClosures--;
                break;
            }
        } else {
            value.string = stringOrNodeOpener;
            bool seenClosing = checkClosing(tokens);
            PetalNode::NameValue attr(name, value);
            attrs.append(attr);
            if (seenClosing) {
                break;
            }
        }
    }
    node->setAttributes(attrs);
    return node;
}

bool loadFromMDL(TQIODevice& file) {
    TQTextStream stream(&file);
    stream.setEncoding(TQTextStream::Latin1);
    TQString line;
    PetalNode *root = NULL;
    linum = 0;
    while (!(line = stream.readLine()).isNull()) {
        linum++;
        if (line.contains( TQRegExp("^\\s*\\(object Petal") )) {
            while (!(line = stream.readLine()).isNull() && !line.contains(')')) {
                linum++; // CHECK: do we need petal version info?
            }
            if (line.isNull())
                break;
        } else {
            TQRegExp objectRx("^\\s*\\(object ");
            if (line.contains(objectRx)) {
                nClosures = 0;
                TQStringList initialArgs = scan(line);
                initialArgs.pop_front();  // remove opening parenthesis
                root = readAttributes(initialArgs, stream);
            }
        }
    }
    file.close();
    if (root == NULL)
        return false;
    return petalTree2Uml(root);
}

}

