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


//-------------------------------------------------------------------
cmOrderLinkDirectories::cmOrderLinkDirectories()
{
  m_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(m_RemoveLibraryExtension.find(libIn))
    {
    cmStdString lib = m_RemoveLibraryExtension.match(1);
    cmStdString ext = m_RemoveLibraryExtension.match(2);
    for(std::vector<cmStdString>::iterator i = m_LinkExtensions.begin();
        i != m_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 = m_LinkPathSet.begin();
      dir != m_LinkPathSet.end(); ++dir)
    {
    for(std::map<cmStdString, Library>::iterator lib
          = m_FullPathLibraries.begin();
        lib != m_FullPathLibraries.end(); ++lib)
      {
      if(lib->second.Path != *dir)
        {
        if(this->LibraryInDirectory(dir->c_str(), lib->second.File.c_str()))
          {
          m_LibraryToDirectories[lib->second.FullPath].push_back(*dir);
          }
        }
      }
    }
}
                 
//-------------------------------------------------------------------
void cmOrderLinkDirectories::FindIndividualLibraryOrders()
{
  for(std::vector<Library>::iterator lib = m_MultiDirectoryLibraries.begin();
      lib != m_MultiDirectoryLibraries.end(); ++lib)
    {
    std::vector<cmStdString>& dirs = m_LibraryToDirectories[lib->FullPath];
    m_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()
{
  m_SplitFramework.compile("(.*)/(.*)\\.framework$");
  cmStdString libext = "(";
  bool first = true;
  for(std::vector<cmStdString>::iterator i = m_LinkExtensions.begin();
      i != m_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;
  m_RemoveLibraryExtension.compile(reg.c_str());
  reg = "";
  if(m_LinkPrefix.size())
    {
    reg = "^";
    reg += m_LinkPrefix;
    }
  reg += "([^/]*)";
  reg += libext;
  m_ExtractBaseLibraryName.compile(reg.c_str());
  reg = "([^/]*)";
  reg += libext;
  m_ExtractBaseLibraryNameNoPrefix.compile(reg.c_str());
}


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

//-------------------------------------------------------------------
bool cmOrderLinkDirectories::FindPathNotInDirectoryToAfterList(
  cmStdString& path)
{
  for(std::map<cmStdString, std::vector<cmStdString> >::iterator i
        = m_DirectoryToAfterList.begin();
      i != m_DirectoryToAfterList.end(); ++i)
    {
    const cmStdString& p = i->first;
    bool found = false;
    for(std::map<cmStdString, std::vector<cmStdString> >::iterator j 
          = m_DirectoryToAfterList.begin(); j != m_DirectoryToAfterList.end() 
          && !found; ++j)
      {
      if(j != i)
        {
        found = (std::find(j->second.begin(), j->second.end(), p) != j->second.end());
        }
      }
    if(!found)
      {
      path = p;
      m_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
  // m_DirectoryToAfterList once it is found
  while(this->FindPathNotInDirectoryToAfterList(path))
    {
    orderedPaths.push_back(path);
    }
  // at this point if there are still paths in m_DirectoryToAfterList
  // then there is a cycle and we are stuck
  if(m_DirectoryToAfterList.size())
    {
    for(std::map<cmStdString, std::vector<cmStdString> >::iterator i
          = m_DirectoryToAfterList.begin();
        i != m_DirectoryToAfterList.end(); ++i)
      {
      m_ImposibleDirectories.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.
  m_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)
    {
    m_DirectoryToAfterList[*p] = empty;
    m_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)
    {
    m_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;
  for(unsigned int i=0; i < m_RawLinkItems.size(); ++i)
    {
    if(cmSystemTools::FileIsFullPath(m_RawLinkItems[i].c_str()))
      {
      if(cmSystemTools::FileIsDirectory(m_RawLinkItems[i].c_str()))
        {
        if(cmSystemTools::IsPathToFramework(m_RawLinkItems[i].c_str()))
          {
          m_SplitFramework.find(m_RawLinkItems[i]);
          cmStdString path = m_SplitFramework.match(1);
          // Add the -F path if we have not yet done so
          if(m_EmittedFrameworkPaths.insert(path).second)
            {
            std::string fpath = "-F";
            fpath += cmSystemTools::ConvertToOutputPath(path.c_str());
            m_LinkItems.push_back(fpath);
            }
          // now add the -framework option
          std::string frame = "-framework ";
          frame += m_SplitFramework.match(2);
          m_LinkItems.push_back(frame);
          framework = true;
          }
        else
          {
          std::string message = "Warning: Ignoring path found in link libraries for target: ";
          message += m_TargetName;
          message += ", path is: ";
          message += m_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(m_RawLinkItems[i].c_str(),
                                        dir, file);
        m_DirectoryToAfterList[dir] = empty;
        m_LinkPathSet.insert(dir);
        aLib.FullPath = m_RawLinkItems[i];
        aLib.File = file;
        aLib.Path = dir;
        m_FullPathLibraries[aLib.FullPath] = aLib;
        m_LinkItems.push_back(file);
        }
      }
    else
      {
      m_LinkItems.push_back(m_RawLinkItems[i]);
      }
    }
  this->FindLibrariesInSeachPaths();
  for(std::map<cmStdString, std::vector<cmStdString> >::iterator lib =
        m_LibraryToDirectories.begin(); lib!= m_LibraryToDirectories.end(); 
      ++lib)
    {
    if(lib->second.size() > 0)
      {
      m_MultiDirectoryLibraries.push_back(m_FullPathLibraries[lib->first]);
      }
    else
      {
      m_SingleDirectoryLibraries.push_back(m_FullPathLibraries[lib->first]);
      }
    }
  this->FindIndividualLibraryOrders();
  m_SortedSearchPaths.clear();
  if(m_Debug)
    {
    this->PrintMap("m_LibraryToDirectories", m_LibraryToDirectories);
    this->PrintMap("m_DirectoryToAfterList", m_DirectoryToAfterList);
    }
  this->OrderPaths(m_SortedSearchPaths); 
  // now turn libfoo.a into foo and foo.a into foo
  // This will prepare the link items for -litem 
  this->PrepareLinkTargets();
  if(m_ImposibleDirectories.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 = m_ImposibleDirectories.begin();
      i != m_ImposibleDirectories.end(); ++i)
    {
    warning += "Directory: ";
    warning += *i;
    warning += " contains:\n";
    std::map<cmStdString, std::vector<cmStdString> >::iterator j;
    for(j = m_LibraryToDirectories.begin(); 
        j != m_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 = m_FullPathLibraries.begin();
      i != m_FullPathLibraries.end(); ++i)
    {
    libs.push_back(i->first);
    }
  
}
