<?php

namespace App\Classes;

use Illuminate\Support\Facades\Log;

use App\Classes\Sock\SocketClient;

/**
 * Class TTY
 * @package App\Classes
 *
 * This base class handles all the comms with the remote
 */
class TTY
{
	protected SocketClient $client;									/* Our incoming client socket */

	private const RX_BUF_SIZE		= (0x8100);
	private const TX_BUF_SIZE		= (0x8100);

	private const MSG_BUFFER		= 2048;

	protected const OK				= 0;
	private const EOF				= -1;
	protected const TIMEOUT			= -2;
	protected const RCDO			= -3;
	protected const GCOUNT			= -4;
	protected const ERROR			= -5;

	private const TTY_SUCCESS		= self::OK;
	private const TTY_TIMEOUT		= self::TIMEOUT;
	private const TTY_HANGUP		= self::RCDO;
	private const TTY_ERROR			= self::ERROR;

	protected const QC_RECVD			= 'c';
	protected const QC_SENDD			= 'd';
	protected const QC_EMSID			= 'g';

	private const QR_POLL	= 'A';
	private const QR_REQ	= 'B';
	private const QR_SEND	= 'D';
	private const QR_STS	= 'E';
	private const QR_CONF	= 'F';
	private const QR_QUIT	= 'G';
	private const QR_INFO	= 'H';
	private const QR_SCAN	= 'I';
	private const QR_KILL	= 'J';
	private const QR_QUEUE	= 'K';
	private const QR_SKIP	= 'L';
	private const QR_REFUSE	= 'M';
	private const QR_HANGUP	= 'N';
	private const QR_RESTMR	= 'O';
	private const QR_CHAT	= 'P';
	private const QR_SET	= 'S';
	private const QR_STYPE	= 'T';

	private const HUP_NONE		= 0;
	private const HUP_LINE		= 1;
	private const HUP_OPERATOR	= 2;
	private const HUP_SESLIMIT	= 3;
	private const HUP_CPS		= 4;

	// @tod these FOP has been duplicated from Protocol - added back here for optimising EMSI
	protected const FOP_OK			= 0;
	protected const FOP_CONT		= 1;
	protected const FOP_SKIP		= 2;
	protected const FOP_ERROR		= 3;
	protected const FOP_SUSPEND		= 4;

	protected int $tty_rx_left		= 0;				/* Number of bytes in receive buffer */
	private string $tty_tx_buf		= '';				/* Data in the buffer to send */
	private int $tty_tx_ptr			= 0;				/* Pointer to next byte to send out in transmit buffer */
	private int $tty_tx_free		= self::TX_BUF_SIZE;/* Number of byte left in transmit buffer */

	private int $tty_status			= self::TTY_SUCCESS;
	private int $tty_gothup			= 0;
	//protected int $rxstatus			= 0;

	public function __construct()
	{
		$this->sendf = new FileSend;		// @todo these should be delcared private if they are staying here
		$this->recvf = new FileReceive;
		$this->rnode = new rnode;
	}

	protected function check_cps(): void
	{
		Log::debug('- Start',['m'=>__METHOD__]);

		$cpsdelay=10; // $cpsdelay=cfgi(CFG_MINCPSDELAY);
		$ncps = 38400/1000; $this->speed/1000;
		$r=1; //$r=cfgi(CFG_REALMINCPS);

		if(!($this->sendf->cps=time()-$this->sendf->start)) {
			$this->sendf->cps=1;
		} else {
			$this->sendf->cps=($this->sendf->foff-$this->sendf->soff)/$this->sendf->cps;
		}

		if(!($this->recvf->cps=time()-$this->recvf->start)) {
			$this->recvf->cps=1;
		} else {
			$this->recvf->cps=($this->recvf->foff-$this->recvf->soff)/$this->recvf->cps;
		}

		if($this->sendf->start&&(true ? 0 : cfgi(CFG_MINCPSOUT))>0&&(time()-$this->sendf->start)>$cpsdelay&&$this->sendf->cps<($r?$cci:$cci*$ncps)) {
			//write_log("mincpsout=%d reached, aborting session",r?cci:cci*ncps);
			$tty_gothup = self::HUP_CPS;
		}

		if($this->recvf->start&&(true ? 0 : cfgi(CFG_MINCPSIN))>0&&(time()-$this->recvf->start)>$cpsdelay&&$this->recvf->cps<($r?$cci:$cci*$ncps)) {
			//write_log("mincpsin=%d reached, aborting session",r?cci:cci*ncps);
			$tty_gothup = self::HUP_CPS;
		}

		$this->getevt();

		Log::debug('- End',['m'=>__METHOD__]);
	}

