/// \file
/// \author Matthew Allen <fret@memecode.com>
#ifndef __IFTP_H
#define __IFTP_H

#include "lgi/common/Net.h"
#include "lgi/common/DateTime.h"
#include "lgi/common/StringClass.h"
#include "lgi/common/Permissions.h"

// Start ftp list parser stuff
struct ftpparse
{
	char *name;			/* not necessarily 0-terminated */
	int namelen;
	int flagtrycwd;		/* 0 if cwd is definitely pointless, 1 otherwise */
	int flagtryretr;	/* 0 if retr is definitely pointless, 1 otherwise */
	int sizetype;
	long size;			/* number of octets */
	int mtimetype;
	time_t mtime;		/* modification time */
	int idtype;
	char *id;			/* not necessarily 0-terminated */
	int idlen;
	char perms[12];
} ;

#define FTPPARSE_SIZE_UNKNOWN 0
#define FTPPARSE_SIZE_BINARY 1 /* size is the number of octets in TYPE I */
#define FTPPARSE_SIZE_ASCII 2 /* size is the number of octets in TYPE A */

#define FTPPARSE_MTIME_UNKNOWN 0
#define FTPPARSE_MTIME_LOCAL 1 /* time is correct */
#define FTPPARSE_MTIME_REMOTEMINUTE 2 /* time zone and secs are unknown */
#define FTPPARSE_MTIME_REMOTEDAY 3 /* time zone and time of day are unknown */
/*
When a time zone is unknown, it is assumed to be GMT. You may want
to use localtime() for LOCAL times, along with an indication that the
time is correct in the local time zone, and gmtime() for REMOTE* times.
*/

#define FTPPARSE_ID_UNKNOWN 0
#define FTPPARSE_ID_FULL 1 /* unique identifier for files on this FTP server */
// End ftp list parser stuff


#define DefaultFtpCharset	"iso-8859-1"

#define IFTP_DIR			0x01
#define IFTP_SYM_LINK		0x02

// Options
#define OPT_LogOpen			"LogOpen"

// Messages
#define M_CREATE_FOLDER		(M_USER+300)
#define M_RENAME			(M_USER+302)
#define M_PERMISSIONS		(M_USER+303)
#define M_RUN				(M_USER+304)
#define M_OPEN_AS_TEXT		(M_USER+305)
#define M_TRANSFER			(M_USER+306)
#define M_REFRESH			(M_USER+307)
#define M_UP				(M_USER+308)

// Classes
enum FtpOpenStatus
{
	FO_ConnectFailed,
	FO_LoginFailed,
	FO_Error,
	FO_Connected
};

class IFtpCallback
{
public:
	virtual ~IFtpCallback() {}
	
	virtual void OnSocketConnect() = 0;
	virtual int MsgBox(const char *Msg, const char *Title, int Btn = MB_OK) = 0;
	virtual int Alert(const char *Title, const char *Text, const char *Btn1, const char *Btn2 = 0, const char *Btn3 = 0) = 0;
	virtual void Disconnect() = 0;
};

/// Represents a file or folder in the remote system.
class IFtpEntry
{
public:
	int Attributes;
	LPermissions Perms;
	int64 Size;
	LString Name;
	LString Path;
	LString User;
	LString Group;
	LDateTime Date;

	// App specific fields
	void *UserData;
	uint8_t Updated : 1;
	uint8_t Added : 1;
	uint8_t Deleted: 1;
	uint8_t Selected : 1;
	uint8_t Unchanged : 1;

	IFtpEntry();
	IFtpEntry(ftpparse *Fp, const char *Cs);
	IFtpEntry(char *Entry, const char *Cs);
	IFtpEntry(IFtpEntry *Entry);
	virtual ~IFtpEntry();

	IFtpEntry &operator =(const IFtpEntry &e);

	bool IsDir()
	{
		if (Perms.IsWindows)
			return Perms.Win.Folder;
		return (Attributes & IFTP_DIR) != 0;
	}

	bool IsHidden()
	{
		if (Perms.IsWindows)
			return Perms.Win.Hidden;
		if (Name)
			return Name(0) == '.';
		return false;
	}

	bool PermissionsFromStr(const char *s);
};

/// The remote folder system interface.
class IFileProtocol
{
public:
	virtual ~IFileProtocol() {}

	// Properties
	virtual const char *GetCharset() = 0;
	virtual void SetCharset(const char *cs) = 0;
	virtual bool IsForceActive() = 0;
	virtual void IsForceActive(bool i) = 0;
	virtual bool IsLongList() = 0;
	virtual void IsLongList(bool i) = 0;
	virtual bool IsShowHidden() = 0;
	virtual void IsShowHidden(bool i) = 0;
	virtual Progress *GetMeter() = 0;
	virtual void SetMeter(Progress *m) = 0;
	virtual bool GetAuthed() = 0;

	// Data
	virtual LSocketI *Handle() = 0;
	
