
#pragma once

#include "SFTPChan.h"
#include "SSHCli.h"

#include "RFolder.h"

#include "MsgData.h"

class CSFTPFolderSFTPDirectory;
class CSFTPFolderSFTP;

struct CSFTPWaitData
{
	ULONG uMsgID;
	int nType;
};

struct CSFTPWaitDirectoryData;

struct CSFTPWaitConfirm : public CWaitResponseData
{
	inline CSFTPWaitConfirm() : CWaitResponseData(WRD_CONFIRM) { }
	inline CSFTPWaitConfirm(int nWaitType) : CWaitResponseData(nWaitType) { }
	int nResult;
	_StringW strMessage;
};

struct CSFTPWaitAttrData : public CSFTPWaitConfirm
{
	inline CSFTPWaitAttrData() : CSFTPWaitConfirm(WRD_FTPWAITATTR) { fileData.dwMask = 0; }
	~CSFTPWaitAttrData() { if (fileData.dwMask & SSH_FILEXFER_ATTR_EXTENDED) free(fileData.aExAttrs); }
	_StringW strFileName;
	_StringW strRemoteDirectory;
	enum
	{
		// -1: End (used for type 3, and represents the result has come)
		typeEnd = -1,
		// 0: Normal
		typeNormal = 0,
		// 1: Read link
		typeReadLink = 1,
		// 2: Retrieve file list for receive files
		typeRetrieveFile = 2,
		// 3: Real path
		typeRealPath = 3
	};
	char nType;
	union
	{
		CFTPFileItem* pItem;

		//CRecvDirectoryData* pDirData;
	};
	CSFTPWaitDirectoryData* pWaitDir;
	CSFTPFileAttribute fileData;
	union
	{
		// base link-file data (the first link file)
		CFTPFileItem* pLinkFrom;

		HSFTPHANDLE hDirHandle;
	};
};

struct CSFTPSendFileData
{
	bool bForSend;
	_StringW strFile;
	IStream* pStream;
	HSFTPHANDLE hFile;
	ULONGLONG uliOffset;
	FILETIME ftFileTime;
	//CSyncSendRecvFile* pSendFile;
	bool bCanceled;
};

struct CSFTPWaitDirectoryData : public CWaitResponseData
{
	inline CSFTPWaitDirectoryData() : CWaitResponseData(WRD_DIRECTORY) { }
	enum
	{
		stepFinished = 0,
		stepRetrieveHandle = 1,
		stepRetrieveFiles = 2
	};
	char nStep;
	bool bResult;
	HSFTPHANDLE hSFTPHandle;
	DWORD dwReadLinkCount;
	CSFTPFolderSFTPDirectory* pDirectory;
};

struct CSFTPWaitFileHandle : public CSFTPWaitConfirm
{
	inline CSFTPWaitFileHandle() : CSFTPWaitConfirm(WRD_FILEHANDLE) { }
	HSFTPHANDLE hSFTPHandle;
};

struct CSFTPWaitSetStat : public CSFTPWaitConfirm
{
	inline CSFTPWaitSetStat() : CSFTPWaitConfirm(WRD_SETSTAT) { }
	_StringW strFileName;
	//bool* pbResult;
};

////////////////////////////////////////////////////////////////////////////////

class CSFTPFolderSFTPDirectory : public CFTPDirectoryBase
{
public:
	CSFTPFolderSFTPDirectory(CDelegateMallocData* pMallocData,
		CFTPDirectoryItem* pItemMe,
		CFTPDirectoryBase* pParent,
		CFTPDirectoryRootBase* pRoot,
		LPCWSTR lpszDirectory);
	virtual ~CSFTPFolderSFTPDirectory();

public:
	STDMETHOD(CreateInstance)(CFTPDirectoryItem* pItemMe, CFTPDirectoryBase* pParent, CFTPDirectoryRootBase* pRoot,
		LPCWSTR lpszDirectory, CFTPDirectoryBase** ppResult);
	STDMETHOD_(void, UpdateItem)(CFTPFileItem* pOldItem, LPCWSTR lpszNewItem, LONG lEvent);

protected:
	CSFTPFolderSFTPDirectory(CDelegateMallocData* pMallocData, CFTPDirectoryItem* pItemMe);
};