	// @todo no longer used?
	protected function getevt(): void
	{
		Log::debug('+ Start', ['m' => __METHOD__]);
		$qsndbuflen = 0;

		while($this->qrecvpkt($qrcv_buf)) {
			Log::debug('  - qrecvpkt Returned', ['m' => __METHOD__,'qrcv_buf'=>$qrcv_buf]);

			switch($qrcv_buf[2]) {	// @todo this doesnt seem right?
				case self::QR_SKIP:
					//$this->rxstatus=self::RX_SKIP;
					break;

				case self::QR_REFUSE:
					//$this->rxstatus=self::RX_SUSPEND;
					break;

				case self::QR_HANGUP:
					$tty_gothup = self::HUP_OPERATOR;
					break;

				case self::QR_CHAT:
					/*
					if($qrcv_buf[3]) {
						xstrcpy((qsnd_buf+qsndbuflen),(qrcv_buf+3),self::CHAT_BUF-$qsndbuflen);
						$qsndbuflen+=strlen((qrcv_buf+3));
						if($qsndbuflen>self::CHAT_BUF-128) {
							$qsndbuflen=self::CHAT_BUF-128;
						}
					} else {
						$i=$chatprot;
						$chatprot=-1;
						chatsend(qsnd_buf);
						if($chatlg) {
							chatlog_done();
						}
						$chatlg=0;
						$chatprot=i;
						xstrcat($qsnd_buf,"\n * Chat closed\n",CHAT_BUF);
						chatsend($qsnd_buf);
						if($chattimer>1) {
							qlcerase();
						}
						$qsndbuflen=0;
						$chattimer=1;
					}
					*/
					break;
			}
			$qsnd_buf[$qsndbuflen]=0;
		}

		if ($qsndbuflen>0)
			if(! $this->chatsend($qsnd_buf)) {
				$qsndbuflen=0;
			}

		Log::debug('- End', ['m' => __METHOD__]);
	}

	protected function qrecvpkt(&$str): int
	{
		Log::debug('+ Start',['m'=>__METHOD__,'str'=>$str]);

		/*
		if (! $this->xsend_cb) {
			return 0;
		}
		*/

		$rc = $this->xrecv($this->client->connection,$str,self::MSG_BUFFER-1,0);
		Log::debug(sprintf('  - qrecvpkt Got [%x] (%d)',$rc,$rc),['m'=>__METHOD__]);
		if ( $rc < 0 && $this->errno != 11 /*MSG_EAGAIN*/ ) {
			if ($this->errno == self::ECONNREFUSED) {
				$xsend_cb = NULL;
			}
			//DEBUG(('I',1,"can't recv (fd=%d): %s",ssock,strerror(errno)));
		}
		Log::debug(sprintf('  - qrecvpkt Got [%x] (%d)',$rc,$rc),['m'=>__METHOD__,'str'=>$str,'len'=>strlen($str)]);
		if ($rc < 3 || ! substr($str,0,2)) {
			Log::debug('+ End',['m'=>__METHOD__,'rc'=>0]);
			return 0;
		}

		//str[rc] = '\0';
		if (! $rc)
			$str = '';

		Log::debug('+ End',['m'=>__METHOD__,'rc'=>$rc]);
		return $rc;
	}

