;;; -*- Mode:LISP; Package:TCP-APPLICATION; Readtable:CL; Base:10 -*-

#|

  Copyright LISP Machine, Inc. 1987
   See filename "Copyright.Text" for
  licensing and release information.

|#

(export '(char-int-if-any
	  int-char-if-any
	  ))

;;;Easy TCP/UDP interface

;;;TCP-HOST and TCP-HOST-PATHNAME flavors

(defflavor tcp-host
	   ()
	   (si:basic-host))
	  
(defmethod (tcp-host :name) ()
  "TCP-HOST")

(defmethod (tcp-host :system-type) ()
  :tcp-host)
	  
(defmethod (tcp-host :name-as-file-computer) ()
  "TCP-HOST")

(defmethod (tcp-host :pathname-flavor) ()
  'tcp-host-pathname)
	  
(defmethod (tcp-host :pathname-host-namep) (name)
  (or (typep name 'tcp-host)
      (string-equal name "TCP-HOST")))

(defflavor tcp-host-pathname
	   ()
	   (fs:pathname))

(defmethod (tcp-host-pathname :string-for-printing) ()
  (format nil "~A: ~D remote ~D local ~D"
	  (send (send self :host) :name)
	  (send self :name)
	  (send self :type)
	  (send self :version)))

(defmethod (tcp-host-pathname :string-for-editor) ()
  (send self :string-for-printing))

(defmethod (tcp-host-pathname :parse-namestring) (host-specified-p namestring
						  &optional (start 0) end)
  (declare (ignore host-specified-p))
  (declare (values device directory name type version))
  (tcp-host-parse-namestring namestring start end))

(defmethod (tcp-host-pathname :open) (&rest options)
  (apply #'open-easy-tcp-stream
	 fs:name
	 fs:type
	 fs:version
	 (cdr options)))

;;;UDP-HOST and UDP-HOST-PATHNAME flavors

(defflavor udp-host
	   ()
	   (si:basic-host))
	  
(defmethod (udp-host :name) ()
  "UDP-HOST")

(defmethod (udp-host :system-type) ()
  :udp-host)
	  
(defmethod (udp-host :name-as-file-computer) ()
  "UDP-HOST")

(defmethod (udp-host :pathname-flavor) ()
  'udp-host-pathname)
	  
(defmethod (udp-host :pathname-host-namep) (name)
  (or (typep name 'udp-host)
      (string-equal name "UDP-HOST")))

(defflavor udp-host-pathname
	   ()
	   (fs:pathname))

(defmethod (udp-host-pathname :string-for-printing) ()
  (format nil "~A: ~D remote ~D local ~D"
	  (send (send self :host) :name)
	  (send self :name)
	  (send self :type)
	  (send self :version)))

(defmethod (udp-host-pathname :string-for-editor) ()
  (send self :string-for-printing))

(defmethod (udp-host-pathname :parse-namestring) (host-specified-p namestring
						  &optional (start 0) end)
  (declare (ignore host-specified-p))
  (declare (values device directory name type version))
  (tcp-host-parse-namestring namestring start end))

(defmethod (udp-host-pathname :open) (&rest options)
  (apply #'open-easy-udp-stream
	 fs:name
	 fs:type
	 fs:version
	 (cdr options)))

(defun tcp-host-parse-namestring (namestring &optional (start 0) (end (length namestring)))
  (flet ((skip-dotted-fields (string count test start end &aux (dot start) last-dot)
	   (loop
	     (when (null dot) (return nil))
	     (and count (zerop count) (return dot))
	     (unless (funcall test (char string (1+ dot)))
	       (return dot))
	     (when count (decf count))
	     (setq last-dot dot)
	     (setq dot (string-search "." string (1+ dot) end))
	     (when (null dot)
	       (return (cond ((null count) last-dot)	;skipped as many as we could
			     ((zerop count) nil)	;used up all dots
			     (t start)))))))	        ;didn't find enough
    (let ((remote-address :wild)
	  (remote-port nil)
	  (local-port nil)
	  (remote-port-start nil)
	  (local-port-start nil)
	  temp)
      (unless (= start end)
	(let ((first (char namestring start))
	      (dot (string-search "." namestring start end))
	      (pound (string-search "#" namestring start end))
	      (name-end nil)
	      (remote-port-end nil)
	      (local-port-end nil))
	  (when (and dot (alphanumericp first))
	    (if (digit-char-p first)		;skip dotted decimal address
		(setq dot (skip-dotted-fields namestring 3 #'digit-char-p dot (or pound end)))
	      (setq dot (skip-dotted-fields namestring nil #'alpha-char-p dot (or pound end)))))
	  (setq name-end (or dot pound end))
	  (setq remote-address (substring namestring start name-end))
	  (when (and pound dot)
	    (setq local-port-start (1+ pound))
	    (setq local-port-end end)
	    (setq local-port (substring namestring local-port-start local-port-end)))
	  (setq remote-port-start (cond (dot (1+ dot))
					(pound (1+ pound))))
	  (when remote-port-start
	    (setq remote-port-end (if local-port-start pound end))
	    (setq remote-port (substring namestring remote-port-start remote-port-end)))))
      (cond ((null remote-port))
	    ((setq temp (global:parse-number remote-port))
	     (setq remote-port temp))
	    ((and (setq temp (global:intern-soft (string-append "IPPORT-" (string-upcase remote-port))
						 "TCP-APPLICATION"))
		  (sym-boundp temp))
	     (setq remote-port (sym-value temp)))
	    (t
	     (global:ferror :parse-pathname-error
			    "Bad REMOTE-PORT specification \"~A\" in: ~S"
			    remote-port
			    namestring)))
      (cond ((null local-port))
	    ((setq temp (global:parse-number local-port))
	     (setq local-port temp))
	    ((and (setq temp (global:intern-soft (string-append "IPPORT-" (string-upcase local-port))
						 "TCP-APPLICATION"))
		  (sym-boundp temp))
	     (setq local-port (sym-value temp)))
	    (t
	     (global:ferror :parse-pathname-error
			    "Bad LOCAL-PORT specification \"~A\" in: ~S"
			    local-port
			    namestring)))
      (values :unspecific
	      :unspecific
	      (or remote-address :wild)		;name == remote address
	      (or remote-port :wild)		;type == remote port
	      (or local-port :wild)))))		;version == local port

(compile-flavor-methods tcp-host udp-host tcp-host-pathname udp-host-pathname)

(defvar *tcp-host* (make-instance 'tcp-host))
(pushnew *tcp-host* fs:*pathname-host-list*)

(defvar *udp-host* (make-instance 'udp-host))
(pushnew *udp-host* fs:*pathname-host-list*)

(defun open-easy-tcp-stream (remote-address remote-port local-port
			     &rest args &key
			     (keyword "Easy TCP stream")
			     (connect t)
			     (buffered t)
			     (auto-force-output nil)
			     (direction :both)
			     (input-buffers 4)
			     (output-buffers 4)
			     coroutine-input
			     for-udp
			     &allow-other-keys
			     )
  (declare (ignore coroutine-input))		;Compatibility -- special option no longer required
  (when for-udp
    ;;This is a kludge -- should use UDP-HOST -- but is compatible with old system
    (return-from open-easy-tcp-stream
      (apply #'open-easy-udp-stream
	     remote-address
	     remote-port
	     local-port
	     (trim-keywords (copy-list args) '(:for-udp)))))
  (let ((flavor (cond ((not buffered) 'tcp-unbuffered-stream)
		      ((not auto-force-output) 'tcp-buffered-stream)
		      (t 'tcp-auto-buffered-stream)))
	(normalp nil)
	(stream nil)
	(trimmed-args (trim-keywords (copy-list args)
				     '(:keyword :connect :buffered :auto-force-output :direction
				       :input-buffers :output-buffers :coroutine-input :for-udp))))
    (unwind-protect
	(progn
	  (when (eq remote-port :wild)		;not specified
	    (setq remote-address nil)		;...so null out remote-address
	    (setq remote-port nil)		; and remote-port
	    (setq connect nil))			; and make into a passive connection
	  (when (eq local-port :wild)
	    (setq local-port nil))
	  (setq stream (if buffered
			   (make-instance flavor
					  :input-buffer-limit (ecase direction
								((:input :both) input-buffers)
								(:output 0))
					  :output-buffer-limit (ecase direction
								 ((:output :both) output-buffers)
								 (:input 0))
					  )
			 (make-instance flavor)))
	  (setq normalp
		(lexpr-send stream
			    :open
			    keyword
			    :active connect
			    :remote-address remote-address
			    :remote-port remote-port
			    :local-port local-port
			    trimmed-args)))
      (unless normalp
	(and stream (close stream))))
    stream))

(defun open-easy-udp-stream (remote-address remote-port local-port
			     &rest args &key
			     (keyword "Easy UDP Stream")		;For Peek
			     (raw t)					;If raw UDP stream
			     (buffered t)				;If buffered or unbuffered
			     (receives-out 4)
			     &allow-other-keys
			     )
  (let ((normalp)
	(flavor (cond (raw 'udp:udp-stream)
		      (buffered 'udp:udp-buffered-stream)
		      (t 'udp:udp-unbuffered-stream)))
	(stream)
	(trimmed-args (trim-keywords (copy-list args) '(:keyword :raw :buffered :receives-out))))
    (unwind-protect
	(progn
	  (when (eq remote-port :wild)		;not specified
	    (setq remote-address nil)		;...so null out remote-address
	    (setq remote-port nil))		; and remote-port
	  (when (eq local-port :wild)
	    (setq local-port nil))
	  (setq stream (make-instance flavor :receives-out receives-out))
	  (setq normalp
		(lexpr-send stream
			    :open
			    keyword
			    :remote-address remote-address
			    :remote-port remote-port
			    :local-port local-port
			    trimmed-args)))
      (when (not normalp)
	(and stream (close stream))))
    stream))

(defun trim-keywords (arglist keywords)
  (dolist (x keywords)
    (remf arglist x))
  arglist)

;;;Compatibility

(defun tcp:get-internet-address (address)
  (ip:parse-internet-address address))

(defun char-int-if-any (x)
  ;; the :TYI message in zetalisp is defined to return a FIXNUM
  ;; for the most part a character will work, but not in
  ;; delimiters for READLINE etc.
  (if (characterp x)
      (char-int x)
    x))

(defun int-char-if-any (x)
  (and x (int-char x)))
