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

  Program:   Insight Segmentation & Registration Toolkit
  Module:    $RCSfile$
  Language:  C++
  Date:      $Date$
  Version:   $Revision$


  Copyright (c) 2000 National Library of Medicine
  All rights reserved.

  See COPYRIGHT.txt for copyright details.

=========================================================================*/
#include "cmCableDefineSetCommand.h"
#include "cmCacheManager.h"

#include "cmRegularExpression.h"


// cmCableDefineSetCommand
bool cmCableDefineSetCommand::Invoke(std::vector<std::string>& args)
{
  if(args.size() < 2)
    {
    this->SetError("called with incorrect number of arguments");
    return false;
    }
  
  // This command needs access to the Cable data.
  this->SetupCableData();
  
  std::vector<std::string>::const_iterator arg = args.begin();
  
  // The first argument is the name of the set.
  m_SetName = *arg++;
  
  // All arguments until a "SOURCE_FILES" are the elements to be placed in
  // the set.
  for(; (arg != args.end()) && (*arg != "SOURCE_FILES"); ++arg)
    {
    // If the element cannot be added, return an error.
    // This can occur when a tag is not specified and can't be generated.
    if(!this->AddElement(*arg))
      { return false; }
    }

  // If we are not at the end, the "SOURCE_FILES" keyword has been
  // encountered.
  if(arg != args.end())
    {
    // The rest of the arguments are source files to be included in
    // any package which references the set.
    for(++arg; arg != args.end(); ++arg)
      {
      if(!this->AddSourceFile(*arg))
        { return false; }
      }
    }
  
  // Write this command's configuration output.
  this->WriteConfiguration();
  
  return true;
}


/**
 * Write the CABLE configuration code to define this Set.
 */
void cmCableDefineSetCommand::WriteConfiguration() const
{
  cmRegularExpression needCdataBlock("[&<>]");
  
  // Get the ouptut information from the cmCableData.
  std::ostream& os = m_CableData->GetOutputStream();
  cmCableData::Indentation indent = m_CableData->GetIndentation();
  
  // Output the code.
  os << indent << "<Set name=\"" << m_SetName.c_str() << "\">" << std::endl;
  for(std::vector<std::string>::const_iterator e = m_SourceHeaders.begin();
      e != m_SourceHeaders.end(); ++e)
    {
    os << indent << "  <File name=\"" << e->c_str() << "\"/>" << std::endl;
    }
  for(std::vector<std::string>::const_iterator e = m_InstantiationSources.begin();
      e != m_InstantiationSources.end(); ++e)
    {
    os << indent << "  <File name=\"" << e->c_str()
       << "\" purpose=\"instantiate\"/>" << std::endl;
    }
  for(Elements::const_iterator e = m_Elements.begin();
      e != m_Elements.end(); ++e)
    {
    os << indent << "  <Element";
    // Only output the tag if it is not the empty string.
    if(e->first.length() > 0)
      {
      os << " tag=\"" << e->first.c_str() << "\"";
      }
    os << ">";
    if(needCdataBlock.find(e->second.c_str()))
      {
      os << "<![CDATA[" << e->second.c_str() << "]]>";
      }
    else
      {
      os << e->second.c_str();
      }
    os << "</Element>" << std::endl;
    }
  os << indent << "</Set>" << std::endl;
}


/**
 * Add an element to the set.  The given string is the argument to the
 * command describing the element.  There are two formats allowed:
 *  "code" = The code describing the element to CABLE is simply given.
 *           The GenerateTag() method will guess at a good tag for the
 *           code.
 *  "tag:code" = The left side of a single colon is text describing the tag.
 *               GenerateTag() will not be called.
 */
bool cmCableDefineSetCommand::AddElement(const std::string& arg)
{
  // A regular expression to match the tagged element specification.
  cmRegularExpression tagGiven("^([A-Za-z_0-9]*)[ \t]*:[ \t]*([^:].*|::.*)$");
  
  std::string tag;
  std::string code;

  if(tagGiven.find(arg.c_str()))
    {
    // A tag was given.  Use it.
    tag = tagGiven.match(1);
    code = tagGiven.match(2);
    }
  else
    {
    // No tag was given.  Try to generate one.
    if(!this->GenerateTag(arg, tag))
      { return false; }
    code = arg;
    }
  
  // Add an element with the given tag and code.
  m_Elements.push_back(Element(tag, code));
  return true;
}