	protected function rxclose(&$f, int $what): int
	{
		Log::debug('+ Start',['m'=>__METHOD__,'what'=>$what,'f'=>$f]);

		$cps=time()-$this->recvf->start;
		$ss = '';

		if(!$f || !$f) {
			Log::debug('= End',['m'=>__METHOD__,'rc'=>self::FOP_ERROR]);
			return self::FOP_ERROR;
		}

		$this->recvf->toff+=$this->recvf->foff;
		$this->recvf->stot+=$this->recvf->soff;
		$p2=0;
		if(! $cps) {
			$cps=1;
		}

		$cps=($this->recvf->foff-$this->recvf->soff)/$cps;
		/*
		IFPerl(if((ss=perl_end_recv(what))) {
			if(!$ss) {
				$what=self::FOP_SKIP;
			} else {
				$p2 = $ss; //xstrcpy(p2,ss,MAX_PATH);
			}
		});
		*/
		switch($what) {
			case self::FOP_SUSPEND:
				$ss="suspended";
				break;
			case self::FOP_SKIP:
				$ss="skipped";
				break;
			case self::FOP_ERROR:
				$ss="error";
				break;
			case self::FOP_OK:
				$ss="ok";
				break;
			default:
				$ss="";
		}

		Log::debug('  -',['m'=>__METHOD__,'soff'=>$this->recvf->soff,'ss'=>$ss]);
		if($this->recvf->soff) {
			//write_log("rcvd: %s, %lu bytes (from %lu), %ld cps [%s]",
			//	recvf.fname, (long) recvf.foff, (long) recvf.soff, cps, ss);
		} else {
			//write_log("rcvd: %s, %lu bytes, %ld cps [%s]",
			//	recvf.fname, (long) recvf.foff, cps, ss);
		}

		fclose($f);
		$f='';

		/*
		snprintf(p, MAX_PATH, "%s/tmp/%s", cfgs(CFG_INBOUND), recvf.fname);
		if($p2) {
			if($p2!='/'&&*$p2=='.') {
				$ss=xstrdup($p2);
				snprintf($p2,MAX_PATH,"%s/%s",cfgs(CFG_INBOUND),$ss);
				xfree($ss);
			}
		} else {
			snprintf(p2, MAX_PATH, "%s/%s", cfgs(CFG_INBOUND), recvf.fname);
		}
		*/

		//$ut->actime=$ut->modtime=$this->recvf->mtime;
		$this->recvf->foff=0;
		switch($what) {
			case self::FOP_SKIP:
				unlink($p);
				break;
			case self::FOP_SUSPEND:
			case self::FOP_ERROR:
				/*
				if($this->whattype($p)==self::IS_PKT&&cfgi(self::CFG_KILLBADPKT)) {
					unlink($p);
				} else {
					//utime(p,&ut);
					touch ($this->recvf->name,octdec($this->recvf->mtime));
				}
				*/
				break;
			case self::FOP_OK:
				$rc=isset($receive_callback)?receive_callback($p):0;

				if($rc) {
					//lunlink(p);
				} else {
					$ss=$p2+strlen($p2)-1;
					$overwrite=0;
					/*
					for(i=cfgsl(CFG_ALWAYSOVERWRITE); i; i=i->next)
						if(!xfnmatch(i->str,recvf.fname,FNM_PATHNAME)) {
								$overwrite=1;
							}
				while(!$overwrite&&!stat(p2, &sb)&&p2[0]) {
					if(sifname(ss)) {
						ss--;
						while('.' == *ss && ss >= p2) {
							ss--;
						}
						if(ss < p2) {
							write_log("can't find suitable name for %s: leaving in temporary directory",p);
							p2[0] = '\x00';
						}
					}
				}
				if(p2[0]) {
					if(overwrite) {
						lunlink(p2);
					}
					if(rename(p, p2)) {
						write_log("can't rename %s to %s: %s",p,p2,strerror(errno));
					} else {
						utime(p2,&ut);
						chmod(p2,cfgi(CFG_DEFPERM));
					}
				}
					*/
					Log::debug(sprintf('Recevied [%s] with mtime [%s]',$this->f->name,$this->recvf->mtime),['m'=>__METHOD__]);
					touch('/tmp/tmp/'.$this->f->name,$this->recvf->mtime);
				}
				break;
		}

		if($what==self::FOP_SKIP||$what==self::FOP_SUSPEND) {
			$skipiftic=$what;
		}
		$this->recvf->start=0;
		$this->recvf->ftot=0;
		//$this->rxstatus=0;
		Log::debug('= End',['m'=>__METHOD__,'rc'=>$what]);
		return $what;
	}

