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


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

//-------------------------------------------------------------------
bool cmOrderLinkDirectories::LibraryInDirectory(const char* dir, 
                                                const char* libIn)
{
  cmStdString path = dir;
  path += "/";
  path += libIn;
  // first look for the library as given
  if(cmSystemTools::FileExists(path.c_str()))
    {
    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)
        {
        path = dir;
        path += "/";
        path += lib + *i;
        if(cmSystemTools::FileExists(path.c_str()))
          {
          return true;
          } 
        }
      }
    }
  return false;
}

//-------------------------------------------------------------------
void cmOrderLinkDirectories::FindLibrariesInSeachPaths()
{
  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(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];
    this->DirectoryToAfterList[lib->Path] = dirs;
    }
}

//-------------------------------------------------------------------
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$");
  cmStdString libext = "(";
  bool first = true;
  for(std::vector<cmStdString>::iterator i = this->LinkExtensions.begin();
      i != this->LinkExtensions.end(); ++i)
    {
    if(!first)
      {
      libext += "|";
      }
    first = false;
    libext += "\\";
#if defined(_WIN32) && !defined(__CYGWIN__)
    libext += this->NoCaseExpression(i->c_str());
#else
    libext += *i;
#endif
    }
  libext += ").*";
  cmStdString reg("(.*)");
  reg += libext;
  this->RemoveLibraryExtension.compile(reg.c_str());
  reg = "";
  if(this->LinkPrefix.size())
    {
    reg = "^";
    reg += this->LinkPrefix;
    }
  reg += "([^/]*)";
  reg += libext;
  this->ExtractBaseLibraryName.compile(reg.c_str());
  reg = "([^/]*)";
  reg += libext;
  this->ExtractBaseLibraryNameNoPrefix.compile(reg.c_str());
}


//-------------------------------------------------------------------
void cmOrderLinkDirectories::PrepareLinkTargets()
{
  for(std::vector<cmStdString>::iterator i = this->LinkItems.begin();
      i != this->LinkItems.end(); ++i)
    {
    // separate the library name from libfoo.a or foo.a
    if(this->ExtractBaseLibraryName.find(*i))
      {
      *i = this->ExtractBaseLibraryName.match(1);
      }
    else if(this->ExtractBaseLibraryNameNoPrefix.find(*i))
      {
      *i = this->ExtractBaseLibraryNameNoPrefix.match(1);
      }
    }
}

//-------------------------------------------------------------------
bool cmOrderLinkDirectories::FindPathNotInDirectoryToAfterList(
  cmStdString& path)
{
  for(std::map<cmStdString, std::vector<cmStdString> >::iterator i
        = this->DirectoryToAfterList.begin();
      i != this->DirectoryToAfterList.end(); ++i)
    {
    const cmStdString& p = i->first;
    bool found = false;
    for(std::map<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::map<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
  )
{
  // Save the target name.
  this->TargetName = targetName;

  // 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)
    {
    this->DirectoryToAfterList[*p] = empty;
    this->LinkPathSet.insert(*p);
    }

  // 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);
    }
}

//-------------------------------------------------------------------
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;
  bool framework = false;
  // 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)
    {
    if(cmSystemTools::FileIsFullPath(this->RawLinkItems[i].c_str()))
      {
      if(cmSystemTools::FileIsDirectory(this->RawLinkItems[i].c_str()))
        {
        if(cmSystemTools::IsPathToFramework(this->RawLinkItems[i].c_str()))
          {
          this->SplitFramework.find(this->RawLinkItems[i]);
          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
          {
          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;
          }
        }
      if(!framework)
        {
        cmSystemTools::SplitProgramPath(this->RawLinkItems[i].c_str(),
                                        dir, file);
#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
        this->DirectoryToAfterList[dir] = empty;
        this->LinkPathSet.insert(dir);
        aLib.FullPath = this->RawLinkItems[i];
        aLib.File = file;
        aLib.Path = dir;
        this->FullPathLibraries[aLib.FullPath] = aLib;
        this->LinkItems.push_back(file);
        }
      }
    else
      {
      this->LinkItems.push_back(this->RawLinkItems[i]);
      }
    }
  this->FindLibrariesInSeachPaths();
  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->PrintMap("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::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);
    }
  
}