	// Connection
	virtual FtpOpenStatus Open(LSocketI *S, char *RemoteHost, int Port, char *User, char *Password) = 0;
	virtual bool Close() = 0;
	virtual bool IsOpen() = 0;
	virtual void Noop() = 0;
	virtual void GetHost(LString *Host, int *Port) = 0;

	// Directory
	virtual LString GetDir() = 0;
	virtual bool SetDir(const char *Dir) = 0;
	virtual bool CreateDir(const char *Dir) = 0;
	virtual bool DeleteDir(const char *Dir) = 0;
	virtual bool ListDir(LArray<IFtpEntry*> &Dir) = 0;
	virtual bool UpDir() = 0;
	
	// File
	virtual bool DeleteFile(const char *Remote) = 0;
	virtual bool DownloadFile(const char *Local, IFtpEntry *Remote, bool Binary = true) = 0;
	virtual bool UploadFile(const char *Local, const char *Remote, bool Binary = true) = 0;
	virtual bool RenameFile(const char *From, const char *To) = 0;
	virtual bool SetPerms(const char *File, LPermissions Perms) = 0;
	virtual bool ResumeAt(int64 Pos) = 0;
	virtual void Abort() = 0;
};

/// An implementation of the remote file system interface for FTP connections
class IFtp : public IFileProtocol
{
protected:
	class IFtpPrivate *d;

	/// The command connection
	LAutoPtr<LSocketI> Socket;	// commands

	ssize_t WriteLine(char *Msg = 0);
	ssize_t ReadLine(char *Msg = 0, ssize_t MsgSize = 0);
	bool TransferFile(const char *Local, const char *Remote, int64 RemoteSize, bool Upload, bool Binary);

	// Data connections
	char Ip[64];
	int Port;
	bool PassiveMode;
	bool ForceActive;
	bool LongList;
	bool ShowHidden;
	Progress *Meter;
	bool Authenticated;

	// Socket factory details.
	// FtpSocketFactory SockFactory;
	// void *FactoryParam;

	// State
	int64 RestorePos;
	bool AbortTransfer;
	
	bool SetupData(bool Binary);
	bool ConnectData();
	char *ToFtpCs(const char *s);
	char *FromFtpCs(const char *s);

public:
	/// Construct an FTP protocol handler.
	IFtp(IFtpCallback *cb = NULL);
	virtual ~IFtp();

	/// \returns the current charset
	const char *GetCharset();
	/// Set the charset used for converting ftp listings to local utf
	void SetCharset(const char *cs);
	/// \returns the active connections only setting.
	bool IsForceActive() { return ForceActive; }
	/// Set the active connections only option
	void IsForceActive(bool i) { ForceActive = i; }
	bool IsLongList() { return LongList; }
	void IsLongList(bool i) { LongList = i; }
	bool IsShowHidden() { return ShowHidden; }
	void IsShowHidden(bool i) { ShowHidden = i; }
	Progress *GetMeter() { return Meter; }
	void SetMeter(Progress *m) { Meter = m; }
	bool GetAuthed() { return Authenticated; }
	const char *GetError();

	/// Returns the socket used for the command connection.
	LSocketI *Handle() { return Socket; }
	
	/// Opens a new command connection to a remote server
	FtpOpenStatus Open(LSocketI *S, char *RemoteHost, int Port, char *User, char *Password);
	/// Returns the host and port
	void GetHost(LString *Host, int *Port);
	/// Closes the currently active connection
	bool Close();
	/// \returns true if the connection is open and active
	bool IsOpen();
	void Noop();

	/// \returns the current remote folder.
	LString GetDir();
	/// Sets the current remote folder.
	bool SetDir(const char *Dir);
	/// Create a new sub-folder under the current remote folder.
	bool CreateDir(const char *Dir);
	/// Delete a sub-folder under the current folder.
	bool DeleteDir(const char *Dir);
	/// List the current remote folder contents.
	/// Caller is responsible for freeing the objects in Dir (e.g. use LArray::DeleteObjects).
	bool ListDir(LArray<IFtpEntry*> &Dir);
	/// Move up to the parent remote folder.
	bool UpDir();
	
	/// Delete a file in the current remote folder
	bool DeleteFile(const char *Remote);
	/// Download a file from the current remote folder
	bool DownloadFile(const char *Local, IFtpEntry *Remote, bool Binary = true);
	/// Upload a local file to the current remote folder
	bool UploadFile(const char *Local, const char *Remote, bool Binary = true);
	/// Rename a file or folder in the current remote folder
	bool RenameFile(const char *From, const char *To);
	/// Set the permissions on a file in the current remote folder
	bool SetPerms(const char *File, LPermissions Perms);
	/// Set the resume point before downloading a file
	bool ResumeAt(int64 Pos);
	/// Abort the current transfer
	void Abort() { AbortTransfer = true; }
};

#endif