class CSFTPFolderSFTP : public CFTPDirectoryRootBase,
	public CSFTPChannelListener,
	public CFingerPrintHandler
{
public:
	friend class CSFTPFolderSFTPDirectory;

	CSFTPFolderSFTP(CDelegateMallocData* pMallocData, CFTPDirectoryItem* pItemMe, CEasySFTPFolderRoot* pFolderRoot);
	virtual ~CSFTPFolderSFTP();

	// IUnknown
public:
	STDMETHOD(QueryInterface)(REFIID riid, void** ppv) { return CFTPDirectoryRootBase::QueryInterface(riid, ppv); }
	STDMETHOD_(ULONG, AddRef)() { return CFTPDirectoryRootBase::AddRef(); }
	STDMETHOD_(ULONG, Release)() { return CFTPDirectoryRootBase::Release(); }

#ifdef _DEBUG
	STDMETHOD(CompareIDs)(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2)
	{
		return CFTPDirectoryRootBase::CompareIDs(lParam, pidl1, pidl2);
	}
#endif

	// CFTPDirectoryRootBase
public:
	STDMETHOD(GetFTPItemUIObjectOf)(HWND hWndOwner, CFTPDirectoryBase* pDirectory,
		const CMyPtrArrayT<CFTPFileItem>& aItems, CFTPDataObject** ppObject);
	STDMETHOD(SetFTPItemNameOf)(HWND hWnd, CFTPDirectoryBase* pDirectory,
		CFTPFileItem* pItem, LPCWSTR pszName, SHGDNF uFlags);
	STDMETHOD(DoDeleteFTPItems)(HWND hWndOwner, CFTPDirectoryBase* pDirectory,
		const CMyPtrArrayT<CFTPFileItem>& aItems);
	STDMETHOD(MoveFTPItems)(HWND hWndOwner, CFTPDirectoryBase* pDirectory, LPCWSTR lpszFromDir, LPCWSTR lpszFileNames);
	STDMETHOD(UpdateFTPItemAttributes)(HWND hWndOwner, CFTPDirectoryBase* pDirectory,
		CServerFilePropertyDialog* pDialog, const CMyPtrArrayT<CServerFileAttrData>& aAttrs, bool* pabResults);
	STDMETHOD(CreateFTPDirectory)(HWND hWndOwner, CFTPDirectoryBase* pDirectory, LPCWSTR lpszName);
	STDMETHOD(CreateShortcut)(HWND hWndOwner, CFTPDirectoryBase* pDirectory, LPCWSTR lpszName, LPCWSTR lpszLinkTo, bool bHardLink);
	STDMETHOD(CreateFTPItemStream)(CFTPDirectoryBase* pDirectory, CFTPFileItem* pItem, IStream** ppStream);
	STDMETHOD(WriteFTPItem)(HWND hWndOwner, CFTPDirectoryBase* pDirectory, LPCWSTR lpszName, IStream* pStream,
		void* pvObject, CTransferStatus* pStatus);
	STDMETHOD(CreateInstance)(CFTPDirectoryItem* pItemMe, CFTPDirectoryBase* pParent, CFTPDirectoryRootBase* pRoot,
		LPCWSTR lpszDirectory, CFTPDirectoryBase** ppResult);

	STDMETHOD(Disconnect)();
	STDMETHOD(IsConnected)() { return m_pClient != NULL ? S_OK : S_FALSE; }
	STDMETHOD(IsTransferring)() { return m_dwTransferringCount > 0 ? S_OK : S_FALSE; }
	STDMETHOD_(IShellFolder*, GetParentFolder)() { return m_pFolderRoot; }

	virtual LPCWSTR GetProtocolName(int& nDefPort) const { nDefPort = 22; return L"sftp"; }
	virtual void PreShowPropertyDialog(CServerFilePropertyDialog* pDialog);
	// pDialog may be NULL
	// return: whether the protocol supports creation of shortcut (link)
	virtual bool PreShowCreateShortcutDialog(CLinkDialog* pDialog);
	virtual void PreShowServerInfoDialog(CServerInfoDialog* pDialog);
	virtual bool ReceiveDirectory(HWND hWndOwner, CFTPDirectoryBase* pDirectory, LPCWSTR lpszDirectory, bool* pbReceived);
	virtual bool ValidateDirectory(LPCWSTR lpszParentDirectory, PCUIDLIST_RELATIVE pidlChild,
		_StringW& rstrRealPath);
	virtual CFTPFileItem* RetrieveFileItem(CFTPDirectoryBase* pDirectory, LPCWSTR lpszFileName);

	// CSFTPChannelListener
public:
	virtual DWORD ChannelGetServerCompatible()
		{ return m_pClient ? m_pClient->m_socket.GetServerCompatible() : 0; }
	virtual bool ChannelSendPacket(BYTE bType, const void* pData, size_t nSize)
		{ return m_pClient ? m_pClient->m_socket.SendPacket(bType, pData, nSize) : false; }
	virtual void ChannelOpenFailure(CSSH2Channel* pChannel, int nReason, const _StringW& strMessage);
	virtual void ChannelOpened(CSSH2Channel* pChannel);
	virtual void ChannelClosed(CSSH2Channel* pChannel);
	virtual void ChannelExitStatus(CSSH2Channel* pChannel, int nExitCode);
	virtual void ChannelConfirm(CSSH2Channel* pChannel, bool bSucceeded);
	virtual void ChannelDataReceived(CSSH2Channel* pChannel, const void* pvData, size_t nSize)
		{ }
	virtual void SFTPOpened(CSFTPChannel* pChannel);
	virtual void SFTPConfirm(CSFTPChannel* pChannel, CSFTPMessage* pMsg,
		int nStatus, const _StringW& strMessage);
	virtual void SFTPFileHandle(CSFTPChannel* pChannel, CSFTPMessage* pMsg,
		HSFTPHANDLE hSFTP);
	virtual void SFTPReceiveFileName(CSFTPChannel* pChannel, CSFTPMessage* pMsg,
		const CSFTPFileData* aFiles, int nCount);
	virtual void SFTPReceiveData(CSFTPChannel* pChannel, CSFTPMessage* pMsg,
		const void* pvData, size_t nLen, const bool* pbEOF);
	virtual void SFTPReceiveAttributes(CSFTPChannel* pChannel, CSFTPMessage* pMsg,
		const CSFTPFileAttribute& attrs);
	virtual void SFTPReceiveStatVFS(CSFTPChannel* pChannel, CSFTPMessage* pMsg,
		const struct sftp_statvfs& statvfs);

	// CFingerPrintHandler
public:
	virtual bool __stdcall CheckFingerPrint(const BYTE* pFingerPrint, size_t nLen);

public:
	bool Connect(HWND hWnd, LPCWSTR lpszHostName, int nPort, CUserInfo* pUser);
	//void Disconnect();

public:
	//_StringW m_strHostName;
	//int m_nPort;
	char m_nServerCharset;

	bool m_bMyUserInfo;
	CUserInfo* m_pUser;
	CSSH2Client* m_pClient;
	bool m_bFirstReceive;
	bool m_bFirstFollowKex;
	bool m_bFirstAuthenticate;
	bool m_bLoggedIn;
	CSFTPChannel* m_pChannel;
	//ULONG m_uDirMsg;
	//_StringW m_strReceivingDirPath;
	//HSFTPHANDLE m_hDirFile;
	CMyKeyList<CWaitResponseData*, ULONG> m_listWaitResponse;
	//CMyKeyList<CSFTPSendFileData*, ULONG> m_listSFTPSendFile;
	//CRITICAL_SECTION m_csListSendFile;
	DWORD m_dwTransferringCount;
	//CRITICAL_SECTION m_csTransferringCount;
	//UINT m_uSFTPDirChangeMsgID;
	//UINT m_uSFTPCreateEditLabelMsgID;

	void IncrementTransferCount();
	void DecrementTransferCount();

protected:
	//ULONG m_uRef;
	CEasySFTPFolderRoot* m_pFolderRoot;

	HWND m_hWndOwner;
	UINT_PTR m_idTimer;
	CRITICAL_SECTION m_csSocket;
	CRITICAL_SECTION m_csReceive;
	static void CALLBACK KeepConnectionTimerProc(UINT_PTR idEvent, LPARAM lParam);
	void OnSFTPSocketReceive();
	// return 0 for success, non-zero for failure (if its value is not (UINT) -1, it means message id)
	UINT _OnSFTPSocketReceiveThreadUnsafe();
	void DoReceiveSocket();
	inline int DoRetryAuthentication(const char* pszAuthList, bool bFirstAttempt)
		{ return m_pFolderRoot->DoRetryAuthentication(m_hWndOwner, m_pUser, true, pszAuthList, bFirstAttempt); }
	void DoNextReadDirectory(CSFTPWaitDirectoryData* pData);

	class CSFTPStreamCounter : public IUnknown
	{
	public:
		inline CSFTPFolderSFTP* This() const
			{ return (CSFTPFolderSFTP*) (((DWORD_PTR) this) - (DWORD_PTR) offsetof(CSFTPFolderSFTP, m_xStreamCounter)); }
		STDMETHOD(QueryInterface)(REFIID riid, void FAR* FAR* ppv) { return E_NOINTERFACE; }
		STDMETHOD_(ULONG, AddRef)()
		{
			This()->IncrementTransferCount();
			return 2;
		}
		STDMETHOD_(ULONG, Release)()
		{
			This()->DecrementTransferCount();
			return 1;
		}
	} m_xStreamCounter;
};