	protected function rxopen(string $name,int $rtime,int $rsize,string &$f): int
	{
		Log::debug('+ Start',['m'=>__METHOD__,'name'=>$name,'rtime'=>$rtime,'rsize'=>$rsize,'f'=>$f]);

		$ccs = '/tmp'; // @todo Base path needs to be a config item
		$this->speed = 38400;

		$prevcps = ($this->recvf->start&&(time()-$this->recvf->start>2))?$this->recvf->cps:$this->speed/10;

		if(! $name) {
			return self::FOP_ERROR;
		}

		$bn = basename($name);//xstrcpy($bn, qbasename($name), self::MAX_PATH);
		Log::debug(sprintf('  - bn[%s]',$bn),['m'=>__METHOD__]);
		//mapname((char*)bn, cfgs(CFG_MAPIN), MAX_PATH);

		//$this->recvf->start=(int)decoct(time());
		$this->recvf->start=time();
		//xfree(recvf.fname);
		$this->recvf->fname=$bn; //xstrdup($bn);
		//dd(['rtime'=>$rtime,'start'=>$this->recvf->start]);
		$this->recvf->mtime=$rtime; //-gmtoff($this->recvf->start);
		$this->recvf->ftot=$rsize;
		if($this->recvf->toff+$rsize > $this->recvf->ttot) {
			$this->recvf->ttot+=$rsize;
		}

		$this->recvf->nf++;
		if($this->recvf->nf > $this->recvf->allf) {
			$this->recvf->allf++;
		}
		//IFPerl(if((rc=perl_on_recv())!=FOP_OK)return rc);
		/*
		if($this->whattype($name)==self::IS_PKT&&($rsize==60||!$rsize)&&cfgi(self::CFG_KILLBADPKT)) {
			return self::FOP_SKIP;
		}
		*/
		$rc=$skipiftic = 0; // @todo
		$skipiftic=0;
		if($rc&&istic($bn)&&cfgi(self::CFG_AUTOTICSKIP)) {
			//write_log($rc==self::FOP_SKIP?$weskipstr:$wesusstr,$this->recvf->fname,"auto");
			return $rc;
		}
		// @todo
		/*
		for($i=cfgsl(self::CFG_AUTOSKIP); $i; $i=$i->next)
			if(!$this->xfnmatch($i->str,$bn, self::FNM_PATHNAME)) {
			//write_log(weskipstr,$this->recvf.fname,"");
			$skipiftic=self::FOP_SKIP;
			return self::FOP_SKIP;
		}
		for($i=cfgsl(self::CFG_AUTOSUSPEND); $i; $i=$i->next)
			if(!$this->xfnmatch($i->str, $bn, self::FNM_PATHNAME)) {
			//write_log(wesusstr,$this->recvf->fname,"");
			$skipiftic=self::FOP_SUSPEND;
			return self::FOP_SUSPEND;
		}
		*/

		$p = '/tmp/tmp/'; //@todo snprintf(p, MAX_PATH, "%s/tmp/", cfgs(CFG_INBOUND));

		//if ($sb = stat($p)) // if(stat($p, &sb))
		if(! is_dir($p) AND ! mkdir($p)) { // && $errno!=EEXIST
			Log::debug(sprintf('  - dir doesnt exist and cannot make it? [%s]',$p),['m'=>__METHOD__,'rc'=>self::FOP_SUSPEND]);
			//write_log("can't make directory %s: %s", p, strerror(errno));
			//write_log(wesusstr,$this->recvf.fname,"");
			$skipiftic=self::FOP_SUSPEND;
			return self::FOP_SUSPEND;
		}

		$p = sprintf('%s/%s',$ccs,$bn);// snprintf($p, self::MAX_PATH, "%s/%s", $ccs, $bn);

		if(file_exists($p) AND ($sb=stat($p)) && $sb['size']==$rsize) {//if(!stat(p, &sb) && sb.st_size==rsize) {
			Log::debug(sprintf('  - file exists and size is same? [%s]',$p),['m'=>__METHOD__,'sb'=>$sb,'rsize'=>$rsize,'rc'=>self::FOP_SKIP]);
			//write_log(weskipstr,$this->recvf.fname,"");
			$skipiftic=self::FOP_SKIP;
			return self::FOP_SKIP;
		}

		//dd(['maxpath'=>self::MAX_PATH,'bn'=>$bn]);
		//snprintf($p, self::MAX_PATH, "%s/tmp/%s", $ccs, $bn);
		$p = sprintf('%s/tmp/%s',$ccs,$bn);

		// If the file exists
		if (file_exists($p) AND $sb=stat($p)) {//if(!stat(p, &sb)) {
			Log::debug(sprintf('  - file exists... [%s]',$p),['m'=>__METHOD__,'sb'=>$sb,'rsize'=>$rsize,
				'mtime'=>$this->recvf->mtime,
				//'mtime-decopt'=>(int)decoct($this->recvf->mtime),
				//'mtime-octdec'=>(int)octdec($this->recvf->mtime),
				'sbmtime'=>$sb['mtime'],
				'sbmtime-decopt'=>(int)decoct($sb['mtime']),
				//'sbmtime-octdec'=>(int)octdec($sb['mtime']),
			]);
			// @todo binkp doesnt use octal.
			if($sb['size']<$rsize && $sb['mtime']==(int)$this->recvf->mtime) {
				Log::debug(sprintf('  - attempt open for append [%s]',$p),['m'=>__METHOD__]);

				$f=fopen($p, "ab");
				if(!$f) {
					Log::debug(sprintf('  - attempt open for append FAILED [%s]',$p),['m'=>__METHOD__,'rc'=>self::FOP_SUSPEND]);
					//write_log("can't open file %s for writing: %s", p,strerror(errno));
					//write_log(wesusstr,$this->recvf.fname,"");
					$skipiftic=self::FOP_SUSPEND;
					return self::FOP_SUSPEND;
				}
				Log::debug(sprintf('  - FTELL REPORTS [%s]',serialize(ftell($f))),['m'=>__METHOD__]);
				//  ftell() gives undefined results for append-only streams (opened with "a" flag).
				$this->recvf->foff = $this->recvf->soff = $sb['size']; //ftell($f);
				Log::debug(sprintf('  - open for append [%s] at [%d]',$p,$this->recvf->soff),['m'=>__METHOD__,'rc'=>self::FOP_CONT]);
				//if(cfgi(self::CFG_ESTIMATEDTIME)) {
				//write_log("start recv: %s, %lu bytes (from %lu), estimated time %s",
				//	$this->recvf.fname, (long) rsize, (long) $this->recvf.soff, estimatedtime(rsize-$this->recvf.soff,prevcps,effbaud));
				//}
				return self::FOP_CONT;
			}
		}

		$f=fopen($p, "wb");
		if(!$f) {
			//write_log("can't open file %s for writing: %s", p,strerror(errno));
			//write_log(wesusstr,$this->recvf.fname,"");
			$skipiftic=self::FOP_SUSPEND;
			return self::FOP_SUSPEND;
		}

		//dd(['sb'=>$sb,'recvf'=>$this->recvf]);
		Log::debug(sprintf('  - new file created [%s]',$p),['m'=>__METHOD__,'rc'=>self::FOP_OK]);
		$this->recvf->foff = $this->recvf->soff = 0;
		//if(cfgi(self::CFG_ESTIMATEDTIME)) {
		//write_log("start recv: %s, %lu bytes, estimated time %s",
		//	$this->recvf.fname, (long) rsize, estimatedtime(rsize,prevcps,effbaud));
		//}
		return self::FOP_OK;
	}

