#include "cmParseDelphiCoverage.h"

#include "cmSystemTools.h"
#include "cmXMLParser.h"
#include <cmsys/Directory.hxx>
#include <cmsys/FStream.hxx>
#include <cmsys/Glob.hxx>
#include <stdio.h>
#include <stdlib.h>


class cmParseDelphiCoverage::HTMLParser
{
public:
  typedef cmCTestCoverageHandlerContainer::SingleFileCoverageVector
  FileLinesType;
  HTMLParser(cmCTest* ctest, cmCTestCoverageHandlerContainer& cont)
     : CTest(ctest), Coverage(cont)
    {
    }

  virtual ~HTMLParser()
    {
    }

  bool initializeDelphiFile(const std::string filename,
     cmParseDelphiCoverage::HTMLParser::FileLinesType &coverageVector)
    {
    std::string line;
    size_t comPos;
    size_t semiPos;
    bool blockComFlag= false;
    bool lineComFlag= false;
    std::vector<std::string> beginSet;
    cmsys::ifstream in(filename.c_str());
    if(!in)
      {
      return false;
      }
    while(cmSystemTools::GetLineFromStream(in, line))
      {
      lineComFlag=false;
      // Unique cases found in lines.
      size_t beginPos = line.find("begin");

      //Check that the begin is the first non-space string on the line
      if( (beginPos == line.find_first_not_of(' ')) && beginPos != line.npos )
        {
        beginSet.push_back("begin");
        coverageVector.push_back(-1);
        continue;
        }
      else if(line.find('{') != line.npos)
        {
        blockComFlag=true;
        }
      else if(line.find('}') != line.npos)
        {
        blockComFlag=false;
        coverageVector.push_back(-1);
        continue;
        }
      else if((line.find("end;") != line.npos)
         && !beginSet.empty())
        {
        beginSet.pop_back();
        coverageVector.push_back(-1);
        continue;
        }

      //  This checks for comments after lines of code, finding the
      //  comment symbol after the ending semicolon.
      comPos = line.find("//");
      if(comPos != line.npos)
        {
        semiPos= line.find(';');
        if(comPos < semiPos)
          {
          lineComFlag=true;
          }
        }
      //Based up what was found, add a line to the coverageVector
      if(!beginSet.empty() && line != ""  && !blockComFlag
         && !lineComFlag)
        {
        coverageVector.push_back(0);
        }
      else
        {
        coverageVector.push_back(-1);
        }
      }
      return true;
    }
  bool ParseFile(const char* file)
    {
    std::string line=file;
    std::string lineresult;
    std::string lastroutine;
    std::string filename;
    std::string filelineoffset;
    size_t afterLineNum = 0;
    size_t lastoffset = 0;
    size_t endcovpos = 0;
    size_t endnamepos = 0;
    size_t pos = 0;

    /*
    *  This first 'while' section goes through the found HTML
    *  file name and attempts to capture the source file name
    *   which is set as part of the HTML file name: the name of
    *   the file is found in parenthesis '()'
    *
    *   See test HTML file name: UTCovTest(UTCovTest.pas).html.
    *
    *   Find the text inside each pair of parenthesis and check
    *   to see if it ends in '.pas'.  If it can't be found,
    *   exit the function.
    */
    while(true)
      {
      lastoffset = line.find('(',pos);
      if(lastoffset==line.npos)
        {
        cmCTestOptionalLog(this->CTest,HANDLER_VERBOSE_OUTPUT,
           endnamepos << "File not found  " << lastoffset  << std::endl,
           this->Coverage.Quiet);
        return false;
        }
      endnamepos = line.find(')',lastoffset);
      filename = line.substr(lastoffset+1,
         (endnamepos-1)-lastoffset);
      if(filename.find(".pas") != filename.npos)
        {
        cmCTestOptionalLog(this->CTest,HANDLER_VERBOSE_OUTPUT,
           "Coverage found for file:  " << filename  << std::endl,
           this->Coverage.Quiet);
        break;
        }
      pos = lastoffset+1;
      }
    /*
    *  Glob through the source directory for the
    *  file found above
    */
    cmsys::Glob gl;
    gl.RecurseOn();
    gl.RecurseThroughSymlinksOff();
    std::string glob = Coverage.SourceDir + "*/" + filename;
    gl.FindFiles(glob);
    std::vector<std::string> const& files = gl.GetFiles();
    if(files.empty())
      {
      /*
      *  If that doesn't find any matching files
      *  return a failure.
      */
      cmCTestOptionalLog(this->CTest,HANDLER_VERBOSE_OUTPUT,
         "Unable to find file matching" << glob << std::endl,
         this->Coverage.Quiet);
      return false;
      }
    FileLinesType&  coverageVector =
       this->Coverage.TotalCoverage[files[0]];

    /*
    *  Initialize the file to have all code between 'begin' and
    *  'end' tags marked as executable
    */

    this->initializeDelphiFile(files[0],coverageVector);

    cmsys::ifstream in(file);
    if(!in)
      {
      return false;
      }

    /*
    *  Now read the HTML file, looking for the lines that have an
    *  "inline" in it. Then parse out the "class" value of that
    *  line to determine if the line is executed or not.
    *
    *  Sample HTML line:
    *
    *  <tr class="covered"><td>47</td><td><pre style="display:inline;">
    *    &nbsp;CheckEquals(1,2-1);</pre></td></tr>
    *
    */

    while(  cmSystemTools::GetLineFromStream(in, line))
      {
      if(line.find("inline") == line.npos)
        {
        continue;
        }

      lastoffset = line.find("class=");
      endcovpos = line.find(">",lastoffset);
      lineresult = line.substr(lastoffset+7,(endcovpos-8)-lastoffset);

      if(lineresult == "covered")
        {
        afterLineNum = line.find('<',endcovpos+5);
        filelineoffset= line.substr(endcovpos+5,
        afterLineNum-(endcovpos+5));
        coverageVector[atoi(filelineoffset.c_str())-1] = 1;
        }
      }
      return true;
    }


  private:
    cmCTest* CTest;
    cmCTestCoverageHandlerContainer& Coverage;
};

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

bool cmParseDelphiCoverage::LoadCoverageData(
  const std::vector<std::string> files)
  {
  size_t i;
  std::string path;
  size_t numf = files.size();
  for (i = 0; i < numf; i++)
    {
    path = files[i];

    cmCTestOptionalLog(this->CTest,HANDLER_VERBOSE_OUTPUT,
       "Reading HTML File " << path  << std::endl, this->Coverage.Quiet);
    if(cmSystemTools::GetFilenameLastExtension(path) == ".html")
      {
      if(!this->ReadDelphiHTML(path.c_str()))
        {
        return false;
        }
      }
    }
  return true;
  }

bool cmParseDelphiCoverage::ReadDelphiHTML(const char* file)
  {
  cmParseDelphiCoverage::HTMLParser
     parser(this->CTest, this->Coverage);
  parser.ParseFile(file);
  return true;
  }
