#include <all_far.h>

#include "Int.h"

/*  1 -       title
	2 -       --- between commands and status
	3 - [OPT] status
	4 - [OPT] --- between status and buttons
			  commands
	5 - [OPT] --- between commands and status
	6 - [OPT] button
	7 - [OPT] button1
	8 - [OPT] button2
	9 - [OPT] button Cancel
*/
#define NBUTTONSADDON 10

//------------------------------------------------------------------------
// DEBUG ONLY
//------------------------------------------------------------------------
#if defined(__FILELOG__)
void LogPanelItems(struct PluginPanelItem *pi, int cn)
{
	Log(("Items in list %p: %d", pi, cn));

	if (pi)
		for (int n = 0; n < cn; n++) {
			Log(("%2d) [%s] attr: %08X (%s)", n + 1, FTP_FILENAME(&pi[n]), pi[n].FindData.dwFileAttributes,
					IS_FLAG(pi[n].FindData.dwFileAttributes, FILE_ATTRIBUTE_DIRECTORY) ? "DIR" : "FILE"));
		}
}
#endif

//------------------------------------------------------------------------
int FARINProc::Counter = 0;

FARINProc::FARINProc(LPCSTR nm, LPCSTR s, ...)
	:
	Name(nm)
{
	va_list ap;
	char str[500];
	DWORD err = WINPORT(GetLastError)();

	if (s) {
		va_start(ap, s);
		snprintf(str, ARRAYSIZE(str), "%*c%s(%s) {", Counter * 2, ' ', nm, MessageV(s, ap));
		va_end(ap);
	} else
		snprintf(str, ARRAYSIZE(str), "%*c%s() {", Counter * 2, ' ', nm);

	LogCmd(str, ldInt);
	Counter++;
	WINPORT(SetLastError)(err);
}

FARINProc::~FARINProc()
{
	DWORD err = WINPORT(GetLastError)();
	char str[500];
	Counter--;
	snprintf(str, ARRAYSIZE(str), "%*c}<%s>", Counter * 2, ' ', Name);
	LogCmd(str, ldInt);
	WINPORT(SetLastError)(err);
}

void _cdecl FARINProc::Say(LPCSTR s, ...)
{
	va_list ap;
	char str[500];
	int rc;
	DWORD err = WINPORT(GetLastError)();
	va_start(ap, s);
	rc = snprintf(str, ARRAYSIZE(str), "%*c", Counter * 2, ' ');

	if (rc < (int)ARRAYSIZE(str))
		vsnprintf(str + rc, ARRAYSIZE(str) - rc, s, ap);

	va_end(ap);
	LogCmd(str, ldInt);
	WINPORT(SetLastError)(err);
}
//------------------------------------------------------------------------
//------------------------------------------------------------------------
char *WINAPI FixFileNameChars(String &fnm, BOOL slashes)
{
	return FixFileNameChars((char *)fnm.c_str(), slashes);
}

