#include "cmOrderLinkDirectories.h"
#include "cmSystemTools.h"
#include "cmsys/RegularExpression.hxx"
#include <ctype.h>

//#define CM_ORDER_LINK_DIRECTORIES_DEBUG

//-------------------------------------------------------------------
cmOrderLinkDirectories::cmOrderLinkDirectories()
{
  this->StartLinkType = LinkUnknown;
  this->LinkTypeEnabled = false;
  this->Debug = false;
}

//-------------------------------------------------------------------
void
cmOrderLinkDirectories
::SetLinkTypeInformation(LinkType start_link_type,
                         const char* static_link_type_flag,
                         const char* shared_link_type_flag)
{
  // We can support link type switching only if all needed flags are
  // known.
  this->StartLinkType = start_link_type;
  if(static_link_type_flag && *static_link_type_flag &&
     shared_link_type_flag && *shared_link_type_flag)
    {
    this->LinkTypeEnabled = true;
    this->StaticLinkTypeFlag = static_link_type_flag;
    this->SharedLinkTypeFlag = shared_link_type_flag;
    }
  else
    {
    this->LinkTypeEnabled = false;
    this->StaticLinkTypeFlag = "";
    this->SharedLinkTypeFlag = "";
    }
}

//-------------------------------------------------------------------
void cmOrderLinkDirectories::SetCurrentLinkType(LinkType lt)
{
  if(this->CurrentLinkType != lt)
    {
    this->CurrentLinkType = lt;

    if(this->LinkTypeEnabled)
      {
      switch(this->CurrentLinkType)
        {
        case LinkStatic:
          this->LinkItems.push_back(this->StaticLinkTypeFlag); break;
        case LinkShared:
          this->LinkItems.push_back(this->SharedLinkTypeFlag); break;
        default: break;
        }
      }
    }
}

//-------------------------------------------------------------------
bool cmOrderLinkDirectories::LibraryInDirectory(const char* desiredLib,
                                                const char* dir,
                                                const char* libIn)
{
  // first look for the library as given
  if(this->LibraryMayConflict(desiredLib, dir, libIn))
    {
    return true;
    }
  // next remove the extension (.a, .so ) and look for the library
  // under a different name as the linker can do either
  if(this->RemoveLibraryExtension.find(libIn))
    {
    cmStdString lib = this->RemoveLibraryExtension.match(1);
    cmStdString ext = this->RemoveLibraryExtension.match(2);
    for(std::vector<cmStdString>::iterator i = this->LinkExtensions.begin();
        i != this->LinkExtensions.end(); ++i)
      {
      if(ext != *i)
        {
        std::string fname = lib;
        lib += *i;
        if(this->LibraryMayConflict(desiredLib, dir, fname.c_str()))
          {
          return true;
          } 
        }
      }
    }
  return false;
}

//-------------------------------------------------------------------
void cmOrderLinkDirectories::FindLibrariesInSearchPaths()
{
  for(std::set<cmStdString>::iterator dir = this->LinkPathSet.begin();
      dir != this->LinkPathSet.end(); ++dir)
    {
    for(std::map<cmStdString, Library>::iterator lib
          = this->FullPathLibraries.begin();
        lib != this->FullPathLibraries.end(); ++lib)
      {
      if(lib->second.Path != *dir)
        {
        if(this->LibraryInDirectory(lib->second.FullPath.c_str(),
                                    dir->c_str(), lib->second.File.c_str()))
          {
          this->LibraryToDirectories[lib->second.FullPath].push_back(*dir);
          }
        }
      }
    }
}
                 
//-------------------------------------------------------------------
void cmOrderLinkDirectories::FindIndividualLibraryOrders()
{
  for(std::vector<Library>::iterator lib = 
        this->MultiDirectoryLibraries.begin();
      lib != this->MultiDirectoryLibraries.end(); ++lib)
    {
    std::vector<cmStdString>& dirs = 
      this->LibraryToDirectories[lib->FullPath];
    std::vector<std::pair<cmStdString, std::vector<cmStdString> > 
      >::iterator i;
    for(i = this->DirectoryToAfterList.begin(); 
        i != this->DirectoryToAfterList.end(); ++i)
      {
      if(i->first == lib->Path)
        {
        break;
        }
      }
    if(i == this->DirectoryToAfterList.end())
      {
      std::cerr << "ERROR: should not happen\n";
      }
    else
      {
      for(std::vector<cmStdString>::iterator d = dirs.begin(); 
          d != dirs.end(); ++d)
        {
        i->second.push_back(*d);
        }
      }
    }
}

