;;;
;;; server.lisp
;;;
;;; Dummied up server
;;;
;;; Copyright (C) 2005, Alastair Bridgewater
;;;

(in-package :clxs)

;; These are constants defined by the protocol.
(defconstant +protocol-major-version+ 11)
(defconstant +protocol-minor-version+ 0)

;; This is the list of error type symbols, in order, with a
;; blank spot at the beginning so that we can use POSITION
;; to find the right code given the symbol.
(defparameter +error-code-list+
  '(nil :request :value :window :pixmap :atom :cursor :font
    :match :drawable :access :alloc :colormap :gcontext
    :idchoice :name :length :implementation))

;; These are set at the discretion of the vendor.
(defparameter +server-vendor+ "CLXS")
(defparameter +server-release+ 0)

;; These are dummy values, and dependant on the input subsystem.
(defparameter +motion-buffer-size+ 0)
(defparameter +min-keycode+ 8)
(defparameter +max-keycode+ 8)

;; These are dummy values, and depend on the display engine.
(defparameter *image-byte-order* :lsb-first)
(defparameter *bitmap-format-bit-order* :least-significant)
(defparameter *bitmap-format-scanline-unit* 32)
(defparameter *bitmap-format-scanline-pad* 32)

;; Pixmap formats
(defparameter *pixmap-formats* '((1 1 32))
  "A list of lists representing pixmap formats.
Each pixmap format is a list (depth bits-per-pixel scanline-pad).")

;; Screens
(defparameter *screens* nil
  "A list of screens, format undecided.")

(defparameter +max-request-length+ (ash +request-buffer-size+ -2))

(defvar *server* nil)
(defvar *show-packet-contents* nil)

(defun protocol-error (error &optional (datum 0))
  (throw 'protocol-error (values error datum)))

(defun send-error-response (network-client client packet error datum)
  (let ((response (make-array 32 :element-type '(unsigned-byte 8)
			      :initial-element 0)))
    (format t "Error response ~A opcode ~A request ~A.~%" error
	    (aref packet 0) (client-state-request-number client))
    ;; FIXME: The error structure has a spot for minor opcode. Where's that?
    (setf (aref response 1) (position error +error-code-list+))
    (setf (aref response 10) (aref packet 0))
    (if (eq (client-state-byte-order client) :lsb-first)
	(progn
	  (setf (request-le-u16 response 2)
		(client-state-request-number client))
	  (setf (request-le-u32 response 4) datum))
	(progn
	  (setf (request-be-u16 response 2)
		(client-state-request-number client))
	  (setf (request-be-u32 response 4) datum)))
    (send-packet network-client response)))

(defun clxs-request-dispatch (network-client client packet)
  (let ((length (if (eq (client-state-byte-order client) :lsb-first)
		    (request-le-u16 packet 2) (request-be-u16 packet 2))))
    ;; A length of 0 is an error, and I don't know the semantics.
    ;; A length beyond max-length is a Length error, and we should
    ;; ignore the rest of the request.
    ;; We don't handle either condition yet.
    (if (< (network-client-packet-size network-client) (ash length 2))
	(progn
	  (setf (network-client-packet-size network-client) (ash length 2)))
	(progn
	  (incf (client-state-request-number client))
	  (multiple-value-bind (error datum)
	      (catch 'protocol-error
		(let ((request-handler (request-handler (aref packet 0))))
		  (unless request-handler (protocol-error :implementation))
		  (funcall request-handler client packet)
		  nil))
	    (when error (send-error-response network-client client
					     packet error datum)))
	  (when *show-packet-contents* (format t "~A~%" packet))
	  (network-client-packet-complete network-client)
	  (setf (network-client-packet-size network-client) 4)))))

(defun initialize-request-dispatch (client)
  (setf (network-client-packet-received-callback
	 (client-state-network-client client))
	(lambda (network-client packet)
	  (clxs-request-dispatch network-client client packet))))

(defun send-authfail-message (client message little-endian)
  (let ((reply (make-array (+ 8 (logand -4 (+ 3 (length message))))
			   :element-type '(unsigned-byte 8)
			   :initial-element 0)))
    (setf (aref reply 0) 0)
    (setf (aref reply 1) (length message))
    (if little-endian
	(progn
	  (setf (request-le-u16 reply 2) +protocol-major-version+)
	  (setf (request-le-u16 reply 4) +protocol-minor-version+)
	  (setf (request-le-u16 reply 6) (ash (+ 3 (length message)) -2)))
	(progn
	  (setf (request-be-u16 reply 2) +protocol-major-version+)
	  (setf (request-be-u16 reply 4) +protocol-minor-version+)
	  (setf (request-be-u16 reply 6) (ash (+ 3 (length message)) -2))))
    (dotimes (i (length message))
      (setf (aref reply (+ 8 i)) (char-code (aref message i))))
    #+(or) (format t "~A~%" reply)
    (send-packet client reply)
    #+nil    (sb-unix:unix-write
     (sb-bsd-sockets:socket-file-descriptor
      (slot-value client 'socket))
     (sb-sys:vector-sap reply)
     0 (length reply))))

(defun screen-data-length ()
  "Compute the total amount of data required to represent all of the screens, in words."
  ;; FIXME: Implement.
  ;; 10 words per screen plus 2 words per depth plus 6 words per visual.
  ;; visuals are per depth, depths are per screen.
  10)

(defun clxs-initialize-client-connection (network-client packet)
  (let ((client (make-instance 'client-state)))
    (setf (slot-value client 'network-client) network-client)
    (setf (slot-value client 'byte-order) (if (eq #x6c (aref packet 0))
					      :lsb-first :msb-first))
    (setf (client-state-request-number client) 0)
    ;; FIXME: Error checking would be nice here.
    (assign-resource-range client)

    (initialize-request-dispatch client)

    ;; Okay, now that we have the client state setup we need
    ;; to send the server response packet back.
    (let* ((pixmap-data-start (+ 8 32 (logand -4 (+ 3 (length +server-vendor+)))))
	   (screen-data-start (+ pixmap-data-start (* 8 (length *pixmap-formats*))))
	   (response-length (+ 8 (* 2 (length *pixmap-formats*))
			       (ash (+ 3 (length +server-vendor+)) -2)
			       (screen-data-length)))
	   (response (make-array (+ 8 (ash response-length 2))
				 :element-type '(unsigned-byte 8)
				 :initial-element 0)))
      (declare (ignorable screen-data-start))
      (setf (aref response 0) 1) ; success
      #+nil (setf (aref response 1) 0) ; unused
      (if (eq (client-state-byte-order client) :lsb-first)
	  (progn
	    (setf (request-le-u16 response 2) +protocol-major-version+)
	    (setf (request-le-u16 response 4) +protocol-minor-version+)
	    (setf (request-le-u16 response 6) response-length)
	    (setf (request-le-u32 response 8) +server-release+)
	    (setf (request-le-u32 response 12)
		  (client-state-resource-id-base client))
	    (setf (request-le-u32 response 16)
		  (client-state-resource-id-mask client))
	    (setf (request-le-u32 response 20) +motion-buffer-size+)
	    (setf (request-le-u16 response 24) (length +server-vendor+))
	    (setf (request-le-u16 response 26) +max-request-length+)
	    ;; FIXME: HACK below for number of screens.
	    (setf (aref response 28) 1 #+nil(length *screens*))
	    (setf (aref response 29) (length *pixmap-formats*))
	    (setf (aref response 30)
		  (position *image-byte-order*
			    '(:lsb-first :msb-first)))
	    (setf (aref response 31)
		  (position *bitmap-format-bit-order*
			    '(:least-significant :most-significant)))
	    (setf (aref response 32) *bitmap-format-scanline-unit*)
	    (setf (aref response 33) *bitmap-format-scanline-pad*)
	    (setf (aref response 34) +min-keycode+)
	    (setf (aref response 35) +max-keycode+)
	    #+nil (setf (request-le-u32 response 36) 0) ; unused
	    (dotimes (i (length +server-vendor+))
	      (setf (aref response (+ 40 i))
		    (char-code (aref +server-vendor+ i))))
	    (loop for i from 0 below (length *pixmap-formats*)
		  for format in *pixmap-formats*
		  with position = (+ pixmap-data-start (* 8 i))
		  do (progn
		       (setf (aref response (+ position 0)) (first format))
		       (setf (aref response (+ position 1)) (second format))
		       (setf (aref response (+ position 2)) (third format))))
	    ;; FIXME: Add screens here.
	    #+nil (format t "~A~%" response)
	    )
	  (progn
	    ;; FIXME: Need a big-endian version of the above.
	    ;; Should probably introduce some packet creation macrology.
	    (format t "Big-endian case not implemented.~%")
	    ))
      (send-packet network-client response))
    ))

(defun clxs-authorize-client (client packet)
  (declare (ignorable client packet))
  t)

(defun clxs-initial-negotiation (client packet)
  (let ((little-endian (= (aref packet 0) #x6c)))
    (let* ((auth-name-length (if little-endian
				 (request-le-u16 packet 6)
				 (request-be-u16 packet 6)))
	   (auth-data-length (if little-endian
				 (request-le-u16 packet 8)
				 (request-be-u16 packet 8)))
	   (final-length (+ 12
			    (logand -4 (+ 3 auth-name-length))
			    (logand -4 (+ 3 auth-data-length)))))
      #+(or) (format t "bs ~X nl ~A dl ~A~%" (aref packet 0) auth-name-length auth-data-length)
      (setf (network-client-packet-size client) final-length)
      
      (when (= final-length (network-client-packet-size client))
	(if (clxs-authorize-client client packet)
	    (progn
	      (clxs-initialize-client-connection client packet)
	      (network-client-packet-complete client)
	      ;; Prepare for first request packet.
	      (setf (network-client-packet-size client) 4))
	    (progn
	      (send-authfail-message client
				     "Server not implemented."
				     little-endian)
	      (network-client-disconnect client)))))))

(defun clxs-connect-callback (server)
  (let ((client (network-server-accept server)))
    (when client
      ;; client connection
      (setf (network-client-packet-size client) 12)
      (setf (network-client-packet-received-callback client)
	    (symbol-function 'clxs-initial-negotiation)))))

(defun start-clxs-server (&key (display 1))
  (setf *server*
	(make-network-server #(127 0 0 1) (+ display 6000)
			     :mp-style :serve-event
			     :connect-callback #'clxs-connect-callback))
  (reset-resource-database)
  (reset-atom-database)
  (network-server-start *server*))

(defun stop-clxs-server ()
  (network-server-stop *server*))

;; Hackety-hack. The below probably belong elsewhere.
#|
(define-x-request get-property (:opcode 20)
    ((opcode :u8 :value 20 :error :implementation)
     (delete :bool)
     (length :u16 :value 6 :error :length)
     (window :window)
     (property :atom)
     (type :atom-or-zero)
     (long-offset :u32)
     (long-length :u32))
  (declare (ignorable delete long-offset long-length))
  ;; FIXME: Implement.
  (protocol-error :implementation))
|#

#|
(define-x-request get-input-focus (:opcode 43)
    ((opcode :u8 :value 43 :error :implementation)
     (unused :u8)
     (length :u16 :value 1 :error :length))
  (declare (ignore unused))
  (format t "Get input focus.~%"))
|#

(define-x-request create-gc (:opcode 55)
    ((opcode :u8 :value 55 :error :implementation)
     (unused :u8)
     (length :u16 :value (+ 4 (logcount value-mask)) :error :length)
     (gcid :new-resource-id)
     ;; FIXME: Next should be :drawable, not :u32.
     ;; Is :u32 because no root window yet.
     (drawable #+nil :drawable :u32)
     (value-mask :u32 :typecheck '(unsigned-byte 23)))
  (declare (ignore unused))
  ;; FIXME: Implement properly.
  (setf (x-resource gcid) (make-instance 'gcontext-resource)))

(define-x-request free-gc (:opcode 60)
    ((opcode :u8 :value 60 :error :implementation)
     (unused :u8)
     (length :u16 :value 2 :error :length)
     (gc :gcontext))
  (declare (ignore unused))
  (format t "Free gcontext ~A~%" gc))

(define-x-request query-extension (:opcode 98)
    ((opcode :u8 :value 98 :error :implementation)
     (unused :u8)
     (length :u16 :value (+ 2 (ash (+ 3 name-length) -2)) :error :length)
     (name-length :u16))
  (declare (ignore unused))
  (let ((name (make-string name-length))
	(response (make-array 32 :element-type '(unsigned-byte 8)
			      :initial-element 0)))
    (dotimes (i name-length)
      (setf (aref name i) (code-char (aref request (+ 8 i)))))
    (format t "QueryExtension: ~A~%" name)

    (setf (aref response 0) 1)
    (if (eq (client-state-byte-order client) :lsb-first)
	(setf (request-le-u16 response 2) (client-state-request-number client))
	(setf (request-be-u16 response 2) (client-state-request-number client)))
    (send-packet (client-state-network-client client) response)))

;;; EOF