	public function setClient(SocketClient $client): void
	{
		$this->client = $client;
	}

	public function timer_expired(int $timer): int
	{
		return (time()>=$timer);
	}

	public function timer_rest(int $timer): int
	{
		return (($timer)-time());
	}

	public function timer_set(int $expire): int
	{
		return (time()+$expire);
	}

	protected function txclose(&$f, int $what):int
	{
		$cps=time()-$this->sendf->start;

		if(!$f) {
			return self::FOP_ERROR;
		}
		$this->sendf->toff+=$this->sendf->foff;
		$this->sendf->stot+=$this->sendf->soff;

		if(!$cps) {
			$cps=1;
		}
		$cps=($this->sendf->foff-$this->sendf->soff)/$cps;
		//IFPerl(perl_end_send(what));
		switch($what) {
			case self::FOP_SUSPEND:
				$ss="suspended";
				break;
			case self::FOP_SKIP:
				$ss="skipped";
				break;
			case self::FOP_ERROR:
				$ss="error";
				break;
			case self::FOP_OK:
				$ss="ok";
				break;
			default:
				$ss="";
		}
		if($this->sendf->soff) {}
		//write_log("sent: %s, %lu bytes (from %lu), %ld cps [%s]", sendf.fname, (long) sendf.foff, (long) sendf.soff, cps, ss);
		else {}
		//write_log("sent: %s, %lu bytes, %ld cps [%s]",sendf.fname, (long) sendf.foff, cps, ss);
		$this->sendf->foff=0;
		$this->sendf->ftot=0;
		$this->sendf->start=0;
		fclose($f);
		$f=NULL;
		return $what;
	}

