// (c) 2000-2007 Electronic Arts Inc.

// http://garp.eac.ea.com/cgi-bin/perfbrowse.pl?@print+//depot/tools/dallas/AttributeEditor.cpp+head#123
// http://msdn.microsoft.com/library/devprods/vs6/visualc/vccore/c4784.htm

/**
 * @mainpage
 *
 * Anna v1.00
 *
 * This program is used to analyze the output of build files and send
 * the results to the napalm build server.  The goal is to automate the
 * the results of nightly builds.
 *
 * @returns Number of errors encountered.
 *
 * @author Gerry Shaw
 */

#pragma warning (disable : 4786)
#include <string>
#include <list>
#include <fstream>
#include <strstream>
#include <iostream>
#include <assert.h>

using namespace std;

#ifndef ASSERT
#define ASSERT assert
#endif

typedef list<string> StringCollection;

class BuildLog
{
// interface
public:
    // construction/destruction
    BuildLog(const string& base, const string& depot, const string& delimeter = "###");

    // attributes
    int m_Warnings;             ///< Number of warnings from all sections that have been processed so far.
    int m_Errors;               ///< Number of errors from all sections that have been processed so far.
    StringCollection m_Lines;   ///< The entire log file as a collection of strings (each string is a line)

    // operations
    bool load(const char* pathname);
    void parse(ostream& out);

// implementation
private:
    void advanceLine(int numberOfLines);

    void parseName(ostream& out, const string& arguments);
    void parseStart(ostream& out, const string& arguments);
    void parseFinish(ostream& out, const string& arguments);
    void parseSection(ostream& out, const string& arguments);

    void parseP4Section(ostream& out);
    void parseVCSection(ostream& out);
    void parseDOXSection(ostream& out);

    string                      m_Delimeter;    ///< The string used to mark the start of a command, default "###".
    StringCollection::iterator  m_Line;         ///< Current line being processed
    int                         m_LineNumber;   ///< Current line number being processed at this time.

    string m_BasePathname;  ///< ie, "C:\\work\\"
    string m_DepotPathname; ///< ie, "//depot/tools/";

};

BuildLog::BuildLog(const string& base, const string& depot, const string& delimeter) :
    m_LineNumber(-1),
    m_Warnings(0),
    m_Errors(0),
    m_Delimeter(delimeter),
    m_BasePathname(base),
    m_DepotPathname(depot)
{
}

bool BuildLog::load(const char* pathname)
{
    bool ok = false;
    if (pathname != NULL)
    {
        ifstream in(pathname);
        if (in)
        {
            StringCollection log;
            string str;
            while (getline(in, str))
            {
                m_Lines.push_back(str);
            }
            ok = true;
        }
    }
    return ok;
}

void BuildLog::advanceLine(int numberOfLines)
{
    while (m_Line != m_Lines.end() && numberOfLines > 0)
    {
        ++m_Line;
        ++m_LineNumber;
        --numberOfLines;
    }
}

void BuildLog::parseName(ostream& out, const string& arguments)
{
    out << "<name>" << arguments << "</name>" << endl;
    advanceLine(1);
}

void BuildLog::parseStart(ostream& out, const string& arguments)
{
    out << "<start>" << arguments << "</start>" << endl;
    advanceLine(1);
}

void BuildLog::parseFinish(ostream& out, const string& arguments)
{
    out << "<finish>" << arguments << "</finish>" << endl;
    advanceLine(1);
}

void BuildLog::parseP4Section(ostream& out)
{
    out << "<section name=\"P4\" description=\"Perforce\">" << endl;
    out << "<![CDATA[";
    advanceLine(1);

    while (m_Line != m_Lines.end())
    {
        string line = (*m_Line);
        if (line.compare(0, m_Delimeter.size(), m_Delimeter) == 0)
        {
            // found a new section, time to end
            break;
        }

        // look for errors on this line
        advanceLine(1);
    }
    out << "]]>";
    out << "</section>" << endl;
}