//-------------------------------------------------------------------
std::string cmOrderLinkDirectories::NoCaseExpression(const char* str)
{
  std::string ret;
  const char* s = str;
  while(*s)
    {
    if(*s == '.')
      {
      ret += *s;
      }
    else
      {
      ret += "[";
      ret += tolower(*s);
      ret += toupper(*s);
      ret += "]";
      }
    s++;
    }
  return ret;
}
    
//-------------------------------------------------------------------
void cmOrderLinkDirectories::CreateRegularExpressions()
{
  this->SplitFramework.compile("(.*)/(.*)\\.framework$");

  // Compute a regex to match link extensions.
  cmStdString libext = this->CreateExtensionRegex(this->LinkExtensions);

  // Create regex to remove any library extension.
  cmStdString reg("(.*)");
  reg += libext;
  this->RemoveLibraryExtension.compile(reg.c_str());

  // Create a regex to match a library name.  Match index 1 will be
  // the prefix if it exists and empty otherwise.  Match index 2 will
  // be the library name.  Match index 3 will be the library
  // extension.
  reg = "^(";
  for(std::set<cmStdString>::iterator p = this->LinkPrefixes.begin();
      p != this->LinkPrefixes.end(); ++p)
    {
    reg += *p;
    reg += "|";
    }
  reg += ")";
  reg += "([^/]*)";

  // Create a regex to match any library name.
  cmStdString reg_any = reg;
  reg_any += libext;
#ifdef CM_ORDER_LINK_DIRECTORIES_DEBUG
  fprintf(stderr, "any regex [%s]\n", reg_any.c_str());
#endif
  this->ExtractAnyLibraryName.compile(reg_any.c_str());

  // Create a regex to match static library names.
  if(!this->StaticLinkExtensions.empty())
    {
    cmStdString reg_static = reg;
    reg_static += this->CreateExtensionRegex(this->StaticLinkExtensions);
#ifdef CM_ORDER_LINK_DIRECTORIES_DEBUG
  fprintf(stderr, "static regex [%s]\n", reg_static.c_str());
#endif
    this->ExtractStaticLibraryName.compile(reg_static.c_str());
    }

  // Create a regex to match shared library names.
  if(!this->SharedLinkExtensions.empty())
    {
    cmStdString reg_shared = reg;
    reg_shared += this->CreateExtensionRegex(this->SharedLinkExtensions);
#ifdef CM_ORDER_LINK_DIRECTORIES_DEBUG
  fprintf(stderr, "shared regex [%s]\n", reg_shared.c_str());
#endif
    this->ExtractSharedLibraryName.compile(reg_shared.c_str());
    }
}

//-------------------------------------------------------------------
std::string
cmOrderLinkDirectories::CreateExtensionRegex(
  std::vector<cmStdString> const& exts)
{
  // Build a list of extension choices.
  cmStdString libext = "(";
  const char* sep = "";
  for(std::vector<cmStdString>::const_iterator i = exts.begin();
      i != exts.end(); ++i)
    {
    // Separate this choice from the previous one.
    libext += sep;
    sep = "|";

    // Store this extension choice with the "." escaped.
    libext += "\\";
#if defined(_WIN32) && !defined(__CYGWIN__)
    libext += this->NoCaseExpression(i->c_str());
#else
    libext += *i;
#endif
    }

  // Finish the list.
  libext += ").*";
  return libext;
}

