/* ###
 * IP: GHIDRA
 * NOTE: Calls to Windows APIs
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "filemanage.hh"

#ifdef _WINDOWS
#include <windows.h>

#else
// POSIX functions for searching directories
extern "C" {
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
}
#endif

namespace ghidra {

// Path name separator
#ifdef _WINDOWS
char FileManage::separator = '\\';
char FileManage::separatorClass[] = "/\\";
#else
char FileManage::separator = '/';
char FileManage::separatorClass[] = "/";
#endif

void FileManage::addDir2Path(const string &path)

{
  if (path.size()>0) {
    pathlist.push_back(path);
    if (!isSeparator(path[path.size()-1]))
      pathlist.back() += separator;
  }
}

void FileManage::findFile(string &res,const string &name) const

{				// Search through paths to find file with given name
  vector<string>::const_iterator iter;

  if (isSeparator(name[0])) {
    res = name;
    ifstream s(res.c_str());
    if (s) {
      s.close();
      return;
    }
  }
  else {
    for(iter=pathlist.begin();iter!=pathlist.end();++iter) {
      res = *iter + name;
      ifstream s(res.c_str());
      if (s) {
	s.close();
	return;
      }
    }
  }
  res.clear();			// Can't find it, return empty string
}

#ifdef _WINDOWS
void FileManage::addCurrentDir(void)

{
  char dirname[256];
  
  if (0!=GetCurrentDirectoryA(256,dirname)) {
    string filename(dirname);
    addDir2Path(filename);
  }
}

#else
void FileManage::addCurrentDir(void)

{				// Add current working directory to path
  char dirname[256];
  char *buf;

  buf = getcwd(dirname,256);
  if ((char *)0 == buf) return;
  string filename(buf);
  addDir2Path(filename);
}
#endif

#ifdef _WINDOWS
bool FileManage::isDirectory(const string &path)

{
  DWORD attribs = GetFileAttributes(path.c_str());
  if (attribs == INVALID_FILE_ATTRIBUTES) return false;
  return ((attribs & FILE_ATTRIBUTE_DIRECTORY)!=0);
}

#else
bool FileManage::isDirectory(const string &path)

{
  struct stat buf;
  if (stat(path.c_str(),&buf) < 0) {
    return false;
  }
  return S_ISDIR(buf.st_mode);
}

#endif

#ifdef _WINDOWS
bool FileManage::isSeparator(char c)

{
  return (c == '/' || c == '\\');
}

#else
bool FileManage::isSeparator(char c)

{
  return c == separator;
}

#endif

#ifdef _WINDOWS
void FileManage::matchListDir(vector<string> &res,const string &match,bool isSuffix,const string &dirname,bool allowdot)

{
  WIN32_FIND_DATAA FindFileData;
  HANDLE hFind;
  string dirfinal;

  dirfinal = dirname;
  if (!isSeparator(dirfinal[dirfinal.size()-1]))
    dirfinal += separator;
  string regex = dirfinal + '*';

  hFind = FindFirstFileA(regex.c_str(),&FindFileData);
  if (hFind == INVALID_HANDLE_VALUE) return;
  do {
    string fullname(FindFileData.cFileName);
    if (match.size() <= fullname.size()) {
      if (allowdot||(fullname[0] != '.')) {
	if (isSuffix) {
	  if (0==fullname.compare(fullname.size()-match.size(),match.size(),match))
	    res.push_back(dirfinal + fullname);
	}
	else {
	  if (0==fullname.compare(0,match.size(),match))
	    res.push_back(dirfinal + fullname);
	}
      }
    }
  } while(0!=FindNextFileA(hFind,&FindFileData));
  FindClose(hFind);
}

#else
void FileManage::matchListDir(vector<string> &res,const string &match,bool isSuffix,const string &dirname,bool allowdot)

{				// Look through files in a directory for those matching -match-
  DIR *dir;
  struct dirent *entry;
  string dirfinal = dirname;
  if (!isSeparator(dirfinal[dirfinal.size()-1]))
    dirfinal += separator;

  dir = opendir(dirfinal.c_str());
  if (dir == (DIR *)0) return;
  entry = readdir(dir);
  while(entry != (struct dirent *)0) {
    string fullname(entry->d_name);
    if (match.size() <= fullname.size()) {
      if (allowdot||(fullname[0] != '.')) {
	if (isSuffix) {
	  if (0==fullname.compare( fullname.size()-match.size(),match.size(),match))
	    res.push_back( dirfinal + fullname );
	}
	else {
	  if (0==fullname.compare(0,match.size(),match))
	    res.push_back(dirfinal + fullname);
	}
      }
    }
    entry = readdir(dir);
  }
  closedir(dir);
}
#endif

void FileManage::matchList(vector<string> &res,const string &match,bool isSuffix) const

{
  vector<string>::const_iterator iter;

  for(iter=pathlist.begin();iter!=pathlist.end();++iter)
    matchListDir(res,match,isSuffix,*iter,false);
}

#ifdef _WINDOWS

void FileManage::directoryList(vector<string> &res,const string &dirname,bool allowdot)

{
  WIN32_FIND_DATAA FindFileData;
  HANDLE hFind;
  string dirfinal = dirname;
  if (!isSeparator(dirfinal[dirfinal.size()-1]))
    dirfinal += separator;
  string regex = dirfinal + "*";
  const char *s = regex.c_str();
  

  hFind = FindFirstFileA(s,&FindFileData);
  if (hFind == INVALID_HANDLE_VALUE) return;
  do {
    if ( (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY ) {
      string fullname(FindFileData.cFileName);
      if (allowdot || (fullname[0] != '.'))
	res.push_back(dirfinal + fullname);
    }
  } while(0!=FindNextFileA(hFind,&FindFileData));
  FindClose(hFind);
}

#else
void FileManage::directoryList(vector<string> &res,const string &dirname,bool allowdot)

{ // List full pathnames of all directories under the directory -dir-
  DIR *dir;
  struct dirent *entry;
  string dirfinal;

  dirfinal = dirname;
  if (!isSeparator(dirfinal[dirfinal.size()-1]))
    dirfinal += separator;

  dir = opendir(dirfinal.c_str());
  if (dir == (DIR *)0) return;
  entry = readdir(dir);
  while(entry != (struct dirent *)0) {
    bool isDirectory = false;
    if (entry->d_type == DT_DIR)
      isDirectory = true;
    else if (entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK) {
      string path = dirfinal + entry->d_name;
      struct stat stbuf;
      stat(path.c_str(), &stbuf);
      isDirectory = S_ISDIR(stbuf.st_mode);
    }
    if (isDirectory) {
      string fullname(entry->d_name);
      if ((fullname!=".")&&(fullname!="..")) {
	if (allowdot || (fullname[0] != '.'))
	  res.push_back( dirfinal + fullname );
      }
    }
    entry = readdir(dir);
  }
  closedir(dir);
}

#endif

void FileManage::scanDirectoryRecursive(vector<string> &res,const string &matchname,const string &rootpath,int maxdepth)

{
  if (maxdepth == 0) return;
  vector<string> subdir;
  directoryList(subdir,rootpath);
  vector<string>::const_iterator iter;
  for(iter = subdir.begin();iter!=subdir.end();++iter) {
    const string &curpath( *iter );
    string::size_type pos = curpath.find_last_of(separatorClass);
    if (pos == string::npos)
      pos = 0;
    else
      pos = pos + 1;
    if (curpath.compare(pos,string::npos,matchname)==0)
      res.push_back(curpath);
    else
      scanDirectoryRecursive(res,matchname,curpath,maxdepth-1); // Recurse
  }
}

void FileManage::splitPath(const string &full,string &path,string &base)

{ // Split path string -full- into its -base-name and -path- (relative or absolute)
  // If there is no path, i.e. only a basename in full, then -path- will return as an empty string
  // otherwise -path- will be non-empty and end in a separator character
  string::size_type end = full.size()-1;
  if (isSeparator(full[full.size()-1])) // Take into account terminating separator
    end = full.size()-2;
  string::size_type pos = full.find_last_of(separatorClass,end);
  if (pos == string::npos) {	// Didn't find any separator
    base = full;
    path.clear();
  }
  else {
    string::size_type sz = (end - pos);
    base = full.substr(pos+1,sz);
    path = full.substr(0,pos+1);
  }
}

string FileManage::buildPath(const vector<string> &pathels,int level)

{ // Build an absolute path using elements from -pathels-, in reverse order
  // Build up to and including pathels[level]
  ostringstream s;

  for(int i=pathels.size()-1;i>=level;--i) {
    s << separator;
    s << pathels[i];
  }
  return s.str();
}

bool FileManage::testDevelopmentPath(const vector<string> &pathels,int level,string &root)

{ // Given pathels[level] is "Ghidra", determine if this is a Ghidra development layout
  if (level + 2 >= pathels.size()) return false;
  string parent = pathels[level + 1];
  if (parent.size() < 11) return false;
  string piecestr = parent.substr(0,7);
  if (piecestr != "ghidra.") return false;
  piecestr = parent.substr(parent.size() - 4);
  if (piecestr != ".git") return false;
  root = buildPath(pathels,level+2);
  vector<string> testpaths1;
  vector<string> testpaths2;
  scanDirectoryRecursive(testpaths1,"ghidra.git",root,1);
  if (testpaths1.size() != 1) return false;
  scanDirectoryRecursive(testpaths2,"Ghidra",testpaths1[0],1);
  return (testpaths2.size() == 1);
}

bool FileManage::testInstallPath(const vector<string> &pathels,int level,string &root)

{
  if (level + 1 >= pathels.size()) return false;
  root = buildPath(pathels,level+1);
  vector<string> testpaths1;
  vector<string> testpaths2;
  scanDirectoryRecursive(testpaths1,"server",root,1);
  if (testpaths1.size() != 1) return false;
  scanDirectoryRecursive(testpaths2,"server.conf",testpaths1[0],1);
  return (testpaths2.size() == 1);
}

string FileManage::discoverGhidraRoot(const char *argv0)

{ // Find the root of the ghidra distribution based on current working directory and passed in path
  vector<string> pathels;
  string cur(argv0);
  string base;
  int skiplevel = 0;
  bool isAbs = isAbsolutePath(cur);

  for(;;) {
    int sizebefore = cur.size();
    splitPath(cur,cur,base);
    if (cur.size() == sizebefore) break;
    if (base == ".")
      skiplevel += 1;
    else if (base == "..")
      skiplevel += 2;
    if (skiplevel > 0)
      skiplevel -= 1;
    else
      pathels.push_back(base);
  }
  if (!isAbs) {
    FileManage curdir;
    curdir.addCurrentDir();
    cur = curdir.pathlist[0];
    for(;;) {
      int sizebefore = cur.size();
      splitPath(cur,cur,base);
      if (cur.size() == sizebefore) break;
      pathels.push_back(base);
    }
  }

  for(int i=0;i<pathels.size();++i) {
    if (pathels[i] != "Ghidra") continue;
    string root;
    if (testDevelopmentPath(pathels,i,root))
      return root;
    if (testInstallPath(pathels,i,root))
      return root;
  }
  return "";
}

} // End namespace ghidra
