/*============================================================================
  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 "cmGeneratorExpression.h"

#include "cmMakefile.h"
#include "cmTarget.h"

//----------------------------------------------------------------------------
cmGeneratorExpression::cmGeneratorExpression(
  cmMakefile* mf, const char* config,
  cmListFileBacktrace const& backtrace, bool quiet):
  Makefile(mf), Config(config), Backtrace(backtrace), Quiet(quiet)
{
  this->TargetInfo.compile("^\\$<TARGET"
                           "(|_SONAME|_LINKER)"  // File with what purpose?
                           "_FILE(|_NAME|_DIR):" // Filename component.
                           "([A-Za-z0-9_-]+)"    // Target name.
                           ">$");
}

//----------------------------------------------------------------------------
const char* cmGeneratorExpression::Process(std::string const& input)
{
  return this->Process(input.c_str());
}

//----------------------------------------------------------------------------
const char* cmGeneratorExpression::Process(const char* input)
{
  this->Data.clear();

  // We construct and evaluate expressions directly in the output
  // buffer.  Each expression is replaced by its own output value
  // after evaluation.  A stack of barriers records the starting
  // indices of open (pending) expressions.
  for(const char* c = input; *c; ++c)
    {
    if(c[0] == '$' && c[1] == '<')
      {
      this->Barriers.push(this->Data.size());
      this->Data.push_back('$');
      this->Data.push_back('<');
      c += 1;
      }
    else if(c[0] == '>' && !this->Barriers.empty())
      {
      this->Data.push_back('>');
      if(!this->Evaluate()) { break; }
      this->Barriers.pop();
      }
    else
      {
      this->Data.push_back(c[0]);
      }
    }

  // Return a null-terminated output value.
  this->Data.push_back('\0');
  return &*this->Data.begin();
}

//----------------------------------------------------------------------------
bool cmGeneratorExpression::Evaluate()
{
  // The top-most barrier points at the beginning of the expression.
  size_t barrier = this->Barriers.top();

  // Construct a null-terminated representation of the expression.
  this->Data.push_back('\0');
  const char* expr = &*(this->Data.begin()+barrier);

  // Evaluate the expression.
  std::string result;
  if(this->Evaluate(expr, result))
    {
    // Success.  Replace the expression with its evaluation result.
    this->Data.erase(this->Data.begin()+barrier, this->Data.end());
    this->Data.insert(this->Data.end(), result.begin(), result.end());
    return true;
    }
  else if(!this->Quiet)
    {
    // Failure.  Report the error message.
    cmOStringStream e;
    e << "Error evaluating generator expression:\n"
      << "  " << expr << "\n"
      << result;
    this->Makefile->GetCMakeInstance()
      ->IssueMessage(cmake::FATAL_ERROR, e.str().c_str(),
                     this->Backtrace);
    return false;
    }
  return true;
}

//----------------------------------------------------------------------------
bool cmGeneratorExpression::Evaluate(const char* expr, std::string& result)
{
  if(this->TargetInfo.find(expr))
    {
    if(!this->EvaluateTargetInfo(result))
      {
      return false;
      }
    }
  else if(strcmp(expr, "$<CONFIGURATION>") == 0)
    {
    result = this->Config? this->Config : "";
    }
  else
    {
    result = "Expression syntax not recognized.";
    return false;
    }
  return true;
}

//----------------------------------------------------------------------------
bool cmGeneratorExpression::EvaluateTargetInfo(std::string& result)
{
  // Lookup the referenced target.
  std::string name = this->TargetInfo.match(3);
  cmTarget* target = this->Makefile->FindTargetToUse(name.c_str());
  if(!target)
    {
    result = "No target \"" + name + "\"";
    return false;
    }
  if(target->GetType() >= cmTarget::UTILITY &&
     target->GetType() != cmTarget::UNKNOWN_LIBRARY)
    {
    result = "Target \"" + name + "\" is not an executable or library.";
    return false;
    }
  this->Targets.insert(target);

  // Lookup the target file with the given purpose.
  std::string purpose = this->TargetInfo.match(1);
  if(purpose == "")
    {
    // The target implementation file (.so.1.2, .dll, .exe, .a).
    result = target->GetFullPath(this->Config, false, true);
    }
  else if(purpose == "_LINKER")
    {
    // The file used to link to the target (.so, .lib, .a).
    if(!target->IsLinkable())
      {
      result = ("TARGET_LINKER_FILE is allowed only for libraries and "
                "executables with ENABLE_EXPORTS.");
      return false;
      }
    result = target->GetFullPath(this->Config, target->HasImportLibrary());
    }
  else if(purpose == "_SONAME")
    {
    // The target soname file (.so.1).
    if(target->IsDLLPlatform())
      {
      result = "TARGET_SONAME_FILE is not allowed for DLL target platforms.";
      return false;
      }
    if(target->GetType() != cmTarget::SHARED_LIBRARY)
      {
      result = "TARGET_SONAME_FILE is allowed only for SHARED libraries.";
      return false;
      }
    result = target->GetDirectory(this->Config);
    result += "/";
    result += target->GetSOName(this->Config);
    }

  // Extract the requested portion of the full path.
  std::string part = this->TargetInfo.match(2);
  if(part == "_NAME")
    {
    result = cmSystemTools::GetFilenameName(result);
    }
  else if(part == "_DIR")
    {
    result = cmSystemTools::GetFilenamePath(result);
    }
  return true;
}