char *WINAPI FixFileNameChars(char *fnm, BOOL slashes)
{
	char *m;
	char *src, *inv = Opt.InvalidSymbols, *cor = Opt.CorrectedSymbols;
	static char buff[MAX_PATH];

	if (!inv[0])
		return fnm;

	StrCpy(buff, fnm, ARRAYSIZE(buff));

	for (src = buff; *src; src++)
		if ((m = strchr(inv, *src)) != NULL)
			*src = cor[m - inv];

	/*if(slashes)
		for(src = buff; *src; src++)
			if(*src == '/') *src = '_';
			else if(*src == ':')  *src = '!';*/

	return buff;
}
//------------------------------------------------------------------------
/*
   Create Socket errr string.
   Returns static buffer

   Windows has no way to create string by socket error, so make it inside plugin
*/
#if 0
static struct
{
	int   Code;
	int   MCode;
} stdSockErrors[] =
{
	/* Windows Sockets definitions of regular Microsoft C error constants */
	{ WSAEINTR,            MWSAEINTR },
	{ WSAEBADF,            MWSAEBADF },
	{ WSAEACCES,           MWSAEACCES },
	{ WSAEFAULT,           MWSAEFAULT },
	{ WSAEINVAL,           MWSAEINVAL },
	{ WSAEMFILE,           MWSAEMFILE },
	/* Windows Sockets definitions of regular Berkeley error constants */
	{ WSAEWOULDBLOCK,      MWSAEWOULDBLOCK },
	{ WSAEINPROGRESS,      MWSAEINPROGRESS },
	{ WSAEALREADY,         MWSAEALREADY },
	{ WSAENOTSOCK,         MWSAENOTSOCK },
	{ WSAEDESTADDRREQ,     MWSAEDESTADDRREQ },
	{ WSAEMSGSIZE,         MWSAEMSGSIZE },
	{ WSAEPROTOTYPE,       MWSAEPROTOTYPE },
	{ WSAENOPROTOOPT,      MWSAENOPROTOOPT },
	{ WSAEPROTONOSUPPORT,  MWSAEPROTONOSUPPORT },
	{ WSAESOCKTNOSUPPORT,  MWSAESOCKTNOSUPPORT },
	{ WSAEOPNOTSUPP,       MWSAEOPNOTSUPP },
	{ WSAEPFNOSUPPORT,     MWSAEPFNOSUPPORT },
	{ WSAEAFNOSUPPORT,     MWSAEAFNOSUPPORT },
	{ WSAEADDRINUSE,       MWSAEADDRINUSE },
	{ WSAEADDRNOTAVAIL,    MWSAEADDRNOTAVAIL },
	{ WSAENETDOWN,         MWSAENETDOWN },
	{ WSAENETUNREACH,      MWSAENETUNREACH },
	{ WSAENETRESET,        MWSAENETRESET },
	{ WSAECONNABORTED,     MWSAECONNABORTED },
	{ WSAECONNRESET,       MWSAECONNRESET },
	{ WSAENOBUFS,          MWSAENOBUFS },
	{ WSAEISCONN,          MWSAEISCONN },
	{ WSAENOTCONN,         MWSAENOTCONN },
	{ WSAESHUTDOWN,        MWSAESHUTDOWN },
	{ WSAETOOMANYREFS,     MWSAETOOMANYREFS },
	{ WSAETIMEDOUT,        MWSAETIMEDOUT },
	{ WSAECONNREFUSED,     MWSAECONNREFUSED },
	{ WSAELOOP,            MWSAELOOP },
	{ WSAENAMETOOLONG,     MWSAENAMETOOLONG },
	{ WSAEHOSTDOWN,        MWSAEHOSTDOWN },
	{ WSAEHOSTUNREACH,     MWSAEHOSTUNREACH },
	{ WSAENOTEMPTY,        MWSAENOTEMPTY },
	{ WSAEPROCLIM,         MWSAEPROCLIM },
	{ WSAEUSERS,           MWSAEUSERS },
	{ WSAEDQUOT,           MWSAEDQUOT },
	{ WSAESTALE,           MWSAESTALE },
	{ WSAEREMOTE,          MWSAEREMOTE },
	/* Extended Windows Sockets error constant definitions */
	{ WSASYSNOTREADY,      MWSASYSNOTREADY },
	{ WSAVERNOTSUPPORTED,  MWSAVERNOTSUPPORTED },
	{ WSANOTINITIALISED,   MWSANOTINITIALISED },
	{ WSAEDISCON,          MWSAEDISCON },
	{ WSAHOST_NOT_FOUND,   MWSAHOST_NOT_FOUND },
	{ WSATRY_AGAIN,        MWSATRY_AGAIN },
	{ WSANO_RECOVERY,      MWSANO_RECOVERY },
	{ WSANO_DATA,          MWSANO_DATA },
	{ WSANO_ADDRESS,       MWSANO_ADDRESS },
	{ 0, MNone__ }
};
#endif
LPCSTR WINAPI GetSocketErrorSTR(void)
{
	return GetSocketErrorSTR(errno);
}