void BuildLog::parseVCSection(ostream& out)
{
    out << "<section name=\"VC\" description=\"Visual C++\">" << endl;
    out << "<![CDATA[";
    advanceLine(1);

    while (m_Line != m_Lines.end())
    {
        string line = (*m_Line);
        if (line.compare(0, m_Delimeter.size(), m_Delimeter) == 0)
        {
            // found a new section, time to end
            break;
        }

        bool formatLine = false;

        // look for errors on this line
        if (line.find(": error LNK") != string::npos
            || line.find(": fatal error LNK") != string::npos)
        {
            // link errors cannot be hyperlinked because we don't have the full pathname to the file
            // even trying to guess wouldn't work as the file might be in a different folder.
            cout << "  " << line << endl;
            ++m_Errors;
        }
        else if (line.find(": error ") != string::npos
            || line.find(": fatal error ") != string::npos)
        {
            ++m_Errors;
            formatLine  = true;
        }
        else if (line.find(": warning ") != string::npos)
        {
            ++m_Warnings;
            formatLine  = true;
        }
        else if (line == "Error: no project loaded.")
        {
            cout << "  " << line << endl;
            ++m_Errors;
        }

        if (formatLine)
        {
            // C:\work\layman\dafi.c(139) : error C2144: syntax error : missing ';' before type 'void'

            // replace the file with a hyperlink to the file
            // http://garp.eac.ea.com/cgi-bin/perfbrowse.pl?@print+//depot/tools/layman/fert/src/dafi.c+head#123
            // http://garp.eac.ea.com/cgi-bin/perfbrowse.pl?@print+//depot/tools/layman/dafi.c+head#139


            // TODO: replace the error code with a hyperlink to a description of the error code
            // http://msdn.microsoft.com/library/devprods/vs6/visualc/vccore/c2144.htm

            // get the part of the href used to hyperlink this source file
            string href = line;
            href.replace(0, m_BasePathname.size(), m_DepotPathname);

            // replace any backslashes with forward slashes
            while (href.find('\\') != string::npos)
                href.replace(href.find('\\'),1, "/");

            // erase the rest of the line
            if (line.find(": error LNK") != string::npos && line.find(": warning LNK") != string::npos)
            {
                int i = line.find('(');
                if (i != string::npos)
                    href.erase(i);

                i = line.find(')');
                if (i != string::npos)
                {
                    string pathname = line.substr(0, i+1);

                    int lineNumberStrStart = line.find('(') + 1;
                    int lineNumberStrEnd   = line.find(')') - 1;
                    string lineNumberStr = line.substr(lineNumberStrStart, lineNumberStrEnd-lineNumberStrStart+1);
                    int lineNumber = atoi(lineNumberStr.c_str());

                    line.erase(0, line.find(')') + 1);

                    // http://garp.eac.ea.com/cgi-bin/perfbrowse.pl?@filelog+//depot/tools/layman/res/scrolldn.ico
                    out << "<a href=\"http://garp.eac.ea.com/cgi-bin/perfbrowse.pl?@filelog+";
                    out << href;
                    out << "\">?</a> ";
                    out << "<a href=\"http://garp.eac.ea.com/cgi-bin/perfbrowse.pl?@print+";
                    out << href;
                    out << "+head#";
                    out << lineNumber;
                    out << "\">";
                    out << pathname;
                    out << "</a>";
                }
            }
            out << line;
            out << endl;
        }

        advanceLine(1);
    }
    out << "]]>";
    out << "</section>" << endl;
}

