// ---------------------------------------------------------------------------
// 
//   Lily Engine alpha
// 
//   Copyright (c) 2010 Clifford Jolly
//     http://cliffjolly.com
//     cliff@cliffjolly.com
// 
// ---------------------------------------------------------------------------
// 
//   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
//   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
// -------------------------- END HEADER -------------------------------------

#include <vector>
#include <map>
#include <string>
#include <iostream>
#include <cstring>
#include <sstream>
#include <cstring>
#include <cstdlib>
using namespace std;

#include "../string/string.h"
#include "../filesystem/filesystem.h"
#include "parser.h"

namespace LilyParser {

    // ------------------------------------------------------------------------
    //   Utility junk
    // ------------------------------------------------------------------------

    inline bool isSymbolCharacter(char c) {

        if((c >= 'a' && c <= 'z') ||
           (c >= 'A' && c <= 'Z') ||
           (c >= '0' && c <= '9') ||
           (c == '_')) {

            return true;
        }

        return false;
    }

    inline bool isNumberCharacter(char c) {

        if((c >= '0' && c <= '9') ||
           c == '.' ||
           c == '-') {

            return true;
        }

        return false;
    }

    enum ParserTokenType {
        PT_UNKNOWN,

        PT_BLOCKSTART,
        PT_BLOCKEND,

        PT_EQUALS,
        PT_LINEEND,

        PT_SYMBOL,
        PT_STRING,
        PT_NUMBER,
    };

    static void indentOutput(ostream &out, int indentLevel) {
        for(int i = 0; i < indentLevel; i++) {
            out << "    ";
        }
    }

    // ------------------------------------------------------------------------
    //   ParserNode implementation
    // ------------------------------------------------------------------------

    std::string ParserNode::getName(void) {
        return name;
    }

    void ParserNode::setName(const std::string &name) {
        this->name = name;
    }

    ParserNode *ParserNode::getChild(int index) {
        if(index >= (int)children.size() || index < 0) {
            return NULL;
        }
        return children[index];
    }

    unsigned int ParserNode::getNumChildren(void) {
        return children.size();
    }

    void ParserNode::addChildToEnd(ParserNode *node) {
        children.push_back(node);
    }

    void ParserNode::addChildToStart(ParserNode *node) {
        children.insert(children.begin(), node);
    }

    void ParserNode::output(ostream &out, int indentLevel) const {
            
        // Print all values.
        bool atLeastOneValue = false;
        for(std::map<std::string, std::string>::const_iterator i = values.begin(); i != values.end(); i++) {
            indentOutput(out, indentLevel);
            out << (*i).first << " = \"" << stringEscape((*i).second) << "\";" << endl;
            atLeastOneValue = true;
        }

        // Add some space before the children.
        if(atLeastOneValue && children.size()) {
            out << endl;
        }

        // Print all children.
        for(unsigned int cn = 0; cn < children.size(); cn++) {

            indentOutput(out, indentLevel);

            if(children[cn]->name.size()) {
                out << children[cn]->name << " ";
            } else {
                out << "null " << endl;
            }

            out << "{" << endl;

            children[cn]->output(out, indentLevel + 1);
            
            indentOutput(out, indentLevel);
            out << "}" << endl;

            // Add a line between children.
            if(cn + 1 != children.size()) {
                out << endl;
            }
        }
    }

    ParserNode::~ParserNode(void) {
        for(unsigned int cn = 0; cn < children.size(); cn++) {
            delete children[cn];
        }
    }

    std::string ParserNode::toString(void) const {
        ostringstream str;
        output(str, 0);
        return str.str();
    }


    bool ParserNode::getBooleanValue(const std::string &name, bool *value) {

        if(values.count(name)) {

            string val = values[name];

            if(val == "false" || val == "0" || val == "") {
                *value = false;
            } else {
                *value = true;
            }

            return true;
        }

        return false;
    }

    bool ParserNode::getStringValue(const std::string &name, std::string *value) {
    
        if(values.count(name)) {

            string val = values[name];
            *value = val;
            return true;
        }

        return false;
    }

    bool ParserNode::getFloatValue(const std::string &name, float *value) {

        if(values.count(name)) {

            string val = values[name];
            *value = atof(val.c_str());
            return true;
        }

        return false;
    }

    bool ParserNode::getIntValue(const std::string &name, int *value) {

        if(values.count(name)) {

            string val = values[name];
            *value = atoi(val.c_str());
            return true;
        }

        return false;
    }
       
    bool ParserNode::getBinaryValue(const std::string &name, char **buf, int *length) {

        if(values.count(name)) {

            *buf = strDecodeHex(values[name], length);
            return true;
        }

        return false;
    }

