#include "cmStandardIncludes.h"
#include "cmSystemTools.h"
#include "cmXMLParser.h"
#include "cmParseCoberturaCoverage.h"
#include <cmsys/Directory.hxx>
#include <cmsys/FStream.hxx>

//----------------------------------------------------------------------------
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 = "";
  }

  virtual ~XMLParser()
  {
  }

protected:


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

  virtual void CharacterDataHandler(const char* data, int length)
  {
     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);
       }
  }

  virtual void StartElement(const std::string& name, const char** atts)
  {
    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 = "";

          // Check if this is an absolute path that falls within our
          // source or binary directories.
          for(size_t i=0;i < FilePaths.size();i++)
            {
            if (filename.find(FilePaths[i]) == 0)
              {
              this->CurFileName = filename;
              break;
              }
            }

          if (this->CurFileName == "")
            {
            // Check if this is a path that is relative to our source or
            // binary directories.
            for(size_t i=0;i < FilePaths.size();i++)
              {
              finalpath = FilePaths[i] + "/" + filename;
              if(cmSystemTools::FileExists(finalpath.c_str()))
                {
                this->CurFileName = finalpath;
                break;
                }
              }
            }

          cmsys::ifstream fin(this->CurFileName.c_str());
          if(this->CurFileName == "" || !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;
}
