/*============================================================================
  CMake - Cross Platform Makefile Generator
  Copyright 2000-2009 Kitware, Inc., Insight Software Consortium

  Distributed under the OSI-approved BSD License (the "License");
  see accompanying file Copyright.txt for details.

  This software is distributed WITHOUT ANY WARRANTY; without even the
  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  See the License for more information.
============================================================================*/
#include "cmOutputRequiredFilesCommand.h"
#include "cmMakeDepend.h"

class cmLBDepend : public cmMakeDepend
{
  /**
   * Compute the depend information for this class.
   */
  virtual void DependWalk(cmDependInformation* info);
};

void cmLBDepend::DependWalk(cmDependInformation* info)
{
  std::ifstream fin(info->FullPath.c_str());
  if(!fin)
    {
    cmSystemTools::Error("error can not open ", info->FullPath.c_str());
    return;
    }

  std::string line;
  while(cmSystemTools::GetLineFromStream(fin, line))
    {
    if(!strncmp(line.c_str(), "#include", 8))
      {
      // if it is an include line then create a string class
      std::string currentline = line;
      size_t qstart = currentline.find('\"', 8);
      size_t qend;
      // if a quote is not found look for a <
      if(qstart == std::string::npos)
        {
        qstart = currentline.find('<', 8);
        // if a < is not found then move on
        if(qstart == std::string::npos)
          {
          cmSystemTools::Error("unknown include directive ",
                               currentline.c_str() );
          continue;
          }
        else
          {
          qend = currentline.find('>', qstart+1);
          }
        }
      else
        {
        qend = currentline.find('\"', qstart+1);
        }
      // extract the file being included
      std::string includeFile = currentline.substr(qstart+1, qend - qstart-1);
      // see if the include matches the regular expression
      if(!this->IncludeFileRegularExpression.find(includeFile))
        {
        if(this->Verbose)
          {
          std::string message = "Skipping ";
          message += includeFile;
          message += " for file ";
          message += info->FullPath.c_str();
          cmSystemTools::Error(message.c_str(), 0);
          }
        continue;
        }

      // Add this file and all its dependencies.
      this->AddDependency(info, includeFile.c_str());
      /// add the cxx file if it exists
      std::string cxxFile = includeFile;
      std::string::size_type pos = cxxFile.rfind('.');
      if(pos != std::string::npos)
        {
        std::string root = cxxFile.substr(0, pos);
        cxxFile = root + ".cxx";
        bool found = false;
        // try jumping to .cxx .cpp and .c in order
        if(cmSystemTools::FileExists(cxxFile.c_str()))
          {
          found = true;
          }
        for(std::vector<std::string>::iterator i =
              this->IncludeDirectories.begin();
            i != this->IncludeDirectories.end(); ++i)
          {
          std::string path = *i;
          path = path + "/";
          path = path + cxxFile;
          if(cmSystemTools::FileExists(path.c_str()))
            {
            found = true;
            }
          }
        if (!found)
          {
          cxxFile = root + ".cpp";
          if(cmSystemTools::FileExists(cxxFile.c_str()))
            {
            found = true;
            }
          for(std::vector<std::string>::iterator i =
                this->IncludeDirectories.begin();
              i != this->IncludeDirectories.end(); ++i)
            {
            std::string path = *i;
            path = path + "/";
            path = path + cxxFile;
            if(cmSystemTools::FileExists(path.c_str()))
              {
              found = true;
              }
            }
          }
        if (!found)
          {
          cxxFile = root + ".c";
          if(cmSystemTools::FileExists(cxxFile.c_str()))
            {
            found = true;
            }
          for(std::vector<std::string>::iterator i =
                this->IncludeDirectories.begin();
              i != this->IncludeDirectories.end(); ++i)
            {
            std::string path = *i;
            path = path + "/";
            path = path + cxxFile;
            if(cmSystemTools::FileExists(path.c_str()))
              {
              found = true;
              }
            }
          }
        if (!found)
          {
          cxxFile = root + ".txx";
          if(cmSystemTools::FileExists(cxxFile.c_str()))
            {
            found = true;
            }
          for(std::vector<std::string>::iterator i =
                this->IncludeDirectories.begin();
              i != this->IncludeDirectories.end(); ++i)
            {
            std::string path = *i;
            path = path + "/";
            path = path + cxxFile;
            if(cmSystemTools::FileExists(path.c_str()))
              {
              found = true;
              }
            }
          }
        if (found)
          {
          this->AddDependency(info, cxxFile.c_str());
          }
        }
      }
    }
}

// cmOutputRequiredFilesCommand
bool cmOutputRequiredFilesCommand
::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &)
{
  if(this->Disallowed(cmPolicies::CMP0032,
      "The output_required_files command should not be called; see CMP0032."))
    { return true; }
  if(args.size() != 2 )
    {
    this->SetError("called with incorrect number of arguments");
    return false;
    }

  // store the arg for final pass
  this->File = args[0];
  this->OutputFile = args[1];

  // compute the list of files
  cmLBDepend md;
  md.SetMakefile(this->Makefile);
  md.AddSearchPath(this->Makefile->GetStartDirectory());
  // find the depends for a file
  const cmDependInformation *info = md.FindDependencies(this->File.c_str());
  if (info)
    {
    // write them out
    FILE *fout = fopen(this->OutputFile.c_str(),"w");
    if(!fout)
      {
      std::string err = "Can not open output file: ";
      err += this->OutputFile;
      this->SetError(err.c_str());
      return false;
      }
    std::set<cmDependInformation const*> visited;
    this->ListDependencies(info,fout, &visited);
    fclose(fout);
    }

  return true;
}

void cmOutputRequiredFilesCommand::
ListDependencies(cmDependInformation const *info,
                 FILE *fout,
                 std::set<cmDependInformation const*> *visited)
{
  // add info to the visited set
  visited->insert(info);
  // now recurse with info's dependencies
  for(cmDependInformation::DependencySetType::const_iterator d =
        info->DependencySet.begin();
      d != info->DependencySet.end(); ++d)
    {
    if (visited->find(*d) == visited->end())
      {
      if(info->FullPath != "")
        {
        std::string tmp = (*d)->FullPath;
        std::string::size_type pos = tmp.rfind('.');
        if(pos != std::string::npos && (tmp.substr(pos) != ".h"))
          {
          tmp = tmp.substr(0, pos);
          fprintf(fout,"%s\n",(*d)->FullPath.c_str());
          }
        }
      this->ListDependencies(*d,fout,visited);
      }
    }
}

