/*
Copyright (c) 1998-2001, Robert O'Callahan
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

Redistributions of source code must retain the above copyright notice, this list of
conditions and the following disclaimer.

Redistributions in binary form must reproduce the above copyright notice, this list
of conditions and the following disclaimer in the documentation and/or other materials
provided with the distribution.

The name of Robert O'Callahan may not be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

/*
This code is copyright (C) 1998-1999 Robert O'Callahan.
See LICENSE.TXT for the license.
*/

#include "ttxssh.h"

void UTIL_init_sock_write_buf(UTILSockWriteBuf FAR * buf)
{
	buf_create(&buf->bufdata, &buf->buflen);
	buf->datastart = 0;
	buf->datalen = 0;
}

static int send_until_block(PTInstVar pvar, SOCKET s,
                            const char FAR * data, int len)
{
	int total_sent = 0;

	while (len > 0) {
		int sent_amount = (pvar->Psend) (s, data, len, 0);

		if (sent_amount < 0) {
			if (WSAGetLastError() == WSAEWOULDBLOCK) {
				return total_sent;
			} else {
				return sent_amount;
			}
		} else {
			total_sent += sent_amount;
			data += sent_amount;
			len -= sent_amount;
		}
	}

	return total_sent;
}

/* Tera Termが動作しているPC上の X サーバプログラムに対して、データを送る。
 * 一度で送れない場合は、リングバッファに格納し、遅延配送する。 
 *
 * 動作フローは下記の通り。
 * (1) 初回のデータが届く。もしくは、リングバッファが空。
 * (2) non-blockingで送信を試みる。全送信できたらreturn。
 * (3) 送信できなかったデータはリングバッファへ格納し、return。
 * (4) 次のデータが届く。
 * (5) リングバッファへ格納し、return。
 * (6) 次のデータが届く。
 * (7) リングバッファがフルになったら、バッファに残っているデータを blocking で送信を試みる。
 *     送信失敗したら、エラーreturn。
 * (8) ユーザデータ(data/len)の送信を blocking で送信を試みる。
 *     送信失敗したら、エラーreturn。 
 * (9) 送信できなかったユーザデータはリングバッファへ格納し、return。
 * (10) リングバッファに残っているデータを non-blockingで送信を試みる。
 *
 * pvar: 共有リソース
 * buf: リングバッファ
 * blocking_write: 同期型のパケット送信関数
 * socket: 非同期型のソケットハンドル
 * data: データ
 * len: データ長
 *
 * return TRUE: 送信成功
 *        FALSE: 送信エラー
 */