	protected function xrecv($sock,&$buf,int $len,int $wait):int
	{
		Log::debug('+ Start',['m'=> __METHOD__]);

		$l = 0;

		if (! $sock) {
			$this->errno = self::EBADF;
			return -1;
		}

		if (! $wait) {
			Log::debug('  - Not wait',['m'=> __METHOD__]);
			$tv_tv_sec = 0;
			$tv_tv_usec = 0;
			$rfd = 0; //FD_ZERO(&rfd);
			//FD_SET(sock, &rfd);
			$read = [$sock];
			$write = [];
			$except = [];
			$rc = socket_select($read,$write,$except,0,0);
			//$foo = '';
			//$rc = socket_recv($this->client->connection,$foo,1,MSG_PEEK | MSG_DONTWAIT);
			Log::debug('  - socket_select',['m'=> __METHOD__,'rc'=>$rc,'read'=>$read,'write'=>$write,'except'=>$except]);
			//$rc = $this->client->hasData(0);
			if ($rc < 1) {
				if (! $rc) {
					$this->errno = 11; //MSG_EAGAIN;
				}

				return -1;
			}
		}

		Log::debug(sprintf('  - doing a read now for [%d].',$len));
		$rc = socket_recv($read[0],$l,$len,MSG_PEEK | MSG_DONTWAIT);
		Log::debug('  - socket_recv PEEK', ['m' => __METHOD__,'l'=>$l,'rc'=>$rc]);

		if ($rc <= 0) {
			return $rc;
		}

		if ($rc == 2) {
			return 2;
			//l = I2H16(l);
//			$l = unpack('s',$l);
			$l = ((ord($l[0])&0x7f)<<8)+ord($l[1]);

//			dd(['l'=>$l,'0'=>ord($l[0])&0xf,'00'=>((ord($l[0])&0x7f)<<8)+ord($l[1]),'1'=>ord($l[1]),'hex'=>sprintf('%x',unpack('v',$l)),'len'=>$len]);
			if (! $l) {
				return 0;
			}

			if ($l > $len) {
				$l = $len;
			}

			Log::debug('  - L is ',['m' => __METHOD__,'l'=>min($l+$rc,$len)]);
			$rc = socket_recv($sock,$buf,min($l+$rc,$len),MSG_WAITALL);
			Log::debug('  - socket_recv GOT', ['m' => __METHOD__,'buf'=>$buf,'len'=>strlen($buf),'rc'=>$rc]);
			if ($rc <= 0) {
				return $rc;
			}
			$rc = min($rc - 2, strlen($buf));
			if ($rc < 1) {
				return 0;
			}
			if ($rc >= $len) {
				$rc = $len - 2;
			}
			$buf = substr($buf,2,$rc); //memcpy(buf, buf + 2, rc);
			return $rc;
		}

		return 0;
	}

	private function tty_bufc(int $ch): int
	{
		return $this->tty_bufblock( chr($ch), 1 );
	}

	// SocketClient::buffer_add()
	public function tty_bufblock(string $data, int $nbytes): int
	{
		Log::debug(sprintf('%s: + Start [%s] (%d)',__METHOD__,$data,$nbytes));
		$rc = self::OK;
		$txptr = self::TX_BUF_SIZE - $this->tty_tx_free;
		$nptr = 0;

		$this->tty_status = self::TTY_SUCCESS;

		while ( $nbytes ) {
			Log::debug(sprintf('  - Num Bytes [%d]: TX Free [%d]',$nbytes,$this->tty_tx_free));

			if ( $nbytes > $this->tty_tx_free ) {
				do {
					$this->tty_bufflush( 5 );
					if ( $this->tty_status == self::TTY_SUCCESS ) {
						$n = min($this->tty_tx_free,$nbytes);
						$this->tty_tx_buf = substr($data,$nptr,$n);
						$this->tty_tx_free -= $n;
						$nbytes -= $n;
						$nptr += $n;
					}
				} while ( $this->tty_status != self::TTY_SUCCESS );

			} else {
				Log::debug(sprintf('  -'),['data'=>$data,'nptr'=>$nptr,'txptr'=>$txptr,'tx_buff'=>substr($data,$nptr+$txptr,$nbytes)]);

				$this->tty_tx_buf .= $data;// memcpy( (void *) (tty_tx_buf + txptr), nptr, nbytes );
				$this->tty_tx_free -= $nbytes;
				$nbytes = 0;
			}
		}

		Log::debug('= End',['m'=>__METHOD__,'rc'=>$rc]);
		return $rc;
	}