void BuildLog::parseDOXSection(ostream& out)
{
    out << "<section name=\"DOX\" description=\"Doxygen\">" << endl;
    out << "<![CDATA[";
    advanceLine(1);

    while (m_Line != m_Lines.end())
    {
        string line = (*m_Line);
        if (line.compare(0, m_Delimeter.size(), m_Delimeter) == 0)
        {
            // found a new section, time to end
            break;
        }

        bool formatLine = false;

        // look for errors on this line
        if (line.find(" : Warning: ") != string::npos)
        {
            ++m_Warnings;
            formatLine = true;
        }

        if (formatLine)
        {
            // C:\work\layman\dafi.c(139) : error C2144: syntax error : missing ';' before type 'void'

            // replace the file with a hyperlink to the file
            // http://garp.eac.ea.com/cgi-bin/perfbrowse.pl?@print+//depot/tools/layman/fert/src/dafi.c+head#123

            // get the part of the href used to hyperlink this source file
            string href = line;
            href.replace(0, m_BasePathname.size(), m_DepotPathname);

            // replace any backslashes with forward slashes
            while (href.find('\\') != string::npos)
                href.replace(href.find('\\'),1, "/");

            // erase the rest of the line
            href.erase(href.find('('));

            string pathname = line.substr(0, line.find(')')+1);

            int lineNumberStrStart = line.find('(') + 1;
            int lineNumberStrEnd   = line.find(')') - 1;
            string lineNumberStr = line.substr(lineNumberStrStart, lineNumberStrEnd-lineNumberStrStart+1);
            int lineNumber = atoi(lineNumberStr.c_str());

            line.erase(0, line.find(')') + 1);

            // http://garp.eac.ea.com/cgi-bin/perfbrowse.pl?@filelog+//depot/tools/layman/res/scrolldn.ico
            out << "<a href=\"http://garp.eac.ea.com/cgi-bin/perfbrowse.pl?@filelog+";
            out << href;
            out << "\">?</a> ";
            out << "<a href=\"http://garp.eac.ea.com/cgi-bin/perfbrowse.pl?@print+";
            out << href;
            out << "+head#";
            out << lineNumber;
            out << "\">";
            out << pathname;
            out << "</a>";
            out << line;
            out << endl;
        }

        advanceLine(1);
    }
    out << "]]>";
    out << "</section>" << endl;
}

void BuildLog::parseSection(ostream& out, const string& arguments)
{
    if (arguments == "P4")
    {
        parseP4Section(out);
    }
    else if (arguments == "VC")
    {
        parseVCSection(out);
    }
    else if (arguments == "DOX")
    {
        parseDOXSection(out);
    }
}

/*
    currently supported tags:
    NAME { string }
    START { string }
    FINISH { string }
    SECTION "P4" | "VC" | "DOX"
*/

void BuildLog::parse(ostream& out)
{
    m_LineNumber = 1;
    m_Line       = m_Lines.begin();

    out << "<buildlog>" << endl;
    while (m_Line != m_Lines.end())
    {
        string line = (*m_Line);
        if (line.compare(0, m_Delimeter.size(), m_Delimeter) == 0)
        {
            // get the operation line with out the operation prefix (ie, ###)
            string operationLine = line.substr(m_Delimeter.size());

            // get the operation and arguments as seperate strings
            int seperator = operationLine.find(' ');
            string operation = operationLine.substr(0, seperator);
            string arguments = operationLine.substr(seperator + 1);

            if (operation == "NAME")
            {
                parseName(out, arguments);
            }
            else if (operation == "START")
            {
                parseStart(out, arguments);
            }
            else if (operation == "FINISH")
            {
                parseFinish(out, arguments);
            }
            else if (operation == "SECTION")
            {
                parseSection(out, arguments);
            }
        }
        else
        {
            // ignore this line
            advanceLine(1);
        }
    }

    // write summary
    out << "<summary warnings=\"" << m_Warnings << "\" errors=\"" << m_Errors << "\">";
    if (m_Errors > 0)
    {
        out << "Failed";
    }
    else
    {
        out << "Success";
    }
    out << "</summary>" << endl;
    out << "</buildlog>" << endl;
}


int main(int argc, char* argv[])
{
    int returnCode = 0;

    cerr << "anna v1.0 : Build log analyzer" << endl;

    bool printHelp = true;
    if (argc == 4)
    {
        BuildLog log(argv[2], argv[3]);
        if (log.load(argv[1]))
        {
            log.parse(cout);
            returnCode = log.m_Errors;
            printHelp = false;
        }
        else
        {
            cerr << "ERROR: bad log file '" << argv[1] << "'" << endl;
        }
    }
    else
    {
            cerr << "ERROR: must give 3 command line arguments" << endl;
    }

    if (printHelp)
    {
        cerr << "usage: anna <buildlog.txt> <basepath> <depotpath> >output.xml" << endl;
        cerr << "ie, anna dallas.log c:\\work\\ //depot/tools/ >dallas.xml" << endl;
        returnCode = 1;
    }
    return returnCode;
}
