#include "cmParseCoberturaCoverage.h"

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

#include "cmsys/FStream.hxx"
#include <stdlib.h>
#include <string.h>

class cmParseCoberturaCoverage::XMLParser : public cmXMLParser
{
public:
    XMLParser(cmCTest* ctest, cmCTestCoverageHandlerContainer& cont)
    : CTest(ctest)
    , Coverage(cont)
    {
        this->InSources     = false;
        this->InSource      = false;
        this->SkipThisClass = false;
        this->FilePaths.push_back(this->Coverage.SourceDir);
        this->FilePaths.push_back(this->Coverage.BinaryDir);
        this->CurFileName.clear();
    }

    ~XMLParser() override {}

protected:
    void EndElement(const std::string& name) override
    {
        if(name == "source")
        {
            this->InSource = false;
        } else if(name == "sources")
        {
            this->InSources = false;
        } else if(name == "class")
        {
            this->SkipThisClass = false;
        }
    }

    void CharacterDataHandler(const char* data, int length) override
    {
        std::string tmp;
        tmp.insert(0, data, length);
        if(this->InSources && this->InSource)
        {
            this->FilePaths.push_back(tmp);
            cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                               "Adding Source: " << tmp << std::endl,
                               this->Coverage.Quiet);
        }
    }

    void StartElement(const std::string& name, const char** atts) override
    {
        std::string FoundSource;
        std::string finalpath;
        if(name == "source")
        {
            this->InSource = true;
        } else if(name == "sources")
        {
            this->InSources = true;
        } else if(name == "class")
        {
            int tagCount = 0;
            while(true)
            {
                if(strcmp(atts[tagCount], "filename") == 0)
                {
                    cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                                       "Reading file: " << atts[tagCount + 1]
                                                        << std::endl,
                                       this->Coverage.Quiet);
                    std::string filename = atts[tagCount + 1];
                    this->CurFileName.clear();

                    // Check if this is an absolute path that falls within our
                    // source or binary directories.
                    for(std::string const& filePath : FilePaths)
                    {
                        if(filename.find(filePath) == 0)
                        {
                            this->CurFileName = filename;
                            break;
                        }
                    }

                    if(this->CurFileName.empty())
                    {
                        // Check if this is a path that is relative to our
                        // source or binary directories.
                        for(std::string const& filePath : FilePaths)
                        {
                            finalpath = filePath + "/" + filename;
                            if(cmSystemTools::FileExists(finalpath))
                            {
                                this->CurFileName = finalpath;
                                break;
                            }
                        }
                    }

                    cmsys::ifstream fin(this->CurFileName.c_str());
                    if(this->CurFileName.empty() || !fin)
                    {
                        this->CurFileName =
                            this->Coverage.BinaryDir + "/" + atts[tagCount + 1];
                        fin.open(this->CurFileName.c_str());
                        if(!fin)
                        {
                            cmCTestOptionalLog(this->CTest,
                                               HANDLER_VERBOSE_OUTPUT,
                                               "Skipping system file "
                                                   << filename << std::endl,
                                               this->Coverage.Quiet);

                            this->SkipThisClass = true;
                            break;
                        }
                    }
                    std::string    line;
                    FileLinesType& curFileLines =
                        this->Coverage.TotalCoverage[this->CurFileName];
                    while(cmSystemTools::GetLineFromStream(fin, line))
                    {
                        curFileLines.push_back(-1);
                    }

                    break;
                }
                ++tagCount;
            }
        } else if(name == "line")
        {
            int tagCount  = 0;
            int curNumber = -1;
            int curHits   = -1;
            while(true)
            {
                if(this->SkipThisClass)
                {
                    break;
                }
                if(strcmp(atts[tagCount], "hits") == 0)
                {
                    curHits = atoi(atts[tagCount + 1]);
                } else if(strcmp(atts[tagCount], "number") == 0)
                {
                    curNumber = atoi(atts[tagCount + 1]);
                }

                if(curHits > -1 && curNumber > 0)
                {
                    FileLinesType& curFileLines =
                        this->Coverage.TotalCoverage[this->CurFileName];
                    {
                        curFileLines[curNumber - 1] = curHits;
                    }
                    break;
                }
                ++tagCount;
            }
        }
    }

private:
    bool                     InSources;
    bool                     InSource;
    bool                     SkipThisClass;
    std::vector<std::string> FilePaths;
    typedef cmCTestCoverageHandlerContainer::SingleFileCoverageVector
                                     FileLinesType;
    cmCTest*                         CTest;
    cmCTestCoverageHandlerContainer& Coverage;
    std::string                      CurFileName;
};

cmParseCoberturaCoverage::cmParseCoberturaCoverage(
    cmCTestCoverageHandlerContainer& cont, cmCTest* ctest)
: Coverage(cont)
, CTest(ctest)
{}

bool
cmParseCoberturaCoverage::ReadCoverageXML(const char* xmlFile)
{
    cmParseCoberturaCoverage::XMLParser parser(this->CTest, this->Coverage);
    parser.ParseFile(xmlFile);
    return true;
}
