/*=========================================================================

  Program:   CMake - Cross-Platform Makefile Generator
  Module:    $RCSfile$
  Language:  C++
  Date:      $Date$
  Version:   $Revision$

  Copyright (c) 2002 Kitware, Inc., Insight Consortium.  All rights reserved.
  See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details.

     This software is distributed WITHOUT ANY WARRANTY; without even 
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
     PURPOSE.  See the above copyright notices for more information.

=========================================================================*/
#include "cmLoadCacheCommand.h"

#include <cmsys/RegularExpression.hxx>

// cmLoadCacheCommand
bool cmLoadCacheCommand::InitialPass(std::vector<std::string> const& args)
{
  if (args.size()< 1)
    {
    this->SetError("called with wrong number of arguments.");
    }

  if(args.size() >= 2 && args[1] == "READ_WITH_PREFIX")
    {
    return this->ReadWithPrefix(args);
    }
  
  // Cache entries to be excluded from the import list.
  // If this set is empty, all cache entries are brought in
  // and they can not be overridden.
  bool excludeFiles=false;
  unsigned int i;
  std::set<cmStdString> excludes;

  for(i=0; i<args.size(); i++)
    {
    if (excludeFiles)
      {
      excludes.insert(args[i]);
      }
    if (args[i] == "EXCLUDE")
      {
      excludeFiles=true;
      }
    if (excludeFiles && (args[i] == "INCLUDE_INTERNALS"))
      {
      break;
      }
    }

  // Internal cache entries to be imported.
  // If this set is empty, no internal cache entries are
  // brought in.
  bool includeFiles=false;
  std::set<cmStdString> includes;

  for(i=0; i<args.size(); i++)
    {
    if (includeFiles)
      {
      includes.insert(args[i]);
      }
    if (args[i] == "INCLUDE_INTERNALS")
      {
      includeFiles=true;
      }
    if (includeFiles && (args[i] == "EXCLUDE"))
      {
      break;
      }
    }

  // Loop over each build directory listed in the arguments.  Each
  // directory has a cache file.
  for(i=0; i<args.size(); i++)
    {
    if ((args[i] == "EXCLUDE") || (args[i] == "INCLUDE_INTERNALS"))
      {
      break;
      }
    this->Makefile->GetCacheManager()->LoadCache(args[i].c_str(), false,
                                             excludes, includes);
    }


  return true;
}

//----------------------------------------------------------------------------
bool cmLoadCacheCommand::ReadWithPrefix(std::vector<std::string> const& args)
{
  // Make sure we have a prefix.
  if(args.size() < 3)
    {
    this->SetError("READ_WITH_PREFIX form must specify a prefix.");
    return false;
    }
  
  // Make sure the cache file exists.
  std::string cacheFile = args[0]+"/CMakeCache.txt";
  if(!cmSystemTools::FileExists(cacheFile.c_str()))
    {
    std::string e = "Cannot load cache file from " + cacheFile;
    this->SetError(e.c_str());
    return false;
    }
  
  // Prepare the table of variables to read.
  this->Prefix = args[2];
  for(unsigned int i=3; i < args.size(); ++i)
    {
    this->VariablesToRead.insert(args[i]);
    }
  
  // Read the cache file.
  std::ifstream fin(cacheFile.c_str());  
  
  // This is a big hack read loop to overcome a buggy ifstream
  // implementation on HP-UX.  This should work on all platforms even
  // for small buffer sizes.
  const int bufferSize = 4096;
  char buffer[bufferSize];  
  std::string line;
  while(fin)
    {
    // Read a block of the file.
    fin.read(buffer, bufferSize);
    if(fin.gcount())
      {
      // Parse for newlines directly.
      const char* i = buffer;
      const char* end = buffer+fin.gcount();
      while(i != end)
        {
        const char* begin = i;
        while(i != end && *i != '\n') { ++i; }
        if(i == begin || *(i-1) != '\r')
          {
          // Include this portion of the line.
          line += std::string(begin, i-begin);
          }
        else
          {
          // Include this portion of the line.
          // Don't include the \r in a \r\n pair.
          line += std::string(begin, i-1-begin);
          }
        if(i != end)
          {
          // Completed a line.
          this->CheckLine(line.c_str());
          line = "";
          
          // Skip the newline character.
          ++i;
          }
        }
      }
    }
  if(line.length())
    {
    // Partial last line.
    this->CheckLine(line.c_str());
    }
  
  return true;
}

//----------------------------------------------------------------------------
void cmLoadCacheCommand::CheckLine(const char* line)
{
  // Check one line of the cache file.
  std::string var;
  std::string value;
  if(this->ParseEntry(line, var, value))
    {
    // Found a real entry.  See if this one was requested.
    if(this->VariablesToRead.find(var) != this->VariablesToRead.end())
      {
      // This was requested.  Set this variable locally with the given
      // prefix.
      var = this->Prefix + var;
      if(value.length())
        {
        this->Makefile->AddDefinition(var.c_str(), value.c_str());
        }
      else
        {
        this->Makefile->RemoveDefinition(var.c_str());
        }
      }
    }
}

//----------------------------------------------------------------------------
bool cmLoadCacheCommand::ParseEntry(const char* entry, std::string& var,
                                    std::string& value)
{
  // input line is:         key:type=value
  cmsys::RegularExpression reg("^([^:]*):([^=]*)=(.*[^\t ]|[\t ]*)[\t ]*$");
  // input line is:         "key":type=value
  cmsys::RegularExpression 
    regQuoted("^\"([^\"]*)\":([^=]*)=(.*[^\t ]|[\t ]*)[\t ]*$");
  bool flag = false;
  if(regQuoted.find(entry))
    {
    var = regQuoted.match(1);
    value = regQuoted.match(3);
    flag = true;
    }
  else if (reg.find(entry))
    {
    var = reg.match(1);
    value = reg.match(3);
    flag = true;
    }

  // if value is enclosed in single quotes ('foo') then remove them
  // it is used to enclose trailing space or tab
  if (flag && 
      value.size() >= 2 &&
      value[0] == '\'' && 
      value[value.size() - 1] == '\'') 
    {
    value = value.substr(1, value.size() - 2);
    }
  return flag;
}