#include "xentax.h"

void GetModulePathname(LPSTR pathname, DWORD maxlen)
{
 // get filename
 char filename[MAX_PATH];
 GetModuleFileNameA(NULL, filename, MAX_PATH);

 // make path from filename
 char c_drive[1024];
 char c_path[1024];
 _splitpath(filename, c_drive, c_path, nullptr, nullptr);
 string temp(c_drive);
 temp += c_path;

 // set pathname
 strcpy_s(&pathname[0], maxlen, temp.c_str());
}

std::string GetShortFilename(const std::string& filename)
{
 // validate
 if(!filename.length()) return std::string();

 // extract components
 char c_param1[MAX_PATH];
 char c_param2[MAX_PATH];
 char c_param3[MAX_PATH];
 char c_param4[MAX_PATH];

 // extract components
 c_param1[0] = '\0';
 c_param2[0] = '\0';
 c_param3[0] = '\0';
 c_param4[0] = '\0';
 _splitpath(filename.c_str(), c_param1, c_param2, c_param3, c_param4); 

 // return name + extension
 std::string retval(c_param3);
 retval += c_param4;
 return retval;
}

std::string GetShortFilenameWithoutExtension(const std::string& filename)
{
 // validate
 if(!filename.length()) return std::string();

 // extract components
 char c_param1[MAX_PATH];
 char c_param2[MAX_PATH];
 char c_param3[MAX_PATH];
 char c_param4[MAX_PATH];

 // extract components
 c_param1[0] = '\0';
 c_param2[0] = '\0';
 c_param3[0] = '\0';
 c_param4[0] = '\0';
 _splitpath(filename.c_str(), c_param1, c_param2, c_param3, c_param4); 

 // return name + extension
 std::string retval(c_param3);
 return retval;
}

std::string GetPathnameFromFilename(const std::string& filename)
{
 // validate
 if(!filename.length()) return std::string();

 // extract components
 char c_param1[MAX_PATH];
 char c_param2[MAX_PATH];
 char c_param3[MAX_PATH];
 char c_param4[MAX_PATH];

 // extract components
 c_param1[0] = '\0';
 c_param2[0] = '\0';
 c_param3[0] = '\0';
 c_param4[0] = '\0';
 _splitpath(filename.c_str(), c_param1, c_param2, c_param3, c_param4); 

 // return name + extension
 std::string retval(c_param1);
 retval += c_param2;
 return retval;
}

std::string GetExtensionFromFilename(const std::string& filename)
{
 // validate
 if(!filename.length()) return std::string();

 // extract components
 char c_param1[MAX_PATH];
 char c_param2[MAX_PATH];
 char c_param3[MAX_PATH];
 char c_param4[MAX_PATH];

 // extract components
 c_param1[0] = '\0';
 c_param2[0] = '\0';
 c_param3[0] = '\0';
 c_param4[0] = '\0';
 _splitpath(filename.c_str(), c_param1, c_param2, c_param3, c_param4); 

 // return name + extension
 strlwr(c_param4);
 return std::string(c_param4);
}

bool BuildFilenameList(std::deque<std::string>& namelist, const char* fext)
{
 // get path of application
 char c_rootname[MAX_PATH];
 GetModuleFileNameA(0, c_rootname, MAX_PATH);

 // split the pathname
 char c_drive[1024];
 char c_dir[1024];
 _splitpath(c_rootname, c_drive, c_dir, nullptr, nullptr);
 strlwr(c_drive);
 strlwr(c_dir);

 // build pathname
 std::string s_rootname(c_drive);
 s_rootname += std::string(c_dir);

 // build directory list
 std::deque<std::string> templist;
 templist.insert(templist.end(), s_rootname);

 size_t i = 0;
 for(;;)
    {
     // get wildcard
     find_file file;
     std::string path(templist[i]);
     path += "*.*";
     file.find(path.c_str());
     if(!file) return error("Could not build wildcard path.");

     // insert directories
     if(file.is_directory() && !file.is_dots() && !file.is_system()) {
        std::string path(templist[i]);
        path += std::string(file.filename());
        path += std::string("\\");
        templist.insert(templist.end(), path);
       }

     while(file.next()) {
        if(file.is_directory() && !file.is_dots() && !file.is_system()) {
           std::string path(templist[i]);
           path += std::string(file.filename());
           path += std::string("\\");
           templist.insert(templist.end(), path);
          }
       }

     file.close();

     // update index
     if(++i == templist.size())
        break;
    }

 // transfer directories with EXT files
 std::deque<std::string> rootlist;
 for(size_t i = 0; i < templist.size(); i++)
    {
     find_file file;
     bool added = false;
     std::string path(templist[i]);
     path += "*";
     path += ".";
     path += fext;
     if(file.find(path.c_str()) && !file.is_directory()) {
        rootlist.insert(rootlist.end(), templist[i]);
        added = true;
       }
    }

 // erase the temporary list
 templist.erase(templist.begin(), templist.end());
 if(rootlist.empty()) return error("No files found in current directory or subdirectories.");

 // build namelist
 namelist.clear();
 for(size_t i = 0; i < rootlist.size(); i++)
    {
     // clear templist
     find_file file;
     templist.erase(templist.begin(), templist.end());

     // add EXT files
     std::string path(rootlist[i]);
     path += "*";
     path += ".";
     path += fext;
     if(file.find(path.c_str())) {
        std::string temp(rootlist[i]);
        temp += file.filename();
        templist.insert(templist.end(), temp);
       }
     while(file.next()) {
        std::string temp(rootlist[i]);
        temp += file.filename();
        templist.insert(templist.end(), temp);
       }

     // sort temporary list and append to the namelist
     if(templist.size()) {
        std::sort(templist.begin(), templist.end());
        namelist.insert(namelist.end(), templist.begin(), templist.end());
       }
    }

 // must have something in namelist
 templist.erase(templist.begin(), templist.end());
 if(!namelist.size()) return error("No files found in root directory or subdirectories.");

 return true;
}