BOOL UTIL_sock_buffered_write(PTInstVar pvar, UTILSockWriteBuf FAR * buf,
                              UTILBlockingWriteCallback blocking_write,
                              SOCKET socket, const char FAR * data,
                              int len)
{
	int curlen;
	int desiredlen;
	int space_required;
	int amount_to_write_from_buffer;
	BOOL did_block = FALSE;
	int first_copy_start;
	int first_copy_amount;

	// 初回呼び出し時は、かならず下記 if 文に入る。
	/* Fast path case: buffer is empty, try nonblocking write */
	if (buf->datalen == 0) {
#if 1
		// まずは non-blocking でパケット送信する。一度でも WSAEWOULDBLOCK エラーになったら、
		// 関数は「送信済みデータ長」を返す。
		// たとえば、X サーバプログラムとして"xterm"を起動していた場合、xtermの端末内に何か
		// 文字を表示し続けている状態で、端末のウィンドウをドラッグすると、関数は 0 を返してくる。
		int sent_amount = send_until_block(pvar, socket, data, len);

		if (sent_amount < 0) {
			return FALSE;
		}
		data += sent_amount;
		len -= sent_amount;
#else
		// ノンブロッキングモードで送れなかった場合、以降の処理へ続くが、バグがあるため、
		// まともに動かない。ゆえに、初回でブロッキングモードを使い、確実に送信してしまう。
		// ポート転送(local-to-remote)において、でかいパケットを受信できない問題への対処。
		// (2007.11.29 yutaka)
		// オリジナルコードにバグがあると思っていたが、SSH遅延送信処理に問題があったため、
		// それが根本原因であり、オリジナルコードには問題がなかったと考える。
		// 本来はノンブロッキングで扱うべきところを、無理矢理ブロッキングにすることにより、
		// Tera Termが「応答なし」、Xmingが「CPUストール」という不可思議な現象が出てしまう
		// ように見える。そのため、本来のコードに戻すことを決断する。
		// (2012.10.14 yutaka)
		if (!blocking_write(pvar, socket, data, len)) {
			return FALSE;
		} else {
			len = 0;
		}
#endif
	}

	// 初回呼び出し時の non-blocking 送信で、すべて送り切れたら、即座に成功で返る。
	if (len == 0) {
		return TRUE;
	}

	// リングバッファ(buf)に残存しているデータと、新規送信データを足して(desiredlen)、
	// 現在のバッファ長(curlen)が足りるかを計算する。
	//
	// (1)データが先頭に格納されているケース
	//
	//                 <----- buflen ------------->
	// buf->bufdata -> +--------------------------+
	//                 |XXXXXXX                   |
	//                 +--------------------------+ 
	//                 <------>
	//                   buf->datalen
	//                 ^
	//                 |
	//                 buf->datastart
	//
	//
	// (2)データが両端に格納されているケース
	//
	//                 <----- buflen ------------->
	// buf->bufdata -> +--------------------------+
	//                 |XXXX                  XXXX|
	//                 +--------------------------+ 
	//                 <--->                  <-->
	//                  (a)                    (b)
	//                      (a)+(b) = buf->datalen
	//                                        ^
	//                                        |
	//                                       buf->datastart
	//
	/* We blocked or the buffer has data in it. We need to put this data
	   into the buffer.
	   First, expand buffer as much as possible and necessary. */
	curlen = buf->buflen;
	desiredlen =
		min(pvar->session_settings.WriteBufferSize, buf->datalen + len);

	if (curlen < desiredlen) {
		int wrap_amount = buf->datastart + buf->datalen - curlen;
		int newlen =
			min(pvar->session_settings.WriteBufferSize, 2 * desiredlen);

		buf->bufdata = realloc(buf->bufdata, newlen);
		buf->buflen = newlen;

		if (wrap_amount > 0) {
			int wrap_to_copy = min(wrap_amount, newlen - curlen);

			memmove(buf->bufdata + curlen, buf->bufdata, wrap_to_copy);
			memmove(buf->bufdata, buf->bufdata + wrap_to_copy,
					wrap_amount - wrap_to_copy);
		}
	}

	/* 1) Write data from buffer
	   2) Write data from user
	   3) Copy remaining user data into buffer
	 */
	// バッファがいっぱいになり、新しいデータ(data)が溢れる場合に space_required が正となる。
	// すなわち、space_requiredは「溢れた分」を表す。
	//
	//                                               space_required
	//                 <----- buflen -------------><-------->
	// buf->bufdata -> +--------------------------+
	//                 |XXXXXXXXXXXXXXXXX         |
	//                 +--------------------------+ 
	//                 <----------------><------------------>
	//                   buf->datalen            len
	//                 ^
	//                 |
	//                 buf->datastart
	space_required = max(0, buf->datalen + len - buf->buflen);
	amount_to_write_from_buffer = min(buf->datalen, space_required);

	if (amount_to_write_from_buffer > 0) {
		int first_part =
			min(amount_to_write_from_buffer, buf->buflen - buf->datastart);

		did_block = TRUE;
		if (!blocking_write
			(pvar, socket, buf->bufdata + buf->datastart, first_part)) {
			return FALSE;
		}
		if (first_part < amount_to_write_from_buffer) {
			if (!blocking_write
				(pvar, socket, buf->bufdata,
				 amount_to_write_from_buffer - first_part)) {
				return FALSE;
			}
		}

		buf->datalen -= amount_to_write_from_buffer;
		if (buf->datalen == 0) {
			buf->datastart = 0;
		} else {
			buf->datastart =
				(buf->datastart +
				 amount_to_write_from_buffer) % buf->buflen;
		}
		space_required -= amount_to_write_from_buffer;
	}

	if (space_required > 0) {
		did_block = TRUE;
		if (!blocking_write(pvar, socket, data, space_required)) {
			return FALSE;
		}
		data += space_required;
		len -= space_required;
	}

	first_copy_start = (buf->datastart + buf->datalen) % buf->buflen;
	first_copy_amount = min(len, buf->buflen - first_copy_start);
	memcpy(buf->bufdata + first_copy_start, data, first_copy_amount);
	if (first_copy_amount < len) {
		memcpy(buf->bufdata, data + first_copy_amount,
			   len - first_copy_amount);
	}
	buf->datalen += len;

	if (did_block) {
		return UTIL_sock_write_more(pvar, buf, socket);
	} else {
		return TRUE;
	}
}

BOOL UTIL_sock_write_more(PTInstVar pvar, UTILSockWriteBuf FAR * buf,
						  SOCKET socket)
{
	int first_amount = min(buf->buflen - buf->datastart, buf->datalen);
	int sent =
		send_until_block(pvar, socket, buf->bufdata + buf->datastart,
						 first_amount);

	if (sent < 0) {
		return FALSE;
	} else {
		if (sent == first_amount && first_amount < buf->datalen) {
			int sentmore =
				send_until_block(pvar, socket, buf->bufdata,
								 buf->datalen - first_amount);

			if (sentmore < 0) {
				return FALSE;
			}
			sent += sentmore;
		}

		buf->datalen -= sent;
		if (buf->datalen == 0) {
			buf->datastart = 0;
		} else {
			buf->datastart = (buf->datastart + sent) % buf->buflen;
		}
	}

	return TRUE;
}

void UTIL_destroy_sock_write_buf(UTILSockWriteBuf FAR * buf)
{
	memset(buf->bufdata, 0, buf->buflen);
	buf_destroy(&buf->bufdata, &buf->buflen);
}

BOOL UTIL_is_sock_deeply_buffered(UTILSockWriteBuf FAR * buf)
{
	return buf->buflen / 2 < buf->datalen;
}

void UTIL_get_lang_msg(PCHAR key, PTInstVar pvar, PCHAR def)
{
	GetI18nStr("TTSSH", key, pvar->ts->UIMsg, sizeof(pvar->ts->UIMsg),
		def, pvar->ts->UILanguageFile);
}

int UTIL_get_lang_font(PCHAR key, HWND dlg, PLOGFONT logfont, HFONT *font, PTInstVar pvar)
{
	if (GetI18nLogfont("TTSSH", key, logfont,
					   GetDeviceCaps(GetDC(dlg),LOGPIXELSY),
					   pvar->ts->UILanguageFile) == FALSE) {
		return FALSE;
	}

	if ((*font = CreateFontIndirect(logfont)) == NULL) {
		return FALSE;
	}

	return TRUE;
}
