#include "cmParseGTMCoverage.h"

#include "cmCTest.h"
#include "cmCTestCoverageHandler.h"
#include "cmSystemTools.h"

#include "cmsys/Directory.hxx"
#include "cmsys/FStream.hxx"
#include <map>
#include <stdio.h>
#include <stdlib.h>
#include <vector>

cmParseGTMCoverage::cmParseGTMCoverage(cmCTestCoverageHandlerContainer& cont,
                                       cmCTest*                         ctest)
: cmParseMumpsCoverage(cont, ctest)
{}

bool
cmParseGTMCoverage::LoadCoverageData(const char* d)
{
    // load all the .mcov files in the specified directory
    cmsys::Directory dir;
    if(!dir.Load(d))
    {
        return false;
    }
    size_t       numf;
    unsigned int i;
    numf = dir.GetNumberOfFiles();
    for(i = 0; i < numf; i++)
    {
        std::string file = dir.GetFile(i);
        if(file != "." && file != ".." && !cmSystemTools::FileIsDirectory(file))
        {
            std::string path = d;
            path += "/";
            path += file;
            if(cmSystemTools::GetFilenameLastExtension(path) == ".mcov")
            {
                if(!this->ReadMCovFile(path.c_str()))
                {
                    return false;
                }
            }
        }
    }
    return true;
}

bool
cmParseGTMCoverage::ReadMCovFile(const char* file)
{
    cmsys::ifstream in(file);
    if(!in)
    {
        return false;
    }
    std::string line;
    std::string lastfunction;
    std::string lastroutine;
    std::string lastpath;
    int         lastoffset = 0;
    while(cmSystemTools::GetLineFromStream(in, line))
    {
        // only look at lines that have coverage data
        if(line.find("^ZZCOVERAGE") == std::string::npos)
        {
            continue;
        }
        std::string filepath;
        std::string function;
        std::string routine;
        int         linenumber = 0;
        int         count      = 0;
        this->ParseMCOVLine(line, routine, function, linenumber, count);
        // skip this one
        if(routine == "RSEL")
        {
            continue;
        }
        // no need to search the file if we just did it
        if(function == lastfunction && lastroutine == routine)
        {
            if(!lastpath.empty())
            {
                this->Coverage
                    .TotalCoverage[lastpath][lastoffset + linenumber] += count;
            } else
            {
                cmCTestLog(this->CTest, ERROR_MESSAGE,
                           "Can not find mumps file : "
                               << lastroutine
                               << "  referenced in this line of mcov data:\n"
                                  "["
                               << line << "]\n");
            }
            continue;
        }
        // Find the full path to the file
        bool found = this->FindMumpsFile(routine, filepath);
        if(found)
        {
            int lineoffset = 0;
            if(this->FindFunctionInMumpsFile(filepath, function, lineoffset))
            {
                cmCTestCoverageHandlerContainer::SingleFileCoverageVector&
                    coverageVector = this->Coverage.TotalCoverage[filepath];
                // This section accounts for lines that were previously marked
                // as non-executable code (-1), if the parser comes back with
                // a non-zero count, increase the count by 1 to push the line
                // into the executable code set in addition to the count found.
                if(coverageVector[lineoffset + linenumber] == -1 && count > 0)
                {
                    coverageVector[lineoffset + linenumber] += count + 1;
                } else
                {
                    coverageVector[lineoffset + linenumber] += count;
                }
                lastoffset = lineoffset;
            }
        } else
        {
            cmCTestLog(this->CTest, ERROR_MESSAGE,
                       "Can not find mumps file : "
                           << routine
                           << "  referenced in this line of mcov data:\n"
                              "["
                           << line << "]\n");
        }
        lastfunction = function;
        lastroutine  = routine;
        lastpath     = filepath;
    }
    return true;
}