LPCSTR WINAPI GetSocketErrorSTR(int err)
{
	static char estr[70];

	if (!err)
		return FP_GetMsg(MWSAENoError);
#if 0
	for(int n = 0; stdSockErrors[n].MCode != MNone__; n++)
		if(stdSockErrors[n].Code == err)
			return FP_GetMsg(stdSockErrors[n].MCode);
#endif
	sprintf(estr, "%s: %d", FP_GetMsg(MWSAEUnknown), err);
	return estr;
}
//------------------------------------------------------------------------
/*
   Procedure for convert digit to string
   Like an AtoI but can manipulate int64_t and has buffer limit

   Align digit to left, if buffer less when digit the output will be
   truncated at right.
   If size set to -1 the whole value will be output to ctring.
*/
char *WINAPI PDigit(char *buff, int64_t val, int sz /*=-1*/)
{
	static char lbuff[100];
	char str[100];
	int pos = ARRAYSIZE(str) - 1;

	if (!buff)
		buff = lbuff;

	str[pos] = 0;

	if (!val)
		str[--pos] = '0';
	else
		for (; pos && val; val/= 10)
			str[--pos] = (char)('0' + val % 10);

	if (sz != -1) {
		if (((int)ARRAYSIZE(str)) - 1 - pos > sz) {
			str[pos + sz - 1] = FAR_RIGHT_CHAR;
		}
	}

	if (pos <= 0)
		str[0] = FAR_LEFT_CHAR;

	StrCpy(buff, str + pos, sz == -1 ? (-1) : (sz + 1));
	return buff;
}
/*
   Output digit to string
   Can delimit thousands by special character

   Fill buffer always with `sz` characters
   If digit less then `sz` in will be added by ' ' at left
   If digit more than buffer digit will be truncated at left
*/
char *WINAPI FDigit(char *buff, int64_t val, int sz)
{
	static char lbuff[100];
	char str[MAX_PATH];
	int len, n, d;
	char *s;

	if (!buff)
		buff = lbuff;

	*buff = 0;

	if (!sz)
		return buff;

	if (!Opt.dDelimit || !Opt.dDelimiter) {
		PDigit(buff, val, sz);
		return buff;
	}

	PDigit(str, val, sz);
	len = (int)strlen(str);
	s = str + len - 1;

	if (sz == -1)
		sz = len + len / 3 - ((len % 3) == 0);

	d = sz;
	buff[d--] = 0;

	for (n = 0; d >= 0 && n < sz && n < len; n++) {
		if (n && (n % 3) == 0) {
			buff[d--] = Opt.dDelimiter;
			sz--;
		}

		if (d >= 0)
			buff[d--] = *(s--);
	}

	if (n > sz)
		buff[0] = FAR_LEFT_CHAR;

	if (d >= 0)
		for (; d >= 0; d--)
			buff[d] = ' ';

	return buff;
}
//------------------------------------------------------------------------
/*
	Show `Message` with attention caption and query user to select YES or NO
	Returns nonzero if user select YES
*/
int WINAPI AskYesNoMessage(LPCSTR LngMsgNum)
{
	static LPCSTR MsgItems[] = {FMSG(MAttention), NULL, FMSG(MYes), FMSG(MNo)};
	MsgItems[1] = LngMsgNum;
	return FMessage(FMSG_WARNING, NULL, MsgItems, ARRAYSIZE(MsgItems), 2);
}

BOOL WINAPI AskYesNo(LPCSTR LngMsgNum)
{
	return AskYesNoMessage(LngMsgNum) == 0;
}
/*
	Show void `Message` with attention caption
*/
void WINAPI SayMsg(LPCSTR LngMsgNum)
{
	LPCSTR MsgItems[] = {FMSG(MAttention), LngMsgNum, FMSG(MOk)};
	FMessage(FMSG_WARNING, NULL, MsgItems, ARRAYSIZE(MsgItems), 1);
}
//------------------------------------------------------------------------
BOOL WINAPI IsCmdLogFile(void)
{
	return Opt.CmdLogFile[0] != 0;
}

LPCSTR WINAPI GetCmdLogFile(void)
{
	static char str[MAX_PATH] = {0};

	if (Opt.CmdLogFile[0]) {
		if (Opt.CmdLogFile[0] == '/')
			return Opt.CmdLogFile;
		else if (str[0])
			return str;
		else {
			strcpy(str, "/var/log");
			//			HMODULE m = GetModuleHandle(FP_GetPluginName());
			//			str[GetModuleFileName(m,str,ARRAYSIZE(str))] = 0;
			//			strrchr(str,'/')[1] = 0;
			StrCat(str, Opt.CmdLogFile, ARRAYSIZE(str));
			return str;
		}
	} else
		return "";
}
/*
   Writes one string to log file

   Start from end if file at open bigger then limit size
   `out` is a direction of string (server, plugin, internal)
*/

static HANDLE LogFile = NULL;