bool BuildFilenameList(std::deque<std::string>& namelist, const char* fext, const char* rootname)
{
 // get path of application
 char c_rootname[MAX_PATH];
 memmove(c_rootname, rootname, strlen(rootname) + 1);

 // add "." to extension
 std::string extension;
 if(fext[0] != '.') extension += ".";
 extension += fext;

 // split the pathname
 char c_drive[1024];
 char c_dir[1024];
 _splitpath(c_rootname, c_drive, c_dir, nullptr, nullptr);
 strlwr(c_drive);
 strlwr(c_dir);

 // build pathname
 std::string s_rootname(c_drive);
 s_rootname += std::string(c_dir);

 // build directory list
 std::deque<std::string> templist;
 templist.insert(templist.end(), s_rootname);

 size_t i = 0;
 for(;;)
    {
     // get wildcard
     find_file file;
     std::string path(templist[i]);
     path += "*.*";
     file.find(path.c_str());
     if(!file) return error("Could not build wildcard path.");

     // insert directories
     if(file.is_directory() && !file.is_dots() && !file.is_system()) {
        std::string path(templist[i]);
        path += std::string(file.filename());
        path += std::string("\\");
        templist.insert(templist.end(), path);
       }

     while(file.next()) {
        if(file.is_directory() && !file.is_dots() && !file.is_system()) {
           std::string path(templist[i]);
           path += std::string(file.filename());
           path += std::string("\\");
           templist.insert(templist.end(), path);
          }
       }

     file.close();

     // update index
     if(++i == templist.size())
        break;
    }

 // transfer directories with EXT files
 std::deque<std::string> rootlist;
 for(size_t i = 0; i < templist.size(); i++)
    {
     find_file file;
     bool added = false;
     std::string path(templist[i]);
     path += "*";
     path += extension;
     if(file.find(path.c_str()) && !file.is_directory()) {
        rootlist.insert(rootlist.end(), templist[i]);
        added = true;
       }
    }

 // erase the temporary list
 templist.erase(templist.begin(), templist.end());
 if(rootlist.empty()) return error("No files found in current directory or subdirectories.");

 // build namelist
 namelist.clear();
 for(size_t i = 0; i < rootlist.size(); i++)
    {
     // clear templist
     find_file file;
     templist.erase(templist.begin(), templist.end());

     // add files
     std::string path(rootlist[i]);
     path += "*";
     path += extension;
     if(file.find(path.c_str())) {
        std::string temp(rootlist[i]);
        temp += file.filename();
        if(!file.is_directory())
           if(!strcmpi(extension.c_str(), GetExtensionFromFilename(temp).c_str()))
              templist.insert(templist.end(), temp);
       }
     while(file.next()) {
        std::string temp(rootlist[i]);
        temp += file.filename();
        if(!file.is_directory())
           if(!strcmpi(extension.c_str(), GetExtensionFromFilename(temp).c_str()))
              templist.insert(templist.end(), temp);
       }

     // sort temporary list and append to the namelist
     if(templist.size()) {
        std::sort(templist.begin(), templist.end());
        namelist.insert(namelist.end(), templist.begin(), templist.end());
       }
    }

 // must have something in namelist
 templist.erase(templist.begin(), templist.end());
 if(!namelist.size()) return error("No files found in root directory or subdirectories.");

 return true;
}