//-------------------------------------------------------------------
void cmOrderLinkDirectories::PrepareLinkTargets()
{
  std::vector<cmStdString> originalLinkItems = this->LinkItems;
  this->LinkItems.clear();
  this->CurrentLinkType = this->StartLinkType;
  for(std::vector<cmStdString>::iterator i = originalLinkItems.begin();
      i != originalLinkItems.end(); ++i)
    {
    // Parse out the prefix, base, and suffix components of the
    // library name.  If the name matches that of a shared or static
    // library then set the link type accordingly.
    //
    // Search for shared library names first because some platforms
    // have shared libraries with names that match the static library
    // pattern.  For example cygwin and msys use the convention
    // libfoo.dll.a for import libraries and libfoo.a for static
    // libraries.  On AIX a library with the name libfoo.a can be
    // shared!
    if(this->ExtractSharedLibraryName.find(*i))
      {
#ifdef CM_ORDER_LINK_DIRECTORIES_DEBUG
      fprintf(stderr, "shared regex matched [%s] [%s] [%s]\n",
              this->ExtractSharedLibraryName.match(1).c_str(),
              this->ExtractSharedLibraryName.match(2).c_str(),
              this->ExtractSharedLibraryName.match(3).c_str());
#endif
      this->SetCurrentLinkType(LinkShared);
      this->LinkItems.push_back(this->ExtractSharedLibraryName.match(2));
      }
    else if(this->ExtractStaticLibraryName.find(*i))
      {
#ifdef CM_ORDER_LINK_DIRECTORIES_DEBUG
      fprintf(stderr, "static regex matched [%s] [%s] [%s]\n",
              this->ExtractStaticLibraryName.match(1).c_str(),
              this->ExtractStaticLibraryName.match(2).c_str(),
              this->ExtractStaticLibraryName.match(3).c_str());
#endif
      this->SetCurrentLinkType(LinkStatic);
      this->LinkItems.push_back(this->ExtractStaticLibraryName.match(2));
      }
    else if(this->ExtractAnyLibraryName.find(*i))
      {
#ifdef CM_ORDER_LINK_DIRECTORIES_DEBUG
      fprintf(stderr, "any regex matched [%s] [%s] [%s]\n",
              this->ExtractAnyLibraryName.match(1).c_str(),
              this->ExtractAnyLibraryName.match(2).c_str(),
              this->ExtractAnyLibraryName.match(3).c_str());
#endif
      this->SetCurrentLinkType(this->StartLinkType);
      this->LinkItems.push_back(this->ExtractAnyLibraryName.match(2));
      }
    else
      {
      this->SetCurrentLinkType(this->StartLinkType);
      this->LinkItems.push_back(*i);
      }
    }

  // Restore the original linking type so system runtime libraries are
  // linked properly.
  this->SetCurrentLinkType(this->StartLinkType);
}

//-------------------------------------------------------------------
bool cmOrderLinkDirectories::FindPathNotInDirectoryToAfterList(
  cmStdString& path)
{   
  for(std::vector<std::pair<cmStdString, std::vector<cmStdString> > 
        >::iterator i = this->DirectoryToAfterList.begin();
      i != this->DirectoryToAfterList.end(); ++i)
    {
    const cmStdString& p = i->first;
    bool found = false;
    for(std::vector<std::pair<cmStdString, std::vector<cmStdString> > 
          >::iterator j = this->DirectoryToAfterList.begin(); 
        j != this->DirectoryToAfterList.end() && !found; ++j)
      {
      if(j != i)
        {
        found = (std::find(j->second.begin(), j->second.end(), p) 
                 != j->second.end());
        }
      }
    if(!found)
      {
      path = p;
      this->DirectoryToAfterList.erase(i);
      return true;
      }
    }
  path = "";
  return false;
}


//-------------------------------------------------------------------
void cmOrderLinkDirectories::OrderPaths(std::vector<cmStdString>&
                                        orderedPaths)
{
  cmStdString path;
  // This is a topological sort implementation
  // One at a time find paths that are not in any other paths after list
  // and put them into the orderedPaths vector in that order
  // FindPathNotInDirectoryToAfterList removes the path from the
  // this->DirectoryToAfterList once it is found
  while(this->FindPathNotInDirectoryToAfterList(path))
    {
    orderedPaths.push_back(path);
    }
  // at this point if there are still paths in this->DirectoryToAfterList
  // then there is a cycle and we are stuck
  if(this->DirectoryToAfterList.size())
    {
    for(std::vector<std::pair<cmStdString, std::vector<cmStdString> > 
          >::iterator i = this->DirectoryToAfterList.begin();
        i != this->DirectoryToAfterList.end(); ++i)
      {
      this->ImpossibleDirectories.insert(i->first);
      // still put it in the path list in the order we find them
      orderedPaths.push_back(i->first);
      }
    
    }
}

//-------------------------------------------------------------------
void cmOrderLinkDirectories::SetLinkInformation(
  const char* targetName,
  const std::vector<std::string>& linkLibraries,
  const std::vector<std::string>& linkDirectories,
  const cmTargetManifest& manifest,
  const char* configSubdir
  )
{
  // Save the target name.
  this->TargetName = targetName;

  // Save the subdirectory used for linking in this configuration.
  this->ConfigSubdir = configSubdir? configSubdir : "";

  // Merge the link directory search path given into our path set.
  std::vector<cmStdString> empty;
  for(std::vector<std::string>::const_iterator p = linkDirectories.begin();
      p != linkDirectories.end(); ++p)
    {
    std::string dir = *p;
#ifdef _WIN32
    // Avoid case problems for windows paths.
    if(dir.size() > 2 && dir[1] == ':')
      {
      if(dir[0] >= 'A' && dir[0] <= 'Z')
        {
        dir[0] += 'a' - 'A';
        }
      }
    dir = cmSystemTools::GetActualCaseForPath(dir.c_str());
#endif
    if(this->DirectoryToAfterListEmitted.insert(dir).second)
      {
      std::pair<cmStdString, std::vector<cmStdString> > dp;
      dp.first = dir;
      this->DirectoryToAfterList.push_back(dp);
      this->LinkPathSet.insert(dir);
      }
    }

  // Append the link library list into our raw list.
  for(std::vector<std::string>::const_iterator l = linkLibraries.begin();
      l != linkLibraries.end(); ++l)
    {
    this->RawLinkItems.push_back(*l);
    }

  // Construct a set of files that will exist after building.
  for(cmTargetManifest::const_iterator i = manifest.begin();
      i != manifest.end(); ++i)
    {
    for(cmTargetSet::const_iterator j = i->second.begin();
        j != i->second.end(); ++j)
      {
      this->ManifestFiles.insert(*j);
      }
    }
}