void WINAPI LogCmd(LPCSTR src, CMDOutputDir out, DWORD Size)
{
	LPCSTR m;

	// File opened and fail
	if (LogFile == INVALID_HANDLE_VALUE)
		return;

	// Params
	if (!IsCmdLogFile() || !src)
		return;

	src = FP_GetMsg(src);

	if (out == ldRaw && (!Size || Size == MAX_DWORD))
		return;
	else if (!src[0])
		return;

	// Open file
	// Name
	m = GetCmdLogFile();

	if (!m || !m[0])
		return;

	// Open
	LogFile = Fopen(m, !LogFile && Opt.TruncateLogFile ? "w" : "w+");

	if (!LogFile) {
		LogFile = INVALID_HANDLE_VALUE;
		return;
	}

	// Check limitations
	if (Opt.CmdLogLimit && Fsize(LogFile) >= (int64_t)Opt.CmdLogLimit * 1000)
		Ftrunc(LogFile, FILE_BEGIN);

	//-- USED DATA
	static SYSTEMTIME stOld = {0};
	SYSTEMTIME st;
	char tmstr[100];

	//-- RAW
	if (out == ldRaw) {
		snprintf(tmstr, ARRAYSIZE(tmstr), "%d ", PluginUsed());
		Fwrite(LogFile, tmstr, static_cast<int>(strlen(tmstr)));
		signed n;

		for (n = ((int)Size) - 1; n > 0 && strchr("\n\r", src[n]); n--)
			;

		if (n > 0) {
			Fwrite(LogFile, "--- RAW ---\r\n", 13);
			Fwrite(LogFile, src, Size);
			Fwrite(LogFile, "\r\n--- RAW ---\r\n", 15);
		}

		Fclose(LogFile);
		return;
	}

	//-- TEXT

	// Replace PASW
	if (!Opt._ShowPassword && StrCmp(src, "PASS ", 5, TRUE) == 0)
		src = "PASS *hidden*";

	// Write multiline to log
	do {
		// Plugin
		snprintf(tmstr, ARRAYSIZE(tmstr), "%d ", PluginUsed());
		Fwrite(LogFile, tmstr, static_cast<int>(strlen(tmstr)));
		// Time
		WINPORT(GetLocalTime)(&st);
		snprintf(tmstr, ARRAYSIZE(tmstr), "%4d.%02d.%02d %02d:%02d:%02d:%04d", st.wYear, st.wMonth, st.wDay,
				st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
		Fwrite(LogFile, tmstr, static_cast<int>(strlen(tmstr)));

		// Delay
		if (!stOld.wYear)
			sprintf(tmstr, " ----");
		else
			sprintf(tmstr, " %04d",
					(st.wSecond - stOld.wSecond) * 1000 + (st.wMilliseconds - stOld.wMilliseconds));

		Fwrite(LogFile, tmstr, (int)strlen(tmstr));
		stOld = st;

		// Direction
		if (out == ldInt)
			Fwrite(LogFile, "| ", 2);
		else
			Fwrite(LogFile, (out == ldOut) ? "|->" : "|<-", 3);

		// Message
		for (; *src && !strchr("\r\n", *src); src++)
			Fwrite(LogFile, src, 1);

		Fwrite(LogFile, "\n", 1);

		while (*src && strchr("\r\n", *src))
			src++;
	} while (*src);

	Fclose(LogFile);
}

//------------------------------------------------------------------------
//  Connection
//------------------------------------------------------------------------
void Connection::InitIOBuff(void)
{
	CloseIOBuff();
	IOBuff = (char *)malloc(Host.IOBuffSize + 1);
}
void Connection::CloseIOBuff(void)
{
	free(IOBuff);
	IOBuff = NULL;
}

/*
   Initialize CMD buffer data
*/
void Connection::InitCmdBuff(void)
{
	if (CmdBuff)
		CloseCmdBuff();

	hIdle = Opt.IdleShowPeriod ? FP_PeriodCreate(Opt.IdleShowPeriod) : NULL;
	CmdVisible = TRUE;
	cmdLineSize = Opt.CmdLine;
	cmdSize = Opt.CmdLength;
	RetryCount = 0;
	CmdBuff = (char **)malloc(sizeof(char *) * (cmdSize + 1));
	RplBuff = (char **)malloc(sizeof(char *) * (cmdSize + 1));

	// Command lines (length + 0)
	for (int n = 0; n < cmdSize; n++) {
		CmdBuff[n] = (char *)malloc(cmdLineSize + 1);
		RplBuff[n] = (char *)malloc(1024 + 1);
	}

	// Command lines + status command
	CmdMsg = (LPCSTR *)malloc(sizeof(LPCSTR) * (cmdSize + NBUTTONSADDON));
	ResetCmdBuff();
}
/*
   Free initialize CMD buffer data
*/
void Connection::CloseCmdBuff(void)
{
	if (CmdBuff)
		for (int n = 0; n < cmdSize; n++) {
			free(CmdBuff[n]);
			free(RplBuff[n]);
		}

	free(CmdBuff);
	free(RplBuff);
	free(CmdMsg);
	CmdBuff = NULL;
	RplBuff = NULL;
	CmdMsg = NULL;
	IOBuff = NULL;
	FP_PeriodDestroy(hIdle);
}
//------------------------------------------------------------------------
/*
   Output message about internal plugin error
*/
void Connection::InternalError(void)
{
	LPCSTR MsgItems[] = {FMSG(MFtpTitle), FMSG(MIntError), FMSG(MOk)};
	FMessage(FMSG_WARNING, NULL, MsgItems, ARRAYSIZE(MsgItems), 1);
}
/*
   Set CMD buffer data to zero state

   Used just after new connection established
*/
void Connection::ResetCmdBuff(void)
{
	hostname[0] = 0;
	LastHost[0] = 0;
	LastMsg[0] = 0;
}

//------------------------------------------------------------------------
/*
   Format `src` string to output info CMD window

   dest - buffer of cmd window
   sz   - size of cmd size (string will be grow|truncated to this size)
   out  - signals if this is output or input string

   IF out == -1
	 `src` placed as is - do not resize to `sz`; do not add direction text
*/
void Connection::SetCmdLine(char *dest, LPCSTR src, int sz, int out)
{
	int l, off;
	char *m;

	if (!Opt._ShowPassword && StrCmp(src, Opt.cmdPass, static_cast<int>(strlen(Opt.cmdPass)), TRUE) == 0)
		src = "PASS *hidden*";
	else if (((BYTE)src[0]) == (BYTE)ffDM
			&& (strncmp(src + 1, "ABOR", 4) == 0 || strncmp(src, "ABOR", 4) == 0))
		src = "<ABORT>";

	if (out != -1) {
		dest[0] = out ? '-' : '<';
		dest[1] = out ? '>' : '-';
		dest[2] = ' ';
		off = 3;
	} else
		off = 0;

	sz--;
	StrCpy(dest + off, src, sz - off);

	if ((m = strchr(dest, '\r')) != NULL)
		*m = 0;

	if ((m = strchr(dest, '\n')) != NULL)
		*m = 0;

	if (out != -1) {
		for (l = static_cast<int>(strlen(dest)); l < sz; l++)
			dest[l] = ' ';

		dest[l] = 0;

		/*		if(Host.CodeCmd)
				{
					if(out)
						ToOEM(dest);
					else
						ToOEM(dest);
				}*/
	}
}
/*
   Add a string to `RPL buffer` and `CMD buffer`

   Do not accept empty strings
   Scrolls buffers up if length of beffers bigger then CMD length
   Call ConnectMessage to refresh CMD window

   IF `str` == NULL
	 Place to buffer last response string
*/
void Connection::AddCmdLine(LPCSTR str)
{
	int n;
	char buff[512];

	if (str) {
		str = FP_GetMsg(str);

		// Skip IAC
		if (((BYTE)str[0]) == ffIAC && ((BYTE)str[1]) == ffIP)
			return;

		// Skip empty
		while (*str && strchr("\n\r", *str) != NULL)
			str++;

		if (!str[0])
			return;
	}

	StrCpy(buff, str ? str : reply_string.c_str(), ARRAYSIZE(buff));

	// Remove overflow lines
	for (; cmdCount >= cmdSize; cmdCount--)
		for (n = 1; n < cmdCount; n++) {
			StrCpy(CmdBuff[n - 1], CmdBuff[n], cmdLineSize);
			StrCpy(RplBuff[n - 1], RplBuff[n], 1024);
		}

	// Add new line
	LogCmd(buff, (str != NULL) ? ldOut : ldIn);
	SetCmdLine(CmdBuff[cmdCount], buff, cmdLineSize, str != NULL);
	SetCmdLine(RplBuff[cmdCount], buff, 1024, str != NULL);
	cmdCount++;

	// Start
	if (!StartReply.Length() && !str) {
		LPCSTR m = reply_string.c_str();

		while (*m && (isdigit(*m) || strchr(" \b\t", *m) != NULL))
			m++;

		StartReply = m;
		//		ToOEM(StartReply);
	}

	ConnectMessage();
}
//------------------------------------------------------------------------
/*
	Show CMD window
	Use `CmdBuff` string to write in window
	Returns nonzero if `btn` to specified or user select button from modal dialog

	IF `btn` != MNone__
	  Show modal message with button `btn`
	IF `btn` < 0
	  Show modal message with button Abs(`btn`) and error color of message window
	IF `HostName` != NULL
	  Set LastHost to this value
*/
static WORD Keys[] = {VK_ESCAPE, 'C', 'R', VK_RETURN};

BOOL Connection::ConnectMessageTimeout(int Msg /*= MNone__*/, LPCSTR HostName /*= NULL*/,
		int BtnMsg /*= MNone__*/)
{
	char str[MAX_PATH];
	char host[MAX_PATH];
	BOOL rc, first = TRUE;
	DWORD b, e;
	double diff;
	int secNum;

	if (IS_FLAG(FP_LastOpMode, OPM_FIND) || !Opt.RetryTimeout || (BtnMsg != -MRetry && BtnMsg != -MRestore))
		return ConnectMessage(Msg, HostName, BtnMsg);

	StrCpy(host, LastHost, ARRAYSIZE(host));
	secNum = 0;
	GET_TIME(b);

	do {
		switch (CheckForKeyPressed(Keys, ARRAYSIZE(Keys))) {
			case 1:
			case 2:
				WINPORT(SetLastError)(ERROR_CANCELLED);
				return FALSE;
			case 3:
			case 4:
				return TRUE;
		}

		GET_TIME(e);
		diff = CMP_TIME(e, b);

		if (first || diff > 1.0) {
			first = FALSE;
			snprintf(str, ARRAYSIZE(str), "\"%s\" %s %2d%s %s", HostName, FP_GetMsg(MAutoRetryText),
					Opt.RetryTimeout - secNum, FP_GetMsg(MSeconds), FP_GetMsg(MRetryText));
			ConnectMessage(Msg, str);
			snprintf(str, ARRAYSIZE(str), "%s %s %d%s", FP_GetMsg(Msg), FP_GetMsg(MAutoRetryText),
					Opt.RetryTimeout - secNum, FP_GetMsg(MSeconds));
			SaveConsoleTitle::Text(str);
			secNum++;
			b = e;
		}

		if (secNum > Opt.RetryTimeout) {
			rc = TRUE;
			break;
		}

		WINPORT(Sleep)(100);
	} while (true);

	StrCpy(LastHost, host, ARRAYSIZE(LastHost));
	return rc;
}

int Connection::ConnectMessage(int Msg /*= MNone__*/, LPCSTR HostName /*= NULL*/, int btn /*= MNone__*/,
		int btn1 /*= MNone__*/, int btn2 /*= MNone__*/)
{
	// PROC(( "ConnectMessage", "%d,%s,%d,%d,%d", Msg, HostName, btn, btn1, btn2 ))
	int num, n;
	BOOL res;
	LPCSTR m;
	BOOL exCmd;

	if (btn < 0 && Opt.DoNotExpandErrors)
		exCmd = FALSE;
	else
		exCmd = Host.ExtCmdView;

	// Called for update CMD window but it disabled
	if (Msg == MNone__ && !HostName && !exCmd)
		return TRUE;

	if (Msg != MNone__)
		SetCmdLine(LastMsg, FP_GetMsg(Msg), ARRAYSIZE(LastMsg), -1);

	if (HostName && HostName[0])
		SetCmdLine(LastHost, HostName, ARRAYSIZE(LastHost), -1);

	if (btn == MNone__)
		if (IS_FLAG(FP_LastOpMode, OPM_FIND) ||								// called from find
				!CmdVisible ||												// Window disabled
				(IS_SILENT(FP_LastOpMode) && !Opt.ShowSilentProgress))		// show silent processing disabled
		{
			if (HostName) {
				WINPORT(SetLastError)(ERROR_SUCCESS);
				IdleMessage(HostName, Opt.ProcessColor);
			}

			return FALSE;
		}

#define ADD_CMD(v)                                                                                             \
	do {                                                                                                       \
		Assert(num < (cmdSize + NBUTTONSADDON));                                                               \
		CmdMsg[num++] = v;                                                                                     \
	} while (0)
	// First line
	num = 0;
	// Title
	String str;
	m = FP_GetMsg(MFtpTitle);

	if (hostname[0]) {
		if (UserPassword[0])
			str.printf("%s \"%s:*@%s\"", m, UserName, hostname);
		else
			str.printf("%s \"%s:%s\"", m, UserName, hostname);
	} else
		str = m;

	str.SetLength(cmdLineSize);
	ADD_CMD(str.c_str());

	// Error delimiter
	if (btn != MNone__ && btn < 0)
		switch (WINPORT(GetLastError)()) {
			case ERROR_SUCCESS:
			case ERROR_CANCELLED:
				break;
			default:
				ADD_CMD("\x1");
		}

	if (WINPORT(GetLastError)() == ERROR_CANCELLED)
		WINPORT(SetLastError)(ERROR_SUCCESS);

	// Commands
	if (exCmd)
		for (n = 0; n < cmdCount; n++)
			ADD_CMD(CmdBuff[n]);

	// Message
	String msg;

	if (Msg != MOk && LastMsg[0]) {
		if (exCmd && cmdCount)
			ADD_CMD("\x1");

		if (exCmd) {
			SYSTEMTIME st;
			WINPORT(GetLocalTime)(&st);
			msg.printf("%02d:%02d:%02d \"%s\"", st.wHour, st.wMinute, st.wSecond, LastMsg);
		} else
			msg = LastMsg;

		msg.SetLength(cmdLineSize);
		ADD_CMD(msg.c_str());
		Log(("CMSG: %s", LastMsg));
	}

	// Host
	String lh;

	if (LastHost[0] && (!HostName || HostName[0])) {
		lh = LastHost;
		lh.SetLength(cmdLineSize);
		ADD_CMD(lh.c_str());
	}

	// Buttons
#define ADD_BTN(v)                                                                                             \
	do {                                                                                                       \
		ADD_CMD(v);                                                                                            \
		btnAddon++;                                                                                            \
	} while (0)
	int btnAddon = 0;

	if (btn != MNone__) {
		ADD_CMD("\x1");
		ADD_BTN(FP_GetMsg(Abs(btn)));

		if (btn1 != MNone__)
			ADD_BTN(FP_GetMsg(btn1));

		if (btn2 != MNone__)
			ADD_BTN(FP_GetMsg(btn2));

		if (btn < 0 && btn != -MOk)
			ADD_BTN(FP_GetMsg(MCancel));
	}

	// Display error in title
	if (btn < 0 && btn != MNone__ && FP_Screen::isSaved()) {
		char errStr[MAX_PATH];
		snprintf(errStr, ARRAYSIZE(errStr), "%s \"%s\"", LastMsg, LastHost);
		SaveConsoleTitle::Text(errStr);
	}

	// Message
	BOOL isErr = (btn != MNone__ || btn < 0) && WINPORT(GetLastError)() != ERROR_SUCCESS,
		isWarn = btn != MNone__ && btn < 0;
	res = FMessage((isErr ? FMSG_ERRORTYPE : 0) | (isWarn ? FMSG_WARNING : 0) | (exCmd ? FMSG_LEFTALIGN : 0),
			NULL, CmdMsg, num, btnAddon);
	Log(("CMSG: rc=%d", res));
	// Del auto-added `cancel`
	btnAddon-= btn < 0 && btn != -MOk;

	// If has user buttons: return number of pressed button
	if (btnAddon > 1)
		return res;

	// If single button: return TRUE if button was selected
	return btn != MNone__ && res == 0;
}

void WINAPI OperateHidden(LPCSTR fnm, BOOL set)
{
	if (!Opt.SetHiddenOnAbort)
		return;
	/* //todo: WTF
		Log(("%s hidden", set ? "Set" : "Clr"));
		DWORD dw = GetFileAttributes(fnm);

		if(dw == MAX_DWORD) return;

		if(set)
			SET_FLAG(dw, FILE_ATTRIBUTE_HIDDEN);
		else
			CLR_FLAG(dw, FILE_ATTRIBUTE_HIDDEN);

		SetFileAttributes(fnm, dw);
		 */
}
