#include "cmParsePHPCoverage.h"

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

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

/*
  To setup coverage for php.

  - edit php.ini to add auto prepend and append php files from phpunit
  auto_prepend_file =
  auto_append_file =
  - run the tests
  - run this program on all the files in c:/tmp

*/

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

bool
cmParsePHPCoverage::ReadUntil(std::istream& in, char until)
{
    char c = 0;
    while(in.get(c) && c != until)
    {
    }
    return c == until;
}
bool
cmParsePHPCoverage::ReadCoverageArray(std::istream&      in,
                                      std::string const& fileName)
{
    cmCTestCoverageHandlerContainer::SingleFileCoverageVector& coverageVector =
        this->Coverage.TotalCoverage[fileName];

    char c;
    char buf[4];
    in.read(buf, 3);
    buf[3] = 0;
    if(strcmp(buf, ";a:") != 0)
    {
        cmCTestLog(this->CTest, ERROR_MESSAGE,
                   "failed to read start of coverage array, found : " << buf
                                                                      << "\n");
        return false;
    }
    int size = 0;
    if(!this->ReadInt(in, size))
    {
        cmCTestLog(this->CTest, ERROR_MESSAGE, "failed to read size ");
        return false;
    }
    if(!in.get(c) && c == '{')
    {
        cmCTestLog(this->CTest, ERROR_MESSAGE, "failed to read open {\n");
        return false;
    }
    for(int i = 0; i < size; i++)
    {
        this->ReadUntil(in, ':');
        int line = 0;
        this->ReadInt(in, line);
        // ok xdebug may have a bug here
        // it seems to be 1 based but often times
        // seems to have a 0'th line.
        line--;
        if(line < 0)
        {
            line = 0;
        }
        this->ReadUntil(in, ':');
        int value = 0;
        this->ReadInt(in, value);
        // make sure the vector is the right size and is
        // initialized with -1 for each line
        while(coverageVector.size() <= static_cast<size_t>(line))
        {
            coverageVector.push_back(-1);
        }
        // if value is less than 0, set it to zero
        // TODO figure out the difference between
        // -1 and -2 in xdebug coverage??  For now
        // assume less than 0 is just not covered
        // CDash expects -1 for non executable code (like comments)
        // and 0 for uncovered code, and a positive value
        // for number of times a line was executed
        if(value < 0)
        {
            value = 0;
        }
        // if unset then set it to value
        if(coverageVector[line] == -1)
        {
            coverageVector[line] = value;
        }
        // otherwise increment by value
        else
        {
            coverageVector[line] += value;
        }
    }
    return true;
}

bool
cmParsePHPCoverage::ReadInt(std::istream& in, int& v)
{
    std::string s;
    char        c = 0;
    while(in.get(c) && c != ':' && c != ';')
    {
        s += c;
    }
    v = atoi(s.c_str());
    return true;
}

bool
cmParsePHPCoverage::ReadArraySize(std::istream& in, int& size)
{
    char c = 0;
    in.get(c);
    if(c != 'a')
    {
        return false;
    }
    if(in.get(c) && c == ':')
    {
        if(this->ReadInt(in, size))
        {
            return true;
        }
    }
    return false;
}

bool
cmParsePHPCoverage::ReadFileInformation(std::istream& in)
{
    char buf[4];
    in.read(buf, 2);
    buf[2] = 0;
    if(strcmp(buf, "s:") != 0)
    {
        cmCTestLog(this->CTest, ERROR_MESSAGE,
                   "failed to read start of file info found: [" << buf
                                                                << "]\n");
        return false;
    }
    char c;
    int  size = 0;
    if(this->ReadInt(in, size))
    {
        size++;  // add one for null termination
        char* s = new char[size + 1];
        // read open quote
        if(in.get(c) && c != '"')
        {
            delete[] s;
            return false;
        }
        // read the string data
        in.read(s, size - 1);
        s[size - 1]          = 0;
        std::string fileName = s;
        delete[] s;
        // read close quote
        if(in.get(c) && c != '"')
        {
            cmCTestLog(this->CTest, ERROR_MESSAGE,
                       "failed to read close quote\n"
                           << "read [" << c << "]\n");
            return false;
        }
        if(!this->ReadCoverageArray(in, fileName))
        {
            cmCTestLog(this->CTest, ERROR_MESSAGE,
                       "failed to read coverage array for file: " << fileName
                                                                  << "\n");
            return false;
        }
        return true;
    }
    return false;
}

bool
cmParsePHPCoverage::ReadPHPData(const char* file)
{
    cmsys::ifstream in(file);
    if(!in)
    {
        return false;
    }
    int size = 0;
    this->ReadArraySize(in, size);
    char c = 0;
    in.get(c);
    if(c != '{')
    {
        cmCTestLog(this->CTest, ERROR_MESSAGE, "failed to read open array\n");
        return false;
    }
    for(int i = 0; i < size; i++)
    {
        if(!this->ReadFileInformation(in))
        {
            cmCTestLog(this->CTest, ERROR_MESSAGE,
                       "Failed to read file #" << i << "\n");
            return false;
        }
        in.get(c);
        if(c != '}')
        {
            cmCTestLog(this->CTest, ERROR_MESSAGE,
                       "failed to read close array\n");
            return false;
        }
    }
    return true;
}

bool
cmParsePHPCoverage::ReadPHPCoverageDirectory(const char* d)
{
    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(!this->ReadPHPData(path.c_str()))
            {
                return false;
            }
        }
    }
    return true;
}