//-------------------------------------------------------------------
bool cmOrderLinkDirectories::DetermineLibraryPathOrder()
{
  // set up all the regular expressions
  this->CreateRegularExpressions();
  std::vector<cmStdString> finalOrderPaths;
  // find all libs that are full paths
  Library aLib;
  cmStdString dir;
  cmStdString file;
  std::vector<cmStdString> empty;
  // do not add a -F for the system frameworks
  this->EmittedFrameworkPaths.insert("/System/Library/Frameworks");
  for(unsigned int i=0; i < this->RawLinkItems.size(); ++i)
    {
    bool framework = false;
#ifdef CM_ORDER_LINK_DIRECTORIES_DEBUG
    fprintf(stderr, "Raw link item [%s]\n", this->RawLinkItems[i].c_str());
#endif
    // if it is a full path to an item then separate it from the path
    // this only works with files and paths
    cmStdString& item = this->RawLinkItems[i];
    if(cmSystemTools::FileIsFullPath(item.c_str()))
      {
      if(cmSystemTools::FileIsDirectory(item.c_str()))
        {
        if(cmSystemTools::IsPathToFramework(item.c_str()))
          {
          this->SplitFramework.find(item.c_str());
          cmStdString path = this->SplitFramework.match(1);
          // Add the -F path if we have not yet done so
          if(this->EmittedFrameworkPaths.insert(path).second)
            {
            std::string fpath = "-F";
            fpath += cmSystemTools::ConvertToOutputPath(path.c_str());
            this->LinkItems.push_back(fpath);
            }
          // now add the -framework option
          std::string frame = "-framework ";
          frame += this->SplitFramework.match(2);
          this->LinkItems.push_back(frame);
          framework = true;
          }
        else
          {
          // A full path to a directory was found as a link item
          // warn user 
          std::string message = 
            "Warning: Ignoring path found in link libraries for target: ";
          message += this->TargetName;
          message += ", path is: ";
          message += this->RawLinkItems[i];
          message += 
            ". Expected a library name or a full path to a library name.";
          cmSystemTools::Message(message.c_str());
          continue;
          }
        } // is it a directory
      if(!framework)
        {
        dir = cmSystemTools::GetFilenamePath(this->RawLinkItems[i]);
        file = cmSystemTools::GetFilenameName(this->RawLinkItems[i]);
#ifdef _WIN32
        // Avoid case problems for windows paths.
        if(dir.size() > 2 && dir[1] == ':')
          {
          if(dir[0] >= 'A' && dir[0] <= 'Z')
            {
            dir[0] += 'a' - 'A';
            }
          }
        dir = cmSystemTools::GetActualCaseForPath(dir.c_str());
#endif
        if(this->DirectoryToAfterListEmitted.insert(dir).second)
          {
          std::pair<cmStdString, std::vector<cmStdString> > dp;
          dp.first = dir;
          this->DirectoryToAfterList.push_back(dp);
          }
        this->LinkPathSet.insert(dir);
        aLib.FullPath = this->RawLinkItems[i];
        aLib.File = file;
        aLib.Path = dir;
        this->FullPathLibraries[aLib.FullPath] = aLib;
#ifdef CM_ORDER_LINK_DIRECTORIES_DEBUG
        fprintf(stderr, "Storing item [%s]\n", file.c_str());
#endif
        this->LinkItems.push_back(file);
        }
      }
    else
      {
      this->LinkItems.push_back(this->RawLinkItems[i]);
      }
    }
  this->FindLibrariesInSearchPaths();
  for(std::map<cmStdString, std::vector<cmStdString> >::iterator lib =
        this->LibraryToDirectories.begin(); 
      lib!= this->LibraryToDirectories.end(); 
      ++lib)
    {
    if(lib->second.size() > 0)
      {
      this->MultiDirectoryLibraries.push_back
        (this->FullPathLibraries[lib->first]);
      }
    else
      {
      this->SingleDirectoryLibraries.push_back
        (this->FullPathLibraries[lib->first]);
      }
    }
  this->FindIndividualLibraryOrders();
  this->SortedSearchPaths.clear();
  if(this->Debug)
    {
    this->PrintMap("this->LibraryToDirectories", this->LibraryToDirectories);
    this->PrintVector("this->DirectoryToAfterList", 
                      this->DirectoryToAfterList);
    }
  this->OrderPaths(this->SortedSearchPaths); 
  // now turn libfoo.a into foo and foo.a into foo
  // This will prepare the link items for -litem 
  this->PrepareLinkTargets();
  if(this->ImpossibleDirectories.size())
    {
    cmSystemTools::Message(this->GetWarnings().c_str());
    return false;
    }
  return true;
}