/**
 * Given the string representing a set element, automatically generate
 * the CABLE element tag for it.
 *
 * **This function determines how the output language of all
 * CABLE-generated wrappers will look!**
 */
bool
cmCableDefineSetCommand::GenerateTag(const std::string& element,
                                     std::string& tag)
{
  // Hold the regular expressions for matching against the element.
  cmRegularExpression regex;
  
  // If the element's code begins in a $, it is referring to a set name.
  // The set's elements have their own tags, so we don't need one.
  regex.compile("^[ \t]*\\$");
  if(regex.find(element))
    { tag = ""; return true; }
  
  // Test for simple integer
  regex.compile("^[ \t]*([0-9]*)[ \t]*$");
  if(regex.find(element))
    {
    tag = "_";
    tag.append(regex.match(1));
    return true;
    }

  // Test for basic integer type
  regex.compile("^[ \t]*(unsigned[ ]|signed[ ])?[ \t]*(char|short|int|long|long[ ]long)[ \t]*$");
  if(regex.find(element))
    {
    tag = "_";
    if(regex.match(1) == "unsigned ")
      { tag.append("u"); }
    if(regex.match(2) == "long long")
      { tag.append("llong"); }
    else
      { tag.append(regex.match(2)); }
    return true;
    }

  // Test for basic floating-point type
  regex.compile("^[ \t]*(long[ ])?[ \t]*(float|double)[ \t]*$");
  if(regex.find(element))
    {
    tag = "_";
    if(regex.match(1) == "long ")
      tag.append("l");
    tag.append(regex.match(2));
    return true;
    }

  // Test for basic wide-character type
  regex.compile("^[ \t]*(wchar_t)[ \t]*$");
  if(regex.find(element))
    {
    tag = "_wchar";
    return true;
    }

  // Test for plain type name (without template arguments).
  regex.compile("^[ \t]*([A-Za-z_][A-Za-z0-9_]*)[ \t]*$");
  if(regex.find(element))
    {
    // The tag is the same as the type.
    tag = regex.match(1);
    return true;
    }
  
  // Test for template class instance.
  regex.compile("^[ \t]*([A-Za-z_][A-Za-z0-9_]*)<.*[ \t]*$");
  if(regex.find(element))
    {
    // The tag is the type without arguments (the arguments may have
    // their own tags).
    tag = regex.match(1);
    return true;
    }
  
  // We can't generate a tag.
  std::string err =
    ("doesn't know how to generate tag for element \""+element+"\" in set \""
     +m_SetName+"\"\nPlease specify one with the \"tag:element\" syntax.");
  this->SetError(err.c_str());

  tag = "";
  
  return false;
}


/**
 * Add a source file associated with this set.  Any package referencing
 * this set will automatically include this source file.
 */
bool cmCableDefineSetCommand::AddSourceFile(const std::string& file)
{
  // We must locate the file in the include path so that we can detect
  // its extension, and whether there is more than one to find.
  std::string header = file+".h";
  std::string txx = file+".txx";
  m_Makefile->ExpandVariablesInString(header);
  m_Makefile->ExpandVariablesInString(txx);
      
  // See if the file just exists here.  The compiler's search path will
  // locate it.
  if(cmSystemTools::FileExists(header.c_str()))
    {
    m_SourceHeaders.push_back(header);
    // See if there is a matching .txx as well.
    if(cmSystemTools::FileExists(txx.c_str()))
      {
      m_InstantiationSources.push_back(txx);
      }
    return true;
    }
  
  // We must look for the file in the include search path.
  const std::vector<std::string>& includeDirectories =
    m_Makefile->GetIncludeDirectories();
  
  for(std::vector<std::string>::const_iterator dir = includeDirectories.begin();
      dir != includeDirectories.end(); ++dir)
    {
    std::string path = *dir + "/";
    m_Makefile->ExpandVariablesInString(path);
    if(cmSystemTools::FileExists((path+header).c_str()))
      {
      m_SourceHeaders.push_back(header);
      // See if there is a matching .txx as well.
      if(cmSystemTools::FileExists((path+txx).c_str()))
        {
        m_InstantiationSources.push_back(txx);
        }
      return true;
      }
    }

  // We couldn't locate the source file.  Report the error.
  std::string err = "couldn't find source file " + header;
  this->SetError(err.c_str());
  return false;
}