#include <all_far.h>

#include "Int.h"

#include <cinttypes>

//--------------------------------------------------------------------------------
void Connection::sendrequest(char *cmd, char *local, char *remote)
{
	//??SaveConsoleTitle _title;
	sendrequestINT(cmd, local, remote);
	IdleMessage(NULL, 0);
}

void Connection::sendrequestINT(char *cmd, char *local, char *remote)
{
	PROC(("sendrequestINT", "%s,%s,%s", cmd, local, remote))

	if (type == TYPE_A)
		restart_point = 0;

	// WIN32_FIND_DATA   ffi;
	FHandle fin;
	SOCKET dout = 0;
	LONG hi;
	FTPCurrentStates oState = CurrentState;
	BOOL oldBrk = FtpSetBreakable(this, -1);
	int64_t fsz;
	FTNNotify ni;
	ni.Upload = TRUE;
	ni.Starting = TRUE;
	ni.Success = TRUE;
	ni.RestartPoint = restart_point;
	ni.Port = ntohs(portnum);
	ni.Password[0] = 0;		// StrCpy( ni.Password, UserPassword, ARRAYSIZE(ni.Password));
	StrCpy(ni.User, UserName, ARRAYSIZE(ni.User));
	StrCpy(ni.HostName, hostname, ARRAYSIZE(ni.HostName));
	StrCpy(ni.LocalFile, local, ARRAYSIZE(ni.LocalFile));
	StrCpy(ni.RemoteFile, remote, ARRAYSIZE(ni.RemoteFile));

	if (proxy) {
		proxtrans(cmd, local, remote);
		return;
	}

	struct stat s = {};
	if (sdc_stat(local, &s)) {
		ErrorCode = errno;
		SysError = TRUE;
		return;
	}

	if ((s.st_mode & S_IFMT) == S_IFDIR) {
		ErrorCode = ERROR_DIRECTORY;
		SysError = TRUE;
		Log(("local is directory [%s]", local));

		if (!ConnectMessage(MNotPlainFile, local, -MRetry))
			ErrorCode = ERROR_CANCELLED;

		return;
	}

	fin.Handle = Fopen(local, "r");

	if (!fin.Handle) {
		ErrorCode = errno;
		Log(("!open local"));
		SysError = TRUE;

		if (!ConnectMessage(MErrorOpenFile, local, -MRetry))
			ErrorCode = ERROR_CANCELLED;

		return;
	}

	fsz = s.st_size;

	if (restart_point && fsz == restart_point) {
		AddCmdLine(FMSG(MFileComplete));
		ErrorCode = ERROR_SUCCESS;
		return;
	}

	if (!initconn()) {
		Log(("!initconn"));
		return;
	}

	if (Host.SendAllo) {
		if (cmd[0] != Opt.cmdAppe[0]) {
			Log(("ALLO %" PRId64, fsz));

			if (command("%s %" PRId64, Opt.cmdAllo, fsz) != RPL_COMPLETE) {
				Log(("!allo"));
				return;
			}
		}
	}

	if (restart_point) {
		if (!ResumeSupport && cmd[0] != Opt.cmdAppe[0]) {
			AddCmdLine(FMSG(MResumeRestart));
			restart_point = 0;
		} else {
			Log(("restart_point %I64u", restart_point));

			if (!Fmove(fin.Handle, restart_point)) {
				SysError = TRUE;
				ErrorCode = WINPORT(GetLastError)();
				Log(("!setfilepointer(%I64u)", restart_point));

				if (!ConnectMessage(MErrorPosition, local, -MRetry))
					ErrorCode = ERROR_CANCELLED;

				return;
			}

			if (cmd[0] != Opt.cmdAppe[0] && command("%s %I64u", Opt.cmdRest, restart_point) != RPL_CONTINUE)
				return;

			TrafficInfo->Resume(restart_point);
		}
	}

	if (Host.PassiveMode) {
		Log(("pasv"));
		dout = dataconn();

		if (dout == INVALID_SOCKET)
			goto abort;
	}

	if (remote) {
		Log(("Upload remote [%s]", remote));

		if (command("%s %s", cmd, remote) != RPL_PRELIM) {
			return;
		}
	} else {
		Log(("!remote"));

		if (command("%s", cmd) != RPL_PRELIM) {
			return;
		}
	}

	if (!Host.PassiveMode) {
		dout = dataconn();

		if (dout == INVALID_SOCKET)
			goto abort;
	}

	switch (type) {
		case TYPE_I:
		case TYPE_L:
		case TYPE_A:

			if (fsz != 0) {
				unsigned rsz = Host.IOBuffSize;

				if (rsz < fsz && (rsz & 511) && rsz > 512)
					rsz&= ~511;

				{
					unsigned int osz = 0;
					socklen_t sz = sizeof(osz);

					if (getsockopt(dout, SOL_SOCKET, SO_SNDBUF, (char *)&osz, &sz) != 0)
						osz = 8 * 1024;		// default - 8K

					if (rsz >= osz && fsz >= osz) {
						if (rsz > 1024 * 1024)
							rsz = 1024 * 1024;	// 1M

						osz = rsz;

						if (fsz < osz)
							osz = (unsigned)fsz;

						++osz;
						setsockopt(dout, SOL_SOCKET, SO_SNDBUF, (char *)&osz, sizeof(osz));
					}
				}

				if (rsz >= fsz || rsz > 64 * 1024)		// 64K (start winsize)
				{
					BOOL one = TRUE;
					setsockopt(dout, IPPROTO_TCP, TCP_NODELAY, (char *)&one, sizeof(one));
				}

				if (PluginAvailable(PLUGIN_NOTIFY))
					FTPNotify().Notify(&ni);

				Log(("Uploading %s->%s from %I64u", local, remote, restart_point));
				FTPConnectionBreakable _brk(this, FALSE);
				CurrentState = fcsProcessFile;
				//-------- READ
				DWORD ind = 0;
				DWORD b, e;
				GET_TIME(b);

				while (true) {
					hi = 0;
					Log(("read %d", rsz));

					if (!WINPORT(ReadFile)(fin.Handle, IOBuff, rsz, (LPDWORD)&hi, NULL)) {
						SysError = TRUE;
						ErrorCode = WINPORT(GetLastError)();
						Log(("pf: !read buff"));
						goto abort;
					}

					if (hi == 0) {
						ErrorCode = WINPORT(GetLastError)();
						SysError = ErrorCode != ERROR_SUCCESS;
						break;
					}

					//-------- SEND
					LONG d;
					char *bufp;
					Log(("doSend"));

					for (bufp = IOBuff; hi > 0; hi-= d, bufp+= d) {
						Log(("ndsend %d", hi));

						if ((d = (LONG)nb_send(&dout, bufp, (int)hi, 0)) <= 0) {
							Log(("pf(%d,%s): !send %d!=%d", code, GetSocketErrorSTR(), d, hi));
							code = RPL_TRANSFERERROR;
							goto abort;
						}

						Log(("sent %d", d));
						ind+= d;
						GET_TIME(e);

						if (CMP_TIME(e, b) >= 0.5) {
							b = e;
							d = ind;
							ind = 0;

							if (IOCallback && !TrafficInfo->Callback((int)d)) {
								Log(("pf(%d,%s): canceled", code, GetSocketErrorSTR()));
								ErrorCode = ERROR_CANCELLED;
								goto abort;
							}
						}
					}	//-- SEND

					Log(("sended"));
					// Sleep(1);
				}	//-- READ

				if (IOCallback)
					TrafficInfo->Callback(0);

				Log(("done"));
			}	/*fsz != 0*/

			break;
		default:
			break;	// TYPE_E, TYPE_NONE
	}				/*SWITCH*/

					// NormExit
	FtpSetBreakable(this, oldBrk);
	CurrentState = oState;

	if (data_peer != INVALID_SOCKET) {
		scClose(data_peer, 1);

		if (getreply(0) > RPL_COMPLETE) {
			ErrorCode = ERROR_WRITE_FAULT;
		}
	} else
		getreply(0);

	if (PluginAvailable(PLUGIN_NOTIFY)) {
		ni.Starting = FALSE;
		ni.Success = TRUE;
		FTPNotify().Notify(&ni);
	}

	return;
abort:
	FtpSetBreakable(this, oldBrk);
	CurrentState = oState;

	if (!cpend) {
		Log(("!!!cpend"));
	}

	int ocode = code, oecode = ErrorCode;
	scClose(data_peer, 1);	// SD_SEND

	if (!SendAbort(data_peer)) {
		Log(("!send abort"));
		lostpeer();
	} else {
		setascii();
		ProcessCommand("pwd");
		code = ocode;
		ErrorCode = oecode;
	}

	if (PluginAvailable(PLUGIN_NOTIFY)) {
		ni.Starting = FALSE;
		ni.Success = FALSE;
		FTPNotify().Notify(&ni);
	}
}
