/* The original version of this program can be found at http://damb.dk */
#include <iostream>
#include <windows.h>
#include <wininet.h>
#include <string>
#include <sstream>
#include <fstream>

// --- This section could be put into some header file.

class FtpInternetConnectionClass;

// This class is a file object, it's created with OpenFile in the connection
class FtpFileClass
{
public:
   ~FtpFileClass();
   bool Write(const char *aStr);
   // Read a string until '\n'
   bool Read(std::string &aStr);
private:
   FtpFileClass(); // Not to be implemented
   friend class FtpConnectionClass;
   // Only FtpConnectionClass can create me
   FtpFileClass(FtpConnectionClass *aConnection);
   bool Open(const char *aFileName, DWORD aAccess, DWORD aFlags);
   HINTERNET HFile;
   FtpConnectionClass *Connection;
};

// A directory listing class, created by calling CreateDirList in the connection
class FtpDirListClass
{
public:
   ~FtpDirListClass();
   std::string GetFileName() { return FindData.cFileName; }
   bool Next() { return InternetFindNextFile(Handle, &FindData); }
private:
   friend class FtpConnectionClass;
   FtpDirListClass(); // Not to be implemented
   // Only FtpConnectionClass can create me
   FtpDirListClass(FtpConnectionClass *aConnection);
   bool Open(const char *aPath);
   HINTERNET Handle;
   WIN32_FIND_DATA FindData;
   FtpConnectionClass *Connection;
};

// The Connection, create one to have a ftp connection.
class FtpConnectionClass
{
public:
   FtpConnectionClass() : HConnection(0), Busy(false)
   {}
   // Call Connect to open a connection.
   bool Connect(const char *aServerName, const char *aUserName = 0, const char *aPassWord = 0);

   enum FileAccessType
   {
      FileAccessRead = 1,
      FileAccessWrite = 2
   };
   enum FileModeType
   {
      FileModeBinary = 4,
      FileModeAcsii  = 8
   };
   // Note: Only one operation can be pending at a time, due to limitations in the ftp protocol,
   // if you need more than one operation at a time, create another connection.

   // Call OpenFile to open a file
   FtpFileClass *OpenFile(const char *aFileName, FileAccessType aFileAccess, FileModeType aFileMode);

   // Call CreateDirList to be able to get a dir list.
   FtpDirListClass *CreateDirList(const char *aFile);

   // Get the current directory on the ftp server
   bool GetCurrentDirectory(std::string &aDir);
   // Change dir on the ftp server
   bool SetCurrentDirectory(const char *aDir);

   // Get a file from the ftp server
   bool GetFile(const char *aFileName);

   // Transfer a file to the ftp server
   bool PutFile(const char *aFileName);
private:
   HINTERNET GetHandle() { return HConnection; }
   friend class FtpFileClass;
   friend class FtpDirListClass;
   void Done() { Busy = false; }
   HINTERNET HConnection;
   bool Busy;
   static HINTERNET HInternet;
};

// --- A test application
int main()
{
   FtpConnectionClass FtpConnection; // The Connection

   if(!FtpConnection.Connect("ftp.microsoft.com", 0, 0))
   {
      std::cerr << "Failed to open connection" << std::endl;
      return 1;
   }
   // List all files on the server
   FtpDirListClass *DirList = FtpConnection.CreateDirList("*");
   if(!DirList)
   {
      std::cerr << "Failed to create dir list" << std::endl;
      return 6;
   }
   do
   {
      std::cout << DirList->GetFileName() << std::endl;
   }
   while(DirList->Next());
   delete DirList;

   // Create a file to write
   FtpFileClass *File = FtpConnection.OpenFile("abc.txt", FtpConnectionClass::FileAccessWrite, FtpConnectionClass::FileModeBinary);
   if(!File)
   {
      std::cerr << "Failed to open file" << std::endl;
      return 2;
   }
   // Write a string to it
   if(!File->Write("Hello Worldrn"))
   {
      std::cerr << "Failed to write" << std::endl;
      return 3;
   }
   // Close the file again
   delete File;
   // Now create a file to read
   File = FtpConnection.OpenFile("abc.txt", FtpConnectionClass::FileAccessRead, FtpConnectionClass::FileModeBinary);
   if(!File)
   {
      std::cerr << "Failed to open file" << std::endl;
      return 4;
   }
   std::string S;
   if(!File->Read(S))
   {
      std::cerr << "Failed to write" << std::endl;
      return 5;
   }
   std::cout << "Read: " << S << std::endl;
   delete File;

   // Get the file we just wrote
   if(!FtpConnection.GetFile("abc.txt"))
   {
      std::cerr << "Failed to get file" << std::endl;
      return 7;
   }
   // Create a local file
   std::ofstream of("abc2.txt");
   of << "Some text" << std::endl;
   of.close();
   // And upload it to the ftp server
   if(!FtpConnection.PutFile("abc2.txt"))
   {
      std::cerr << "Failed to put file" << std::endl;
      return 8;
   }

   // Get and set directory
   if(!FtpConnection.GetCurrentDirectory(S))
   {
      std::cerr << "Failed to get current directory" << std::endl;
      return 9;
   }
   std::cout << "Current Dir: " << S << std::endl;
   if(!FtpConnection.SetCurrentDirectory("new"))
   {
      std::cerr << "Failed to set current directory" << std::endl;
      return 10;
   }
   return 0;
}