    void ParserNode::setBinaryValue(const std::string &name, char *buf, int length) {
        strEncodeHex(buf, length, values[name]);
    }

    void ParserNode::setBooleanValue(const std::string &name, bool value) {
        ostringstream str;
        str << (value ? "true" : "false");
        values[name] = str.str();
    }

    void ParserNode::setStringValue(const std::string &name, const std::string &value) {
        values[name] = value;
    }

    void ParserNode::setFloatValue(const std::string &name, float value) {
        ostringstream str;
        str << value;
        values[name] = str.str();
    }

    void ParserNode::setIntValue(const std::string &name, int value) {
        ostringstream str;
        str << value;
        values[name] = str.str();
    }

    void ParserNode::clearValue(const std::string &name) {
    
        values.erase(name);

    }

    int ParserNode::getChildIndexByName(const std::string &name, int after) {
        
        if(after < -1 || after >= int(children.size()) - 1) {
            return int(children.size());
        }

        for(int i = after + 1; i < int(children.size()); i++) {
            if(children[i] && children[i]->name == name) {
                return i;
            }
        }

        return int(children.size());
    }

    ParserNode *ParserNode::getChildByName(const std::string &name) {

        return getChild(getChildIndexByName(name));

    }

    ostream &operator<<(ostream &out, const ParserNode &node) {

        node.output(out, 0);

        return out;
    }

    /// Default constructor.
    ParserNode::ParserNode(void) {
        name = "null";
    }

    /// Constructor with a name.
    ParserNode::ParserNode(const std::string &name) {
        this->name = name;
    }
    

    // ------------------------------------------------------------------------
    //   The parser itself
    // ------------------------------------------------------------------------

    class ParserToken {

        public:

            ParserToken(const std::string &str, int type, int lineNumber) {
                this->str = str;
                this->type = type;
                this->lineNumber = lineNumber;
            }

            ParserToken(char c, int lineNumber) {

                // This seems oddly the simplest way to do this...
                str = " ";
                str[0] = c;

                this->type = PT_UNKNOWN;

                this->lineNumber = lineNumber;
            }

            std::string str;
            int type;
            int lineNumber;
        
        private:
    };

    static ParserNode *parseTokens(const std::vector<ParserToken*> &tokens, const std::string &nodeName, int *pos, std::string *errorStr) {

        ParserNode *node = new ParserNode();
    
        node->setName(nodeName);

        while(*pos < (int)tokens.size()) {

            int tokensLeft = (int)tokens.size() - *pos;

            if(tokensLeft >= 4 &&
               tokens[*pos    ]->type == PT_SYMBOL &&
               tokens[*pos + 1]->type == PT_EQUALS &&
               (   tokens[*pos + 2]->type == PT_SYMBOL ||
                   tokens[*pos + 2]->type == PT_NUMBER ||
                   tokens[*pos + 2]->type == PT_STRING) &&
               tokens[*pos + 3]->type == PT_LINEEND) {

                // Something in the form of: symbol = "someValue";

                node->setStringValue(tokens[*pos]->str, tokens[*pos + 2]->str);

                (*pos) += 4;

            } else if(tokensLeft >=2 &&
                      tokens[*pos]->type == PT_SYMBOL &&
                      tokens[*pos + 1]->type == PT_LINEEND) {

                // Simple boolean flag.

                node->setBooleanValue(tokens[*pos]->str, true);

                (*pos) += 2;

            } else if(tokensLeft >= 2 &&
                      tokens[*pos]->type == PT_SYMBOL &&
                      tokens[*pos + 1]->type == PT_BLOCKSTART) {

                // Recurse into a child thinger.

                string childNodeName = tokens[*pos]->str;

                (*pos) += 2;

                ParserNode *childNode = parseTokens(tokens, childNodeName, pos, errorStr);

                if(childNode) {

                    node->addChildToEnd(childNode);

                } else {

                    // An error happened when parsing the child. Clean
                    // up and return.
                    delete node;
                    return NULL;
                }

            } else if(tokensLeft >= 1 && tokens[*pos]->type == PT_BLOCKEND) {

                // Done with this block!
                (*pos)++;

                break;

            } else {

                // I don't know what this is. Toss out an error message
                // and bail out.
                if(errorStr) {
                    ostringstream errStrStr;
                    errStrStr << "Syntax error at: \"" << tokens[*pos]->str << "\" on line " << tokens[*pos]->lineNumber;
                    *errorStr = errStrStr.str();
                }

                delete node;
                return NULL;

            }

        }

        return node;
    }