bool SearchFileForSignature(std::ifstream& ifile, const char* signature, size_t sigsize, std::deque<uint64>& offsets)
{
 // validation
 if(!ifile.is_open()) return false;
 if(!signature || !sigsize) return false;
 if(offsets.size()) offsets.clear();

 // get filesize
 ifile.seekg(0, ios::end);
 uint64 filesize = ifile.tellg();
 ifile.seekg(0, ios::beg);

 // read buffer properties
 const uint32 maxbuffersize = 32*1024*1024;
 uint32 buffersize = (filesize < (uint64)maxbuffersize ? (uint32)filesize : maxbuffersize);
 boost::shared_array<char> buffer(new char[buffersize]);

 // boyer-moore-horspool properties
 boost::shared_array<unsigned char> table(new unsigned char[256]);
 for(uint32 i = 0; i < 256; i++) table[i] = sigsize;
 uint32 p_len = sigsize;
 const unsigned char* p_str = reinterpret_cast<const unsigned char*>(signature);
 for(uint32 i = 0; i < p_len - 1; i++) table[p_str[i]] = p_len - i - 1;

 // search properties
 uint64 bytes_read = 0;
 uint64 bytes_left = filesize;
 uint64 start = 0;

 // search
 while(bytes_read < filesize)
      {
       // search partial buffer
       if(bytes_left < buffersize)
         {
          // read data
          ifile.read(buffer.get(), bytes_left);
          if(ifile.fail()) return false;

          // resume search?

          // boyer-moore-horspool properties
          uint32 s_len = (uint32)bytes_left;
          const unsigned char* s_str = reinterpret_cast<const unsigned char*>(buffer.get());          

          // boyer-moore-horspool search
          size_t j = 0;
          while(j <= (s_len - p_len)) {
                unsigned char c = s_str[j + p_len - 1];
                if(p_str[p_len - 1] == c && memcmp(p_str, s_str + j, p_len - 1) == 0) offsets.push_back(bytes_read + j);
                j += table[c];
               }

          // adjust search properties
          bytes_read = bytes_read + bytes_left;
          bytes_left = 0;
         }
       // search full buffer
       else
         {
          // read data
          ifile.read(buffer.get(), buffersize);
          if(ifile.fail()) return false;

          // resume search?

          // boyer-moore-horspool properties
          size_t s_len = buffersize;
          const unsigned char* s_str = reinterpret_cast<const unsigned char*>(buffer.get());          

          // boyer-moore-horspool search
          size_t j = 0;
          while(j <= (s_len - p_len)) {
                unsigned char c = s_str[j + p_len - 1];
                if(p_str[p_len - 1] == c && memcmp(p_str, s_str + j, p_len - 1) == 0) offsets.push_back(bytes_read + j);
                j += table[c];
               }

          // adjust search properties
          bytes_read = bytes_read + buffersize;
          bytes_left = bytes_left - buffersize;
         }
      }

 return true;
}

bool SearchFileForSignature(std::ifstream& ifile, const char* signature, size_t sigsize, size_t alignment, std::deque<uint64>& offsets)
{
 // validation
 if(!ifile.is_open()) return false;
 if(!signature || !sigsize) return false;
 if(offsets.size()) offsets.clear();

 // get filesize
 ifile.seekg(0, ios::end);
 uint64 filesize = ifile.tellg();
 ifile.seekg(0, ios::beg);

 // read buffer properties
 const uint32 maxbuffersize = 32*1024*1024;
 uint32 buffersize = (filesize < (uint64)maxbuffersize ? (uint32)filesize : maxbuffersize);
 boost::shared_array<char> buffer(new char[buffersize]);

 // boyer-moore-horspool properties
 boost::shared_array<unsigned char> table(new unsigned char[256]);
 for(uint32 i = 0; i < 256; i++) table[i] = sigsize;
 uint32 p_len = sigsize;
 const unsigned char* p_str = reinterpret_cast<const unsigned char*>(signature);
 for(uint32 i = 0; i < p_len - 1; i++) table[p_str[i]] = p_len - i - 1;

 // search properties
 uint64 bytes_read = 0;
 uint64 bytes_left = filesize;
 uint64 start = 0;

 // search
 while(bytes_read < filesize)
      {
       // search partial buffer
       if(bytes_left < buffersize)
         {
          // read data
          ifile.read(buffer.get(), bytes_left);
          if(ifile.fail()) return false;

          // resume search?

          // boyer-moore-horspool properties
          uint32 s_len = (uint32)bytes_left;
          const unsigned char* s_str = reinterpret_cast<const unsigned char*>(buffer.get());          

          // boyer-moore-horspool search
          size_t j = 0;
          while(j <= (s_len - p_len)) {
                unsigned char c = s_str[j + p_len - 1];
                if(p_str[p_len - 1] == c && memcmp(p_str, s_str + j, p_len - 1) == 0)
                   if(!((bytes_read + j) % alignment)) offsets.push_back(bytes_read + j);
                j += table[c];
               }

          // adjust search properties
          bytes_read = bytes_read + bytes_left;
          bytes_left = 0;
         }
       // search full buffer
       else
         {
          // read data
          ifile.read(buffer.get(), buffersize);
          if(ifile.fail()) return false;

          // resume search?

          // boyer-moore-horspool properties
          size_t s_len = buffersize;
          const unsigned char* s_str = reinterpret_cast<const unsigned char*>(buffer.get());          

          // boyer-moore-horspool search
          size_t j = 0;
          while(j <= (s_len - p_len)) {
                unsigned char c = s_str[j + p_len - 1];
                if(p_str[p_len - 1] == c && memcmp(p_str, s_str + j, p_len - 1) == 0)
                  if(!((bytes_read + j) % alignment)) offsets.push_back(bytes_read + j);
                j += table[c];
               }

          // adjust search properties
          bytes_read = bytes_read + buffersize;
          bytes_left = bytes_left - buffersize;
         }
      }

 return true;
}