	private function tty_bufclear(): void
	{
		$this->tty_tx_ptr = 0;
		$this->tty_tx_free = self::TX_BUF_SIZE;
		$this->tty_tx_buf = '';
	}

	protected function tty_bufflush(int $tsec): int
	{
		Log::debug('+ Start',['m'=>__METHOD__,'tsec'=>$tsec,'txfree'=>$this->tty_tx_free,'txptr'=>$this->tty_tx_ptr,'txbuff'=>$this->tty_tx_buf]);

		$rc = self::OK;
		$restsize = self::TX_BUF_SIZE - $this->tty_tx_free - $this->tty_tx_ptr;

		$tm = $this->timer_set( $tsec );
		while (self::TX_BUF_SIZE != $this->tty_tx_free ) {
			$wd = true;
			$tv = $this->timer_rest( $tm );

			if (( $rc = $this->client->canSend($tv) > 0 && $wd )) {

				Log::debug(sprintf('  - Sending [%d]: Buffer [%s] Size [%d]',substr($this->tty_tx_buf,$this->tty_tx_ptr,$restsize),$this->tty_tx_buf,$restsize));
				$rc = $this->client->send(substr($this->tty_tx_buf,$this->tty_tx_ptr,$restsize),0,$restsize);
				Log::debug(sprintf('  - Sent [%d]: Buffer [%s] Size [%d]',$rc,$this->tty_tx_buf,$restsize));

				if ($rc == $restsize ) {
					$this->tty_bufclear();
				} else if ( $rc > 0 ) {
					$this->tty_tx_ptr += $rc;
					$restsize -= $rc;
				} else if ( $rc < 0 && $this->tty_status != self::TTY_TIMEOUT ) {
					return self::ERROR;
				}

			} else {
				return $rc;
			}

			if ($this->timer_expired( $tm )) {
				return self::ERROR;
			}
		}

		Log::debug('= End',['m'=>__METHOD__,'rc'=>$rc]);
		return $rc;
	}

	public function tty_getc(int $timeout): int
	{
		Log::debug(sprintf('%s: + Start [%d]',__METHOD__,$timeout),['rx_left'=>$this->tty_rx_left]);

		if ($this->tty_rx_left == 0 ) {
			if ($this->client->hasData($timeout) > 0) {
				if (! ($this->tty_rx_buf = $this->client->read(0,self::RX_BUF_SIZE))) {
					Log::debug(sprintf('%s:  - Nothing read',__METHOD__));

					return ($this->EWBOEA()) ? self::TTY_TIMEOUT : self::ERROR;
				}

				Log::info(sprintf('%s:   - Read [%d]',__METHOD__,strlen($this->tty_rx_buf)));
				$this->tty_rx_ptr = 0;
				$this->tty_rx_left = strlen($this->tty_rx_buf);

			} else {
				return ( $this->tty_gothup ? self::TTY_HANGUP : self::TTY_TIMEOUT );
			}
		}

		$rc = ord(substr($this->tty_rx_buf,$this->tty_rx_ptr,1)); //tty_rx_buf[tty_rx_ptr++];

		$this->tty_rx_left--;
		$this->tty_rx_ptr++;

		Log::debug(sprintf('%s: = Return [%x] (%c)',__METHOD__,$rc,$rc));
		return $rc;
	}

	private function tty_getc_timed(int $timeout): int
	{
		$t = time();

		$rc = $this->tty_getc($timeout);
		$timeout -= (time() - $t);
		return $rc;
	}