bool
cmParseGTMCoverage::FindFunctionInMumpsFile(std::string const& filepath,
                                            std::string const& function,
                                            int&               lineoffset)
{
    cmsys::ifstream in(filepath.c_str());
    if(!in)
    {
        return false;
    }
    std::string line;
    int         linenum = 0;
    while(cmSystemTools::GetLineFromStream(in, line))
    {
        std::string::size_type pos = line.find(function);
        if(pos == 0)
        {
            char nextchar = line[function.size()];
            if(nextchar == ' ' || nextchar == '(' || nextchar == '\t')
            {
                lineoffset = linenum;
                return true;
            }
        }
        if(pos == 1)
        {
            char prevchar = line[0];
            char nextchar = line[function.size() + 1];
            if(prevchar == '%' && (nextchar == ' ' || nextchar == '('))
            {
                lineoffset = linenum;
                return true;
            }
        }
        linenum++;  // move to next line count
    }
    lineoffset = 0;
    cmCTestLog(this->CTest, ERROR_MESSAGE,
               "Could not find entry point : " << function << " in " << filepath
                                               << "\n");
    return false;
}

bool
cmParseGTMCoverage::ParseMCOVLine(std::string const& line, std::string& routine,
                                  std::string& function, int& linenumber,
                                  int& count)
{
    // this method parses lines from the .mcov file
    // each line has ^COVERAGE(...) in it, and there
    // are several variants of coverage lines:
    //
    // ^COVERAGE("DIC11","PR1",0)="2:0:0:0"
    //          ( file  , entry, line ) = "number_executed:timing_info"
    // ^COVERAGE("%RSEL","SRC")="1:0:0:0"
    //          ( file  , entry ) = "number_executed:timing_info"
    // ^COVERAGE("%RSEL","init",8,"FOR_LOOP",1)=1
    //          ( file  , entry, line, IGNORE ) =number_executed
    std::vector<std::string> args;
    std::string::size_type   pos = line.find('(', 0);
    // if no ( is found, then return line has no coverage
    if(pos == std::string::npos)
    {
        return false;
    }
    std::string arg;
    bool        done = false;
    // separate out all of the comma separated arguments found
    // in the COVERAGE(...) line
    while(line[pos] && !done)
    {
        // save the char we are looking at
        char cur = line[pos];
        // , or ) means end of argument
        if(cur == ',' || cur == ')')
        {
            // save the argument into the argument vector
            args.push_back(arg);
            // start on a new argument
            arg.clear();
            // if we are at the end of the ), then finish while loop
            if(cur == ')')
            {
                done = true;
            }
        } else
        {
            // all chars except ", (, and % get stored in the arg string
            if(cur != '\"' && cur != '(' && cur != '%')
            {
                arg.append(1, line[pos]);
            }
        }
        // move to next char
        pos++;
    }
    // now parse the right hand side of the =
    pos = line.find('=');
    // no = found, this is an error
    if(pos == std::string::npos)
    {
        return false;
    }
    pos++;  // move past =

    // if the next positing is not a ", then this is a
    // COVERAGE(..)=count line and turn the rest of the string
    // past the = into an integer and set it to count
    if(line[pos] != '\"')
    {
        count = atoi(line.substr(pos).c_str());
    } else
    {
        // this means line[pos] is a ", and we have a
        // COVERAGE(...)="1:0:0:0" type of line
        pos++;  // move past "
        // find the first : past the "
        std::string::size_type pos2 = line.find(':', pos);
        // turn the string between the " and the first : into an integer
        // and set it to count
        count = atoi(line.substr(pos, pos2 - pos).c_str());
    }
    // less then two arguments is an error
    if(args.size() < 2)
    {
        cmCTestLog(this->CTest, ERROR_MESSAGE,
                   "Error parsing mcov line: [" << line << "]\n");
        return false;
    }
    routine  = args[0];  // the routine is the first argument
    function = args[1];  // the function in the routine is the second
    // in the two argument only format
    // ^COVERAGE("%RSEL","SRC"), the line offset is 0
    if(args.size() == 2)
    {
        // To avoid double counting of line 0 of each entry point,
        // Don't count the lines that do not give an explicit line
        // number.
        routine.clear();
        function.clear();
    } else
    {
        // this is the format for this line
        // ^COVERAGE("%RSEL","SRC",count)
        linenumber = atoi(args[2].c_str());
    }
    return true;
}