// --- The rest is implementation of the ftp class'es, should be put into some .cpp file
HINTERNET FtpConnectionClass::HInternet;

FtpFileClass::FtpFileClass(FtpConnectionClass *aConnection) : Connection(aConnection)
{
}

bool FtpFileClass::Open(const char *aFileName, DWORD aAccess, DWORD aFlags)
{
   HFile = FtpOpenFile(Connection->GetHandle(), aFileName, aAccess, aFlags, 0);
   return HFile != 0;
}

FtpFileClass::~FtpFileClass()
{
   InternetCloseHandle(HFile);
   Connection->Done();
}

FtpDirListClass::~FtpDirListClass()
{
   InternetCloseHandle(Handle);
   Connection->Done();
}

FtpDirListClass::FtpDirListClass(FtpConnectionClass *aConnection) : Connection(aConnection)
{
}

bool FtpDirListClass::Open(const char *aPath)
{
   Handle = FtpFindFirstFile(Connection->GetHandle(), aPath, &FindData, INTERNET_FLAG_RELOAD, 0);
   return Handle != 0;
}

bool FtpFileClass::Write(const char *aStr)
{
   DWORD Dummy;
   return InternetWriteFile(HFile, aStr, strlen(aStr), &Dummy);
}

bool FtpFileClass::Read(std::string &aStr)
{
   aStr = "";
   char Data[2];
   Data[1] = 0;
   DWORD Dummy;
   while(InternetReadFile(HFile, Data, 1, &Dummy) && Dummy)
   {
      aStr += Data;
      if(Data[0] == '\n')
         return true;
   }
   return !aStr.empty();
}


bool FtpConnectionClass::Connect(const char *aServerName, const char *aUserName, const char *aPassWord)
{
   if(!HInternet)
   {
      HInternet = InternetOpen("MyApp", INTERNET_OPEN_TYPE_PROXY, 0, 0, 0);
      if(!HInternet)
      {
         return false;
      }
   }
   if(HConnection)
   {
      InternetCloseHandle(HConnection);
   }
   HConnection = InternetConnect(HInternet, aServerName, INTERNET_DEFAULT_FTP_PORT, aUserName, aPassWord, INTERNET_SERVICE_FTP, 0, 1);
   return HConnection != 0;
}

FtpFileClass *FtpConnectionClass:: OpenFile(const char *aFileName, FileAccessType aFileAccess, FileModeType aFileMode)
{
   if(!HConnection)
   {
      return 0;
   }
   if(Busy)
   {
      return 0;
   }
   FtpFileClass *FtpFile = new FtpFileClass(this);
   if(!FtpFile->Open(aFileName,
                     aFileAccess == FileAccessRead ? GENERIC_READ : GENERIC_WRITE,
                     aFileMode == FileModeBinary ? FTP_TRANSFER_TYPE_BINARY  : FTP_TRANSFER_TYPE_ASCII))
   {
      delete FtpFile;
      return 0;
   }
   Busy = true;
   return FtpFile;
}

FtpDirListClass *FtpConnectionClass::CreateDirList(const char *aFile)
{
   if(!HConnection)
   {
      return 0;
   }
   if(Busy)
   {
      return 0;
   }
   FtpDirListClass *FtpDirList = new FtpDirListClass(this);
   if(!FtpDirList->Open(aFile))
   {
      delete FtpDirList;
      return 0;
   }
   Busy = true;
   return FtpDirList;
}

bool FtpConnectionClass::GetCurrentDirectory(std::string &aDir)
{
   char Name[MAX_PATH];
   DWORD Size = MAX_PATH;
   if(!FtpGetCurrentDirectory(HConnection, Name, &Size))
   {
      return false;
   }
   aDir = Name;
   return true;
}

bool FtpConnectionClass::SetCurrentDirectory(const char *aDir)
{
   return FtpSetCurrentDirectory(HConnection, aDir);
}

bool FtpConnectionClass::GetFile(const char *aFileName)
{
   return FtpGetFile(HConnection, aFileName, aFileName, false, FILE_ATTRIBUTE_NORMAL, FTP_TRANSFER_TYPE_BINARY, 0);
}

bool FtpConnectionClass::PutFile(const char *aFileName)
{
   return FtpPutFile(HConnection, aFileName, aFileName, FTP_TRANSFER_TYPE_BINARY, 0);
}

// --- EOF