	protected function tty_purge(): void
	{
		//DEBUG(('M',3,"tty_purge"));

		$this->tty_rx_ptr = $this->tty_rx_left = 0;
		/*
	if ( isatty( tty_fd )) {
		tio_flush_queue( tty_fd, TIO_Q_IN );
	}
		*/
	}

	private function tty_purgeout(): void
	{
		//DEBUG(('M',3,"tty_purgeout"));

		$this->tty_bufclear();
		/*
		if ( isatty( tty_fd )) {
			tio_flush_queue( tty_fd, TIO_Q_OUT );
		}
		*/
	}

	private function tty_putc(string $ch):int
	{
		$this->tty_bufblock($ch,1);
		return $this->tty_bufflush(5);
	}

	protected function tty_select($rd,$wd,int $tval): int
	{
		//DEBUG(('T',2,"tty_select"));
		$rfd = $this->client->connection;
		$wfd = $this->client->connection;
		//dump($rfd,$wfd);

		//FD_ZERO( &rfd );
		//FD_ZERO( &wfd );
		//if ($rd && $rd) {
			//FD_SET($tty_fd,$rfd);
			$rd = FALSE;
		//}

		//if ($wd && $wd ) {
			//FD_SET($tty_fd,$wfd);
			$wd = FALSE;
		//}

		$tty_error = 0;
		$read = [$this->client->connection];
		$write = [$this->client->connection];
		$except = [];
		dump('calling socket_select',['timeout'=>$tval,'read'=>$read,'write'=>$write]);
		$rc = socket_select($read, $write, $except,($tval ?: NULL));
		dump('done socket_select',$tval);

		$tty_error = socket_last_error();
		$tty_status = self::TTY_SUCCESS;

		if ($rc < 0 ) {
			if (EWBOEA()) {
				$tty_status = self::TTY_TIMEOUT;

			} else if ($errno == self::EINTR) {
				$tty_status = ($tty_online && $tty_gothup ) ? self::TTY_HANGUP : self::TTY_TIMEOUT;
			} else if ($errno == self::EPIPE) {
				$tty_gothup = self::HUP_LINE;
				$tty_status = self::TTY_HANGUP;
			} else {
				$tty_status = self::TTY_ERROR;
			}

		} else if ($rc == 0) {
			$tty_status = self::TTY_TIMEOUT;

			/*
		} else {
			if ($rd /*&& FD_ISSET( tty_fd, &rfd )*) {
				$rd = TRUE;
			}
			if ($wd /*&& FD_ISSET( tty_fd, &wfd )*) {
				$wd = TRUE;
			}
		*/
		}

		//DEBUG(('T',2,"tty_select: fd=%d rc=%i (rd=%s, wd=%s)", tty_fd, rc, FDS( rd ), FDS( wd )));

		return $rc;
	}

	protected function BUFCHAR(int $c)
	{
		$this->tty_bufc($c);
	}

	protected function BUFFLUSH(int $tsec): int
	{
		return $this->tty_bufflush($tsec);
	}

	// @todo this should go into SocketCLient?
	protected function EWBOEA(): bool
	{
		$errno = socket_last_error($this->client->connection);
		Log::debug('+ Start',['m'=> __METHOD__,'errno'=>$errno]);
		return $errno === 11 /*MSG_EAGAIN*/;
	}

	protected function GETCHAR(int $t): int
	{
		return $this->tty_getc($t);
	}

	protected function GETCHART($t): int
	{
		return $this->tty_getc_timed($t);
	}

	public function NOTTO(string $ch): int
	{
		return (($ch)==self::ERROR || ($ch)==self::RCDO || ($ch)==self::EOF);
	}

	protected function PUTSTR(string $s):void
	{
		$this->tty_bufblock($s,strlen($s));
		$this->BUFFLUSH( 5);
	}

	protected function PURGEALL(): void
	{
		$this->tty_purge();
		$this->tty_purgeout();
	}

	protected function PUTCHAR(string $c)
	{
		$this->tty_putc( $c );
	}

	protected function PUTSTRCR(string $str)
	{
		$this->tty_bufblock($str."\r",strlen($str)+1);
		return $this->tty_bufflush(5);
	}
}

class rnode
{
	public $starttime = 0;
	public $options = 0;
	public $netmail = 0;
	public $files = 0;
	public $ewboea = 0;
	public $phone = '';
}