    ParserNode *parseBuffer(const char *buf, int length, std::string *errorStr) {

        // First, tokenize.

        vector<ParserToken*> tokens;

        int pos = 0;
        int lineNumber = 0;

        while(pos < length) {
        
            char c = buf[pos];

            if(isWhiteSpace(c)) {

                if(c == '\n') {
                    lineNumber++;
                }

                // Just whitespace. Ignore it.
                pos++;

            } else if(
                (c == '#') ||                                       // Bash comment
                (c == '/' && pos + 1 < length && buf[pos+1] == '/') // C++ comment
                ) {

                // Comment. Read until end of line or file.
            
                while(pos < length && c != '\n') {
                    pos++;
                    c = buf[pos];
                }

                // Note: Don't skip over the \n. Let the line number
                // counter get it next pass.

            } else if(isNumberCharacter(c)) {

                // Number. Go to end of number.

                int numberStartPos = pos;

                while(pos < length && isNumberCharacter(c)) {
                    pos++;
                    c = buf[pos];
                }

                int numberLength = pos - numberStartPos;
                char *numberCStr = new char[numberLength + 1];
                strncpy(numberCStr, buf + numberStartPos, numberLength);
                numberCStr[numberLength] = 0;

                tokens.push_back(new ParserToken(numberCStr, PT_NUMBER, lineNumber));

                delete[] numberCStr;

            } else if(isSymbolCharacter(c)) {

                // Start of a symbol. Go to end of symbol.

                int symbolStartPos = pos;

                while(pos < length && isSymbolCharacter(c)) {
                    pos++;
                    c = buf[pos];
                }

                int symbolLength = pos - symbolStartPos;
                char *symbolCStr = new char[symbolLength + 1];
                strncpy(symbolCStr, buf + symbolStartPos, symbolLength);
                symbolCStr[symbolLength] = 0;

                tokens.push_back(new ParserToken(symbolCStr, PT_SYMBOL, lineNumber));

                delete[] symbolCStr;

            } else if(c == '"') {

                // Start of a quote block thinger.

                int consecutiveBackSlashes = 0;
                int quoteStartPos = pos;

                while(pos < length) {
                    pos++;
                    c = buf[pos];

                    if(c == '"') {

                        // As long as we have an even number of slashes,
                        // the quote isn't escaped. I think.
                        if(consecutiveBackSlashes % 2 == 0) {
                            break;
                        }
                    }

                    if(c == '\\') {
                        consecutiveBackSlashes++;
                    } else {
                        consecutiveBackSlashes = 0;
                    }
                }

                int quoteLength = pos - quoteStartPos;
                char *quoteCStr = new char[quoteLength + 1];
                strncpy(quoteCStr, buf + quoteStartPos, quoteLength);
                quoteCStr[quoteLength] = 0;

                tokens.push_back(new ParserToken(stringUnescape(quoteCStr + 1), PT_STRING, lineNumber));

                delete[] quoteCStr;

                // Move OFF the last quote.
                pos++;

            } else {
            
                // All the single-character special tokens.

                ParserToken *pt = new ParserToken(c, lineNumber);

                switch(c) {

                case '{':
                    pt->type = PT_BLOCKSTART;
                    break;

                case '}':
                    pt->type = PT_BLOCKEND;
                    break;

                case '=':
                    pt->type = PT_EQUALS;
                    break;

                case ';':
                    pt->type = PT_LINEEND;
                    break;

                default:

                    // Spew out an error and bail out.
                    if(errorStr) {
                        ostringstream errStrStr;
                        errStrStr << "Unknown character token: \'" << c << "\' (0x" << std::hex << int(c) << ") on line " << lineNumber;
                        *errorStr = errStrStr.str();
                    }

                    delete pt;

                    // Clean up.
                    for(unsigned int i = 0; i < tokens.size(); i++) {
                        delete tokens[i];
                    }

                    return NULL;
                }

                tokens.push_back(pt);
                pos++;
            }

        }

        // Now do some simple not-really-grammars.

        pos = 0;

        ParserNode *node = parseTokens(tokens, "root", &pos, errorStr);

        // Clean up.
        for(unsigned int i = 0; i < tokens.size(); i++) {
            delete tokens[i];
        }

        return node;

    }

    ParserNode *parseString(const std::string &str, std::string *errorStr) {
    
        return parseBuffer(str.c_str(), str.size(), errorStr);

    }

    ParserNode *loadAndParse(const std::string &fileName, std::string *errorStr) {

        int length = 0;
        char *inBuf = FileSystem::loadFile(fileName, &length);
        ParserNode *node = NULL;

        if(inBuf) {
            node = parseBuffer(inBuf, length, errorStr);
            delete[] inBuf;
        }

        return node;
    }

}