std::string cmOrderLinkDirectories::GetWarnings()
{
  std::string warning = 
    "It is impossible to order the linker search path in such a way "
    "that libraries specified as full paths will be picked by the "
    "linker.\nDirectories and libraries involved are:\n";

  for(std::set<cmStdString>::iterator i = this->ImpossibleDirectories.begin();
      i != this->ImpossibleDirectories.end(); ++i)
    {
    warning += "Directory: ";
    warning += *i;
    warning += " contains:\n";
    std::map<cmStdString, std::vector<cmStdString> >::iterator j;
    for(j = this->LibraryToDirectories.begin(); 
        j != this->LibraryToDirectories.end(); ++j)
      {
      if(std::find(j->second.begin(), j->second.end(), *i)
         != j->second.end())
        {
        warning += "Library: ";
        warning += j->first;
        warning += "\n";
        }
      }
    warning += "\n";
    }
  warning += "\n";
  return warning;
}

//-------------------------------------------------------------------
void
cmOrderLinkDirectories::PrintMap(const char* name,
                       std::map<cmStdString, std::vector<cmStdString> >& m)
{
  std::cout << name << "\n";
  for(std::map<cmStdString, std::vector<cmStdString> >::iterator i =
        m.begin(); i != m.end();
      ++i)
    {
    std::cout << i->first << ":  ";
    for(std::vector<cmStdString>::iterator l = i->second.begin();
        l != i->second.end(); ++l)
      {
      std::cout << *l << " ";
      }
    std::cout << "\n";
    }
}
//-------------------------------------------------------------------
void
cmOrderLinkDirectories::PrintVector(const char* name,
                                    std::vector<std::pair<cmStdString, 
                                    std::vector<cmStdString> > >& m)
{
  std::cout << name << "\n";
  for(std::vector<std::pair<cmStdString, std::vector<cmStdString> > 
        >::iterator i = m.begin(); i != m.end(); ++i)
    {
    std::cout << i->first << ":  ";
    for(std::vector<cmStdString>::iterator l = i->second.begin();
        l != i->second.end(); ++l)
      {
      std::cout << *l << " ";
      }
    std::cout << "\n";
    }
}

void cmOrderLinkDirectories::GetFullPathLibraries(std::vector<cmStdString>& 
                                                  libs)
{
  for(std::map<cmStdString, Library>::iterator i = 
        this->FullPathLibraries.begin();
      i != this->FullPathLibraries.end(); ++i)
    {
    libs.push_back(i->first);
    }
  
}

//----------------------------------------------------------------------------
bool cmOrderLinkDirectories::LibraryMayConflict(const char* desiredLib,
                                                const char* dir,
                                                const char* fname)
{
  // We need to check whether the given file may be picked up by the
  // linker.  This will occur if it exists as given or may be built
  // using the name given.
  bool found = false;
  std::string path = dir;
  path += "/";
  path += fname;
  if(this->ManifestFiles.find(path) != this->ManifestFiles.end())
    {
    found = true;
    }
  else if(cmSystemTools::FileExists(path.c_str()))
    {
    found = true;
    }

  // When linking with a multi-configuration build tool the
  // per-configuration subdirectory is added to each link path.  Check
  // this subdirectory too.
  if(!found && !this->ConfigSubdir.empty())
    {
    path = dir;
    path += "/";
    path += this->ConfigSubdir;
    path += "/";
    path += fname;
    if(this->ManifestFiles.find(path) != this->ManifestFiles.end())
      {
      found = true;
      }
    else if(cmSystemTools::FileExists(path.c_str()))
      {
      found = true;
      }
    }

  // A library conflicts if it is found and is not a symlink back to
  // the desired library.
  if(found)
    {
    return !cmSystemTools::SameFile(desiredLib, path.c_str());
    }
  return false;
}
