;;; xwem-win.el --- Windows ops for XWEM.

;; Copyright (C) 2003 by Free Software Foundation, Inc.

;; Author: Zajcev Evgeny <zevlg@yandex.ru>
;; Created: 21 Mar 2003
;; Keywords: xlib, xwem
;; X-CVS: $Id: xwem-win.el,v 1.3 2004/05/05 22:43:06 lg Exp $

;; This file is part of XWEM.

;; XWEM is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.

;; XWEM is distributed in the hope that it will be useful, but WITHOUT
;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
;; or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
;; License for more details.

;; You should have received a copy of the GNU General Public License
;; along with XEmacs; see the file COPYING.  If not, write to the Free
;; Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
;; 02111-1307, USA.

;;; Synched up with: Not in FSF

;;; Commentary:
;;
;; This file contain operations on XWEM windows.  Window is part of
;; Frame, Window holds X client - CL.
;;

;;; Code


;;; Customization
(defgroup xwem-win nil
  "Group to customize XWEM windows."
  :prefix "xwem-win-"
  :group 'xwem)

;;;###autoload
(defcustom xwem-win-min-width 80
  "*Minimal width for window"
  :type 'number
  :group 'xwem-win)

;;;###autoload
(defcustom xwem-win-min-height 80
  "*Minimal height for window"
  :type 'number
  :group 'xwem-win)

;;;###autoload
(defcustom xwem-win-delim-width 3
  "*Window delimeter width, including shadowness."
  :type 'number
  :group 'xwem-win)

(defcustom xwem-win-delim-shadow-thicksness 1
  "*How thick to draw 3D shadows around window delims."
  :type 'number
  :group 'xwem-win)

(defcustom xwem-win-default-properties
  (list	'attachable nil)
  "*Default properties list for frame windows.

dead       - window that referenced but not workable.
deleted    - deleted window have this equal to t
frame      - frame window attached to.
next	   - next window in windows double linked list.
prev	   - previous window.
hchild     - child window after horizontal split.
vchild     - child window after vertical split.
parent	   - parent window (window in which we do split).
client     - client currently active in window. (index in clients-list)
attachable - if non-nil then frame may be attached to some frame's
             window. (not yet implemented [is it will be usefull?])
expectances - list of expectances for window.
"
  :type '(restricted-sexp :match-alternatives (valid-plist-p))
  :group 'xwem-win)

(defcustom xwem-win-winmove-allow-jupming t
  "*Non-nil allows jumping to opposite edge, when no window founded."
  :type 'boolean
  :group 'xwem-win)

(defcustom xwem-win-collect-deleted-clients t
  "*Non-nil mean clients managed in window that are deleting now, will
be placed in `xwem-window-other' window."
  :type 'boolean
  :group 'xwem-win)

(defcustom xwem-win-max-clients 32
  "*Maximum number of clients in window.
NOT USED."
  :type 'number
  :group 'xwem-win)


;;; Hooks
(defcustom xwem-win-switch-hook nil
  "Hooks to be called when window switching occurs.
Function will receive two arguments OLD-WIN and NEW-WIN."
  :type 'hook
  :group 'xwem-hooks)

(defcustom xwem-win-delete-hook nil
  "Hooks called with one arg - window when deleting window."
  :type 'hook
  :group 'xwem-hooks)

(defcustom xwem-win-clients-change-hook nil
  "Hooks called when win's clients list changed."
  :type 'hook
  :group 'xwem-hooks)

;;;###autoload
(defcustom xwem-win-after-split-hook nil
  "Hook to be called after window split.
Functions will be called with three arguments: WIN HOW and NEW-SIZE."
  :type 'hook
  :group 'xwem-hooks)


;;; Structures
;;;###autoload
(defstruct (xwem-win (:include X-Geom)
		     (:predicate xwem-iswin-p))
  clients				; xwem clients list managed in this win
  props					; property list aka plist
  cl					; xwem client
  frame					; xwem frame
  dead
  deleted
  next
  prev
  hchild
  vchild
  parent
  expectances)				; expectances list

;;;###autoload
(defstruct (xwem-win-saved (:include X-Geom)
			   (:predicate xwem-iswinsaved-p))
  clients                               ; clients managed in window
  cl					; xwem client sele
  props					; properties
  expectances				; expectances list
  currentp				; non-nil for selected window
  first-hchild first-vchild
  next prev)

;;;###autoload
(defstruct (xwem-win-config (:predicate xwem-iswinconfig-p))
  frame frame-width frame-height
  current-cl				; cl in selected window
  min-width min-height
  saved-root-window)
  

;; Functions
;;;###autoload
(defun xwem-win-cl-current-p (cl &optional win)
  "Return non-nil if CL is current WIN's client."
  (let ((ww (or win (xwem-cl-win cl))))
    (when (xwem-win-p ww)
      (eq cl (xwem-win-cl ww)))))

;;;###autoload
(defun xwem-win-p (window &optional sig)
  "Return t if WINDOW is XWEM frame's window."
  (let ((iswin (xwem-iswin-p window)))
    (if (and (not iswin) sig)
	(signal 'wrong-type-argument (list sig 'xwem-win-p window))
      iswin)))

(defun xwem-win-make-list-by-next (window)
  "Create list of WINDOW and all next windows."
  (let ((wins window)
	rlist)
    (while (xwem-win-p wins)
      (setq rlist (cons wins rlist))
      (setq wins (xwem-win-next wins)))
    (nreverse rlist)))

;;;###autoload
(defun xwem-win-selected (&optional frame)
  "Return selected window in selected frame."
  (unless frame
    (setq frame (xwem-frame-selected)))
  (when (xwem-frame-p frame)
    (xwem-frame-selwin frame)))

;;;###autoload
(defun xwem-win-selected-p (win)
  "Return non-nil if WIN is selected window."
  (and (xwem-win-p win)
       (eq win (xwem-frame-selwin (xwem-win-frame win)))))

;;;###autoload
(defun xwem-win-new (&optional params props)
  "Create new window with properties PROPS."
  (let ((nwin (apply 'make-xwem-win params))
	(rplist (copy-sequence xwem-win-default-properties)))

    ;; Prepare window properties
    (while props
      (setq rplist (plist-put rplist (car props) (cadr props)))
      (setq props (cddr props)))
    
    (setf (xwem-win-clients nwin) nil)	; list of clients
    (setf (xwem-win-props nwin) rplist)	; window properties
    (setf (xwem-win-cl nwin) nil)	; no visible client yet

    nwin))

(defun xwem-win-replace (old new)
  "Replace OLD window with contents of NEW window."
  (when (not (and (xwem-win-p old) (xwem-win-p new)))
    (error "Hmm .. one of OLD or NEW is not a xwem window."))

  (when (eq (xwem-frame-rootwin (xwem-win-frame old)) old)
    (setf (xwem-frame-rootwin (xwem-win-frame old)) new))

  (setf (xwem-win-x new) (xwem-win-x old))
  (setf (xwem-win-y new) (xwem-win-y old))
  (setf (xwem-win-width new) (xwem-win-width old))
  (setf (xwem-win-height new) (xwem-win-height old))

  (let ((tem))
    (setq tem (xwem-win-next old))
    (setf (xwem-win-next new) tem)
    (when (xwem-win-p tem)
      (setf (xwem-win-prev tem) new))

    (setq tem (xwem-win-prev old))
    (setf (xwem-win-prev new) tem)
    (when (xwem-win-p tem)
      (setf (xwem-win-next tem) new))

    (setq tem (xwem-win-parent old))
    (setf (xwem-win-parent new) tem)
    (when (xwem-win-p tem)
      (when (eq (xwem-win-vchild tem) old)
	(setf (xwem-win-vchild tem) new))
      (when (eq (xwem-win-hchild tem) old)
	(setf (xwem-win-hchild tem) new))
      )))

(defun xwem-win-make-parent (window)
  "Make dummy parent for WINDOW."
  (let ((pwin (xwem-win-new 
	       (list :frame (xwem-win-frame window)
		     :dead t		; XXX dead mean that window
					; can't contain clients
		     ))))

    (xwem-win-replace window pwin)

    (setf (xwem-win-next window) nil)
    (setf (xwem-win-prev window) nil)
    (setf (xwem-win-hchild window) nil)
    (setf (xwem-win-vchild window) nil)
    (setf (xwem-win-parent window) pwin)
    ))

;;;###autoload
(defun xwem-window-next (&optional window)
  "Return next window after WINDOW in canonical ordering of windows.
If omitted, WINDOW defaults to the `(xwem-win-selected)'."
  (let ((win (or window (xwem-win-selected)))
	while-exit tem)

    (while (and (not while-exit)
                (null (setq tem (xwem-win-next win))))
      (if (xwem-win-p (setq tem (xwem-win-parent win)))
	  (setq win tem)

	;;else
        (setq tem (xwem-frame-rootwin (xwem-win-frame win)))
        (setq while-exit t)		;break from loop
        ))

    (setq win tem)

    ;; now if we have a horizontal or vertical combination - find the
    ;; first child
    (while (cond ((xwem-win-p (xwem-win-child win))
		  (progn (setq win (xwem-win-child win)) t))
		 (t nil)))		;break
    win))

;;;###autoload
(defun xwem-window-next-vertical (&optional window)
  "Return next window which is vertically after WINDOW.
If WINDOW is not given `(xwem-win-selected)' will be used."
  (let* ((win (or window (xwem-win-selected)))
	 (root-win (xwem-frame-rootwin (xwem-win-frame win)))
	 (rwin nil))

    (when (eq root-win win)
      (while (cond ((xwem-win-p (xwem-win-hchild win))
		    (progn (setq win (xwem-win-hchild win)) t))
		   ((xwem-win-p (xwem-win-vchild win))
		    (progn (setq win (xwem-win-vchild win)) t))
		   (t (progn (setq rwin win) nil)))))
    (if rwin
	rwin
      
      ;; TODO: check for root window in frame
      (while (progn
               (if (and (xwem-win-p (xwem-win-parent win))
                        (xwem-win-p (xwem-win-vchild (xwem-win-parent win))))
		     (if (xwem-win-p (xwem-win-next win))
			 (setq rwin (xwem-win-next win))
		       (setq win (xwem-win-parent win)))
                 (setq win (xwem-win-parent win)))
               (and (null rwin) (not (eq win root-win)))))

      (if rwin
          rwin
        (while (progn
                 (if (xwem-win-p (xwem-win-hchild win))
                     (setq win (xwem-win-hchild win))
                   (if (xwem-win-p (xwem-win-vchild win))
                       (setq win (xwem-win-vchild win))
                     (setq rwin win)))
                 (null rwin)))
        rwin))))

;;;###autoload
(defun xwem-window-prev (&optional window)
  "Retrun previous window before WINDOW in canonical ordering of windows.
If ommitted, WINDOW defaults to the `(xwem-win-selected)'."
  (let* ((win (or window (xwem-win-selected)))
	 while-exit tem)

    (while (and (not while-exit)
		(null (setq tem (xwem-win-prev win))))

      (if (xwem-win-p (setq tem (xwem-win-parent win)))
          (setq win tem)

	(setq tem (xwem-frame-rootwin (xwem-win-frame win)))
	(setq while-exit t)		;break from loop
	))

    (setq win tem)

    ;; now if we have a horizontal or vertical combination find
    ;; the first child
    (while (and
	    (cond ((xwem-win-p (xwem-win-child win))
		   (progn (setq win (xwem-win-child win)) t))
		  (t nil))		;break
	    (progn
	      (while (xwem-win-p (xwem-win-next win))
		(setq win (xwem-win-next win)))
	      t)))
    win))

;;;###autoload
(defun xwem-window-other (cnt &optional window)
  "Return CNT's next window for WINDOW if CNT is greater then zero and
  previous if negative."
  (let ((ofn (if (>= cnt 0) 'xwem-window-next 'xwem-window-prev))
	(cnt (abs cnt))
	(win (or window (xwem-win-selected))))
    (while (> cnt 0)
      (setq win (funcall ofn win))
      (setq cnt (1- cnt)))
    win))

(defun xwem-win-xy-in-p (x y win &optional inc-gutter)
  "Returns non-nil if X Y lies in WIN.
If INC-GUTTER is non-nil, than include gutters width as WIN area."
  (let ((edges (xwem-win-pixel-edges win))
	(gw (if inc-gutter xwem-win-delim-width 0)))
    (and (>= x (- (nth 0 edges) gw))
	 (<= x (+ (nth 2 edges) gw))
	 (>= y (- (nth 1 edges) gw))
	 (<= y (+ (nth 3 edges) gw)))))

(defun xwem-window-at (x y &optional frame)
  "Returns window where X and Y lies in it."
  (catch 'found
    (xwem-win-map (lambda (win)
		    (when (xwem-win-xy-in-p x y win t)
		      (throw 'found win)))
                  (xwem-frame-selwin (or frame (xwem-frame-selected))))
    ))

;;; -- Moving around windows --
;;
(defun xwem-winmove-distance ()
  "Returns distance between windows."
  (+ xwem-win-delim-width 0))

(defun xwem-winmove-refloc (&optional arg window)
  "Calculates the reference location for directional window selection.
Returns cons cell in form (hpos . vpos)."
  (let* ((effarg (if (null arg) 0 (prefix-numeric-value arg)))
	 (edges (xwem-win-pixel-edges window))
	 (top-left (cons (nth 0 edges) (nth 1 edges)))
	 (bot-right (cons (1- (nth 2 edges)) (1- (nth 3 edges)))))
    (cond ((> effarg 0) top-left)
	  ((< effarg 0) bot-right)
	  (t
	   ;; As if point in center of WINDOW
	   (cons (+ (nth 0 edges) (/ (- (nth 2 edges) (nth 0 edges)) 2))
		 (+ (nth 1 edges) (/ (- (nth 3 edges) (nth 1 edges)) 2)))))
    ))

(defun xwem-winmove-other-window (dir &optional arg window)
  "Calculates location of window to be moved to.
Returns cons cell (x . y)."
  (let ((edges (xwem-win-pixel-edges window))
	(refpoint (xwem-winmove-refloc arg window)))
    (cond ((eq dir 'left)
	   (cons (- (nth 0 edges)
		    (xwem-winmove-distance))
		 (cdr refpoint)))
	  ((eq dir 'up)
	   (cons (car refpoint)
		 (- (nth 1 edges)
		    (xwem-winmove-distance))))
	  ((eq dir 'right)
	   (cons (+ (nth 2 edges)
		    (xwem-winmove-distance))
		 (cdr refpoint)))
	  ((eq dir 'down)
	   (cons (car refpoint)
		 (+ (nth 3 edges)
		    (xwem-winmove-distance))))
	  (t (error "`xwem-winmove-other-window': Invalid direction %s" dir)))))
	  
(defun xwem-winmove-select (dir &optional arg window)
  "Moves to the window in DIR direction."
  (let* ((frame (xwem-win-frame (or window (xwem-win-selected))))
         (owin-loc (xwem-winmove-other-window dir arg window))
	 (x (car owin-loc))
	 (y (cdr owin-loc))
	 (owin (xwem-window-at x y frame)))

    (when xwem-win-winmove-allow-jupming
      (let ((rwin (xwem-frame-rootwin (xwem-win-frame (or window (xwem-win-selected))))))
	(when (not (xwem-win-xy-in-p x y rwin))
	  ;; we are outside root window
	  (setq owin-loc
		(cond ((eq dir 'left)
		       (cons (- (nth 2 (xwem-win-pixel-edges rwin))
				(abs x)) y))
		      ((eq dir 'right)
		       (cons (- x (nth 2 (xwem-win-pixel-edges rwin)))
			     y))
		      ((eq dir 'up)
		       (cons x (- (nth 3 (xwem-win-pixel-edges rwin))
				  (abs y))))
		      ((eq dir 'down)
		       (cons x (+ (- y (nth 3 (xwem-win-pixel-edges rwin))) (nth 1 (xwem-win-pixel-edges rwin)))))
		      (t (error "invalid direction."))))
	  (setq owin (xwem-window-at (car owin-loc) (cdr owin-loc) frame)))))
    (if (not (xwem-win-p owin))
	(xwem-message 'err "No window at %s" dir)

	(xwem-window-select-maybe-redraw owin))
    ))

;;;###autoload(autoload 'xwem-winmove-up "xwem-win" "" t)
(define-xwem-command xwem-winmove-up (arg)
  "Selects window that up for selected."
  (xwem-interactive "P")
  (xwem-winmove-select 'up arg))

;;;###autoload(autoload 'xwem-winmove-down "xwem-win" "" t)
(define-xwem-command xwem-winmove-down (arg)
  "Selects window that down for selected."
  (xwem-interactive "P")
  (xwem-winmove-select 'down arg))

;;;###autoload(autoload 'xwem-winmove-left "xwem-win" "" t)
(define-xwem-command xwem-winmove-left (arg)
  "Selects window that left for selected."
  (xwem-interactive "P")
  (xwem-winmove-select 'left arg))

;;;###autoload(autoload 'xwem-winmove-right "xwem-win" "" t)
(define-xwem-command xwem-winmove-right (arg)
  "Selects window that right for selected."
  (xwem-interactive "P")
  (xwem-winmove-select 'right arg))

;;; 
;;;###autoload
(defun xwem-window-select (window)
  "Select WINDOW as current for WINDOW's frame.
Runs `xwem-win-switch-hook'."
  (let* ((wframe (xwem-win-frame window))
	 (ow (xwem-frame-selwin wframe)))
    (setf (xwem-frame-selwin wframe) window)

    ;; Now run switch hook
    (run-hook-with-args 'xwem-win-switch-hook ow window)))

;;;###autoload
(defun xwem-window-select-maybe-redraw (window)
  "Select window WINDOW and if maybe redraw WINDOW's frame."
  (let* ((wframe (xwem-win-frame window))
	 (ow (xwem-frame-selwin wframe)))
    (xwem-window-select window)
    (unless (eq ow window)
      (xwem-frame-redraw wframe))))

;;;###autoload
(defun xwem-window-set-pixsize (window nsize nodelete is-height)
  "Set pixsize for WINDOW."
  (let ((old-pixsize (if is-height 
			 (xwem-win-height window)
		       (xwem-win-width window)))
	machild michild pos)

    (if is-height
	(progn
	  (setf (xwem-win-height window) nsize)
	  (setq machild (xwem-win-vchild window))
	  (setq michild (xwem-win-hchild window)))
      (progn
	(setf (xwem-win-width window) nsize)
	(setq machild (xwem-win-hchild window))
	(setq michild (xwem-win-vchild window))))

    ;; Also refit window's client
    (when (xwem-cl-p (xwem-win-cl window))
      (xwem-manda-refit (xwem-win-cl window)))
  
    (cond ((xwem-win-p michild)
	   (mapc (lambda (child)
		   (if is-height
		       (setf (xwem-win-y child) (xwem-win-y window))
		     (setf (xwem-win-x child) (xwem-win-x window)))

		   (xwem-window-set-pixsize child nsize nodelete is-height))
		 (xwem-win-make-list-by-next michild)))

	  ((xwem-win-p machild)
	   ;; TODO: adjust geom for major child
	   (let* ((last-pos (if is-height
				(xwem-win-y window)
			      (xwem-win-x window)))
		  (first last-pos)
		  (last-old-pos 0)
		  (delims-size 0)
		  (old-pos nil)
		  (mchils (xwem-win-make-list-by-next machild)))

	     ;; Calculate width sum of all delimetrs
	     (mapc (lambda (el)
		     (when (xwem-win-p (xwem-win-next el))
		       (setq delims-size (+ delims-size xwem-win-delim-width))))
		   mchils)

	     (mapc (lambda (child)
		     (if is-height
			 (progn
			   (setq old-pos
				 (+ last-old-pos
				    (xwem-win-height child)))
			   (setf (xwem-win-y child) last-pos))
		       (progn
			 (setq old-pos (+ last-old-pos
					  (xwem-win-width child)))
			 (setf (xwem-win-x child) last-pos)))

		     (setq pos (/ (+ (* old-pos 
					(if (xwem-win-p (xwem-win-next child))
					    (- nsize delims-size)
					  nsize)
					2)
				     (- old-pixsize delims-size))
				  (* 2 (- old-pixsize delims-size))))

		     (xwem-window-set-pixsize child (- (+ pos first) last-pos) t is-height)

		     (setq last-pos (+ pos first xwem-win-delim-width))
		     (setq last-old-pos old-pos))
		   mchils))

	   ;; Now delete any children that became too small.
	   (when (not nodelete)
	     (mapc (lambda (child)
		     (if is-height
			 (xwem-window-set-pixsize
			  child (xwem-win-height child) nil t)
		       (xwem-window-set-pixsize
			child (xwem-win-width child) nil nil)))
		   (xwem-win-make-list-by-next machild)))
	   ))))
	 
;;;###autoload
(defun xwem-window-delete (&optional window)
  "Delete WINDOW. Default is `(xwem-win-selected)'."
  (let* ((win (or window (xwem-win-selected)))
         (frame (xwem-win-frame win))
         dclients owin par pwin)
    
    (if (null (xwem-win-parent win))
	(progn
	  ;; win is top level window
	  ;; TODO: should I delete frame?
	  nil)

      ;; Normal window - delete it
      (setq par (xwem-win-parent win))
      (setq pwin (xwem-win-selected frame))
      (while (and (xwem-win-p pwin) (not (eq pwin win)))
        (setq pwin (xwem-win-parent pwin)))

      ;; If we're going to delete selected window then we should
      ;; update selected window and demanage client, and maybe
      ;; collect its clients to other window.
      (when (xwem-cl-p (xwem-win-cl win))
        (xwem-client-hide (xwem-win-cl win) 'managed))
      (setq dclients (xwem-win-make-cl-list win))
      (setq owin (xwem-window-other 1 win))

       ;; Try to collect DCLINTS in other window.
      (when (and xwem-win-collect-deleted-clients dclients)
        (mapc (lambda (cl)
                (setf (xwem-cl-win cl) owin))
              dclients)
        (run-hook-with-args 'xwem-win-clients-change-hook owin))

      (when (eq pwin win)
        (xwem-window-select (xwem-window-next win)))

      ;; close that hole in list
      ;; XXX: check h v h split then delete right win ...
      (when (xwem-win-p (xwem-win-next win))
        (setf (xwem-win-prev (xwem-win-next win)) (xwem-win-prev win)))
      (when (xwem-win-p (xwem-win-prev win))
        (setf (xwem-win-next (xwem-win-prev win)) (xwem-win-next win)))
      (when (eq win (xwem-win-hchild par))
        (setf (xwem-win-hchild par) (xwem-win-next win)))
      (when (eq win (xwem-win-vchild par))
        (setf (xwem-win-vchild par) (xwem-win-next win)))

      ;; TODO: adjust the geometry
      (let ((sib (xwem-win-prev win)))
        (when (null sib)
          (setq sib (xwem-win-next win))
          (setf (xwem-win-x sib) (xwem-win-x win))
          (setf (xwem-win-y sib) (xwem-win-y win)))
	  
        (when (xwem-win-p (xwem-win-vchild par))
          (xwem-window-set-pixsize
           sib
           (+ (xwem-win-height sib)
              (xwem-win-height win)
              xwem-win-delim-width)
           t t))

        (when (xwem-win-p (xwem-win-hchild par))
          (xwem-window-set-pixsize
           sib
           (+ (xwem-win-width sib)
              (xwem-win-width win)
              xwem-win-delim-width)
           t nil)))

      ;; If parent now has only one child put child into parent
      ;; place
      (when (null (xwem-win-next (xwem-win-child par)))
        (xwem-win-replace par (xwem-win-child par))
        (xwem-win-mark-deleted par))

      ;; Since we deleting combination of windows we should delete
      ;; all childs
      (when (xwem-win-p (xwem-win-child win))
        (xwem-win-delete-subwindows (xwem-win-child win)))

      (xwem-win-mark-deleted win)

      ;; Now run on-delete hooks
      (run-hook-with-args 'xwem-win-delete-hook win owin))
    ))

;;;###autoload
(defun xwem-window-delete-others (&optional window)
  "Delete all xwem windows other then WINDOW."
  (let ((win (or window (xwem-win-selected))))
    (xwem-win-map (lambda (ww)
                    (when (not (eq ww win))
                      (xwem-window-delete ww)))
                  win)))

(defun xwem-win-delete-subwindows (win)
  "Delete all childs of WIN."
  (when (xwem-win-p (xwem-win-next win))
    (xwem-win-delete-subwindows (xwem-win-next win)))
  (when (xwem-win-child win)
    (xwem-win-delete-subwindows (xwem-win-child win)))

  (xwem-win-mark-deleted win))

;;;###autoload
(defun xwem-win-map (fn &optional window)
  "Apply FN to FRAME-WIN and each subwindow.
FN will be called with a window as argument.
If ommitted, WINDOW defaults to the `(xwem-win-selected)'."
  (let* ((start-win (or window (xwem-win-selected)))
	 cur-win)

    (when start-win
      (funcall fn start-win)
      (setq cur-win (xwem-window-next start-win)))

    (while (not (eq cur-win start-win))
      (funcall fn cur-win)
      (setq cur-win (xwem-window-next cur-win)))))

(defun xwem-win-count (&optional window)
  "Count windows."
  (let ((cnt 0))
    (xwem-win-map (lambda (win) (setq cnt (+ cnt 1))) window)
    cnt))

;;;###autoload(autoload 'xwem-balance-windows "xwem-win" "" t)
(define-xwem-command xwem-balance-windows (frame)
  "Make all windows in FRAME the same height or width."
  (xwem-interactive (list (xwem-frame-selected)))

  (let* ((frootw (xwem-frame-rootwin frame))
	 (height-p (xwem-win-p (xwem-win-vchild frootw)))
	 (getsizefn (lambda (w) (if height-p (xwem-win-height w) (xwem-win-width w))))
	 (wins (xwem-win-make-list-by-next (xwem-win-child frootw)))
	 size)

    (when wins
      (setq size (/ (funcall getsizefn frootw) (length wins)))

      (mapc (lambda (w)
	      (xwem-window-enlarge (- size (funcall getsizefn w)) height-p w))
	    wins)

      (xwem-frame-draw frame nil))))

;;;###(autoload 'xwem-transpose-windows "xwem-win")
(define-xwem-command xwem-transpose-windows (arg)
  "Transpose selected window with other window.
Prefix ARG directly passed to `xwem-window-other' to findout other
window."
  (xwem-interactive "p")

  (let ((cw (xwem-win-selected))
	(ow (xwem-window-other arg)))
    (if (or (eq cw ow) (not (xwem-win-p cw)) (not (xwem-win-p ow)))
	(xwem-message 'warn "Can't transpose.")

      (let ((cwcl (xwem-win-cl cw))
	    (cwcls (xwem-win-make-cl-list cw))
	    (owcl (xwem-win-cl ow))
	    (owcls (xwem-win-make-cl-list ow)))
	
	(setf (xwem-win-cl cw) nil)
        (when (xwem-cl-p cwcl)
          (setf (xwem-cl-state cwcl) 'unknown))
	(setf (xwem-win-cl ow) nil)
        (when (xwem-cl-p owcl)
          (setf (xwem-cl-state owcl) 'unknown))

        (mapc (lambda (cl)
                (setf (xwem-cl-win cl) ow))
              cwcls)
        (mapc (lambda (cl)
                (setf (xwem-cl-win cl) cw))
              owcls)

	(when (xwem-cl-p cwcl)
	  (xwem-manda-manage cwcl ow))

	(when (xwem-cl-p owcl)
	  (xwem-manda-manage owcl cw))

        (run-hook-with-args 'xwem-win-clients-change-hook cw)
        (run-hook-with-args 'xwem-win-clients-change-hook ow)
	))
    ))
  
;;;###autoload
(defun xwem-win-only-one-p (&optional window)
  "Return non-nil if WINDOW is only one in chain.
If WINDOW ommitted `(xwem-win-selected)' used."
  (let ((sl-win (if window window (xwem-win-selected))))
    (= 1 (xwem-win-count sl-win))))

(defun xwem-window-list (&optional frame)
  "Return list of windows for FRAME.
If FRAME ommitted then `xwem-current-frame' will be used."
  (let* ((wlf (if frame frame (xwem-frame-selected)))
	 (win (xwem-frame-rootwin wlf))
	 (rlist nil))
    (xwem-win-map
     (function (lambda (win)
		 (setq rlist (cons win rlist))))
     win)
    rlist))

(defun xwem-win-pixel-edges (&optional window)
  "Return a list of the pixel edge coordinates of WINDOW.
\\(LEFT TOP RIGHT BOTTOM\\), all relative to 0, 0 at top left corner of
frame.  The frame title are considered to be outside of this area."
  (let* ((win (if window window (xwem-win-selected))))
  (list (xwem-win-x win)
	(xwem-win-y win)
	(+ (xwem-win-x win) (xwem-win-width win))
	(+ (xwem-win-y win) (xwem-win-height win)))))

(defun xwem-win-pixel-width (&optional window)
  "Retrun width in pixels of WINDOW."
  (xwem-win-width (if window window (xwem-win-selected))))

(defun xwem-win-pixel-height (&optional window)
  "Return height in pixels of WINDOW."
  (xwem-win-height (if window window (xwem-win-selected))))

(defun xwem-win-pixels-steps (&optional window)
  "Convert size in pixels to size in steps for WINDOW."
  (error "xwem-win-pixels-steps not yet implemented."))

(defun xwem-win-steps-pixels (&optional window)
  "Convert size in steps to size in pixels for WINDOW."
  (error "xwem-win-steps-pixels not yet implemented."))

;;; START: Window configurations section

;;;###autoload
(defun xwem-window-configuration (&optional frame)
  "Return an object representing the current window configuration of xwem FRAME.
If FRAME is nil or ommited, use the sected frame."
  (let ((frm (or frame (xwem-frame-selected))))
    (when (xwem-frame-p frm)
      (make-xwem-win-config
       :frame frm
       :frame-width (xwem-frame-width frm)
       :frame-height (xwem-frame-height frm)
       :current-cl (xwem-win-cl (xwem-frame-selwin frm))
       :min-width xwem-win-min-width :min-height xwem-win-min-height
       :saved-root-window (xwem-win-root->saved-win (xwem-frame-rootwin frm))))))

(defun xwem-win-root->saved-win (win)
  "Convert an xwem root WIN into a tree of saved-window structures."
  (let ((saved-win
	 (make-xwem-win-saved
	  :x (xwem-win-x win) :y (xwem-win-y win) :width (xwem-win-width win) :height (xwem-win-height win)
          :clients (xwem-win-clients win)
	  :cl (xwem-win-cl win)
	  :props (copy-sequence (xwem-win-props win))
	  :expectances (copy-sequence (xwem-win-expectances win))
	  :currentp (xwem-win-selected-p win)
	  :first-hchild (and (xwem-win-p (xwem-win-hchild win))
			     (xwem-win-root->saved-win (xwem-win-hchild win)))
	  :first-vchild (and (xwem-win-p (xwem-win-vchild win))
			     (xwem-win-root->saved-win (xwem-win-vchild win)))

	  :next (and (xwem-win-p (xwem-win-next win))
		     (xwem-win-root->saved-win (xwem-win-next win)))
	  )))
    saved-win))

(defun xwem-win-config-equal (win-cfg0 win-cfg1)
  "Return non-nil if two window configurations WIN-CFG0 and WIN-CFG1 are equal."
  (equal win-cfg0 win-cfg1))

;;;###autoload
(defun xwem-set-window-configuration (config)
  "Set window CONFIG."
  (let ((frame (xwem-win-config-frame config)))
    (when (and (xwem-frame-alive-p frame)
	       (not (xwem-win-config-equal config (xwem-window-configuration frame))))

      (xwem-frame-reduce-to-one-window frame)
      (xwem-frame-set-win-config-frame-size config)
      
      (let ((rwin (xwem-frame-rootwin frame))
	    (xwem-win-config-current-window))
	(declare (special xwem-win-config-current-window))
	
        (let ((xwem-win-switch-hook nil)  ; omit hooks
              (xwem-win-after-split-hook nil)) ; omit hooks
          (xwem-win-restore-saved-win config rwin
                                      (xwem-win-config-saved-root-window config)
                                      'vertical))

	(when xwem-win-config-current-window
	  (xwem-window-select xwem-win-config-current-window))
        ;; Why selecting frame needed?
;	  (xwem-frame-select (xwem-win-frame xwem-win-config-current-window)))

        ;; Outline windows and draw windows delimiters
        (xwem-frame-redraw frame)
        (xwem-win-draw-delims (xwem-frame-rootwin frame))

	(setq xwem-win-min-width (xwem-win-config-min-width config))
	(setq xwem-win-min-height (xwem-win-config-min-height config))
	))))

(defun xwem-frame-reduce-to-one-window (frame)
  "Delete all windows except the one."
  (let ((swin (xwem-frame-selwin frame)))
    (xwem-win-map (lambda (ww)
                    (when (not (eq ww swin))
                      (xwem-window-delete ww)))
                  swin)))

(defun xwem-frame-set-win-config-frame-size (config)
  "Restore FRAME size of a window configuration CONFIG."
  (xwem-frame-set-size (xwem-win-config-frame config)
    (xwem-win-config-frame-width config)
    (xwem-win-config-frame-height config)))

(defun xwem-win-restore-saved-win (config win saved-win direction)
  "Within CONFIG, restore WIN to the state of SAVED-WIN."
  (and (xwem-win-saved-next saved-win)
       (progn
	 (xwem-win-split win direction nil t)
	 (xwem-win-restore-saved-win config (xwem-win-next win)
				     (xwem-win-saved-next saved-win)
				     direction)))
  
  (when (xwem-win-saved-first-hchild saved-win)
    (xwem-win-restore-saved-win config win
				(xwem-win-saved-first-hchild saved-win)
				'horizontal))
  (when (xwem-win-saved-first-vchild saved-win)
    (xwem-win-restore-saved-win config win
				(xwem-win-saved-first-vchild saved-win)
				'vertical))

  (xwem-win-restore-win-params config win saved-win)
  )

(defun xwem-win-restore-win-params (config win saved-win)
  "Restore the windown parameters stored in SAVED-WIN on WIN."
  (declare (special xwem-win-config-current-window))
  (let ((cln (xwem-win-saved-cl saved-win)))
    (setf (xwem-win-props win) (copy-sequence (xwem-win-saved-props saved-win)))
    (setf (xwem-win-expectances win) (copy-sequence (xwem-win-saved-expectances saved-win)))

    ;; Collect clients
    (mapc (lambda (cl)
            (when (xwem-cl-alive-p cl)
              (xwem-manda-manage cl win)))
          (xwem-win-saved-clients saved-win))

    (when (xwem-cl-alive-p cln)
      (xwem-manda-manage cln win))

    (when (and (not (xwem-win-saved-first-hchild saved-win))
	       (not (xwem-win-saved-first-vchild saved-win)))
      (when (not (eq win (xwem-frame-rootwin (xwem-win-frame win))))
	(xwem-window-enlarge (- (xwem-win-saved-width saved-win)
				(xwem-win-width win))
			     nil
			     win)
	(xwem-window-enlarge (- (xwem-win-saved-height saved-win)
				(xwem-win-height win))
			     t
			     win)))

    (when (xwem-win-saved-currentp saved-win)
      (setq xwem-win-config-current-window win))
    ))

;;; END:

;;;###autoload(autoload 'xwem-window-configuration-to-register "xwem-win" "" t)
(define-xwem-command xwem-window-configuration-to-register (reg)
  "Save current window configuration to register."
  (xwem-interactive "rRegister: ")

  (xwem-message 'todo "Save window configuration to `%S' register." reg))

;;;###autoload
(defun xwem-win-split (&optional window how new-size no-redraw)
  "Split WINDOW.
When WINDOW is ommitted `(xwem-win-selected)' used.
HOW is 'vertical of 'horizontal, default is 'horizontal.
If NEW-SIZE is given make WINDOW NEW-SIZE pixels bigger after split."
  (let* ((sp-win (or window (xwem-win-selected)))
	 (sp-how (or how 'horizontal))
	 (hor (equal sp-how 'horizontal))
	 (nwin (xwem-win-new (list :frame (xwem-win-frame sp-win))))
	 (nsiz (or new-size 0))
	 psize psize1 psize2 remd)

    (if hor
	(progn
	  (setq psize (/ (- (xwem-win-width sp-win)
			    xwem-win-delim-width) 2))
	  (setq remd (% (- (xwem-win-width sp-win)
			   xwem-win-delim-width) 2)))
      (progn
	  (setq psize (/ (- (xwem-win-height sp-win)
			    xwem-win-delim-width) 2))
	  (setq remd (% (- (xwem-win-height sp-win)
			   xwem-win-delim-width) 2))))

    (setq psize1 (+ psize nsiz))
    (setq psize2 (+ psize remd (- nsiz)))
    ;; Check that minimal widht or height is not exceeded
    (X-Dpy-log (xwem-dpy) "Doing split hor=%s psize=%d\n" 'hor 'psize)
    (if hor
	;; horizontal split
	(if (or (< psize1 xwem-win-min-width)
		(< psize2 xwem-win-min-width))
	    (error "Can't do split.")
	  
	  ;; TODO:
	  (when (or (null (xwem-win-parent sp-win))
		    (null (xwem-win-hchild (xwem-win-parent sp-win))))
	    (xwem-win-make-parent sp-win)
	    (setf (xwem-win-hchild (xwem-win-parent sp-win)) sp-win)))

      ;; vertical split
      (if (or (< psize1 xwem-win-min-height)
	      (< psize2 xwem-win-min-height))
	  (error "Can't do split.")
	;; TODO
	(when (or (null (xwem-win-parent sp-win))
		  (null (xwem-win-vchild (xwem-win-parent sp-win))))
	  (xwem-win-make-parent sp-win)
	  (setf (xwem-win-vchild (xwem-win-parent sp-win)) sp-win))))

    (setf (xwem-win-frame nwin) (xwem-win-frame sp-win))
    (setf (xwem-win-next nwin) (xwem-win-next sp-win))
    (when (xwem-win-p (xwem-win-next nwin))
      (setf (xwem-win-prev (xwem-win-next nwin)) nwin))
    (setf (xwem-win-prev nwin) sp-win)
    (setf (xwem-win-next sp-win) nwin)
    (setf (xwem-win-parent nwin) (xwem-win-parent sp-win))
    
    ;; TODO: adjust geometry
    (if hor
	(progn
	  (setf (xwem-win-width sp-win) psize1)
	  (setf (xwem-win-x nwin)
		(+ xwem-win-delim-width
		   (xwem-win-x sp-win)
		   (xwem-win-width sp-win)))
	  (setf (xwem-win-y nwin) (xwem-win-y sp-win))
	  (setf (xwem-win-width  nwin) psize2)
	  (setf (xwem-win-height nwin) (xwem-win-height sp-win)))
      (progn
	(setf (xwem-win-height sp-win) psize1)
	(setf (xwem-win-x nwin) (xwem-win-x sp-win))
	(setf (xwem-win-y nwin)
	      (+ xwem-win-delim-width
		 (xwem-win-y sp-win)
		 (xwem-win-height sp-win)))
	(setf (xwem-win-width nwin) (xwem-win-width sp-win))
	(setf (xwem-win-height nwin) psize2)))

    ;; Client refitting if needed
    (when (xwem-cl-p (xwem-win-cl sp-win))
      (xwem-manda-refit (xwem-win-cl sp-win)))

    ;; Redraw delimiters if needed
    (unless no-redraw
      (xwem-win-draw-delims (xwem-win-parent nwin)))

    ;; NOTE: nwin is (xwem-win-next sp-win)
    (run-hook-with-args 'xwem-win-after-split-hook sp-win how new-size)
    ))

;;;###autoload
(defun xwem-win-draw-delims (win)
  "Draw delimetrs in window WIN."
  (let ((wf (xwem-win-frame win))
	(hc (xwem-win-hchild win))
	(vc (xwem-win-vchild win)))
    
    (X-Dpy-send-excursion (xwem-dpy)
      (while (xwem-win-p hc)
	;; For horizontal split
	(when (xwem-win-p (xwem-win-next hc))
	  (xwem-misc-draw-bar
	   (xwem-dpy) (xwem-frame-xwin wf)
	   (xwem-face-get-gc 'xwem-face-win-delimeter)
	   (xwem-face-get-gc 'xwem-face-white)
	   (xwem-face-get-gc 'xwem-face-win-delimeter-shadow)
	   (+ (xwem-win-x hc) (xwem-win-width hc))
	   (xwem-win-y hc)
	   xwem-win-delim-width
	   (xwem-win-height hc)
	   xwem-win-delim-shadow-thicksness))

	(xwem-win-draw-delims hc)
	(setq hc (xwem-win-next hc)))

      (while (xwem-win-p vc)
	;; For vertical split
	(when (xwem-win-p (xwem-win-next vc))
	  (xwem-misc-draw-bar 
	   (xwem-dpy) (xwem-frame-xwin wf)
	   (xwem-face-get-gc 'xwem-face-win-delimeter)
	   (xwem-face-get-gc 'xwem-face-white)
	   (xwem-face-get-gc 'xwem-face-win-delimeter-shadow)
	   (xwem-win-x vc)
	   (+ (xwem-win-y vc) (xwem-win-height vc))
	   (xwem-win-width vc)
	   xwem-win-delim-width
	   xwem-win-delim-shadow-thicksness))

	(xwem-win-draw-delims vc)
	(setq vc (xwem-win-next vc)))
      )))

(defun xwem-window-change-size (win delta height-p)
  "Change WIN size to old-size plus DELTA."
  (let ((getsizefn (lambda (www)
		     (if height-p (xwem-win-height www)
		       (xwem-win-width www))))
	(setsizefn (lambda (www nsize)
		     (xwem-window-set-pixsize www nsize nil height-p)))
	(par nil)
	wsize minsize maxdelta)

    (if (eq win (xwem-frame-rootwin (xwem-win-frame win)))
	(xwem-message 'warn "Can't change size of frame root window.")

      (setq par (xwem-win-parent win))
      (while (and par (not (if height-p
			       (xwem-win-vchild par)
			     (xwem-win-hchild par))))
	(setq win par)
	(setq par (xwem-win-parent win)))

      (setq wsize (funcall getsizefn win))
      (setq minsize (if height-p xwem-win-min-height xwem-win-min-width))
      (if (< (+ wsize delta) minsize)
	  (xwem-message 'warn "Minimum size exceeded while in `xwem-window-change-size'")
	
	(setq maxdelta (if par (- (funcall getsizefn par) wsize)
			 (if (xwem-win-next win) (- (funcall getsizefn (xwem-win-next win))
						    minsize)
			   (if (xwem-win-prev win) (- (funcall getsizefn (xwem-win-prev win))
						      minsize)
			     (setq delta 0)))))
	(when (> delta maxdelta)
	  (setq delta maxdelta))

	(unless (= delta 0)
	  (if (and (xwem-win-p (xwem-win-next win))
		   (>= (- (funcall getsizefn (xwem-win-next win)) delta) minsize))
	      (progn
		(X-Dpy-log (xwem-dpy) "HERE IN xwem-window-change-size: delta=%d\n" 'delta)
		(if height-p
		    (setf (xwem-win-y (xwem-win-next win))
			  (+ (xwem-win-y (xwem-win-next win)) delta))
		  (setf (xwem-win-x (xwem-win-next win))
			(+ (xwem-win-x (xwem-win-next win)) delta)))
		(funcall setsizefn (xwem-win-next win) 
			 (- (funcall getsizefn (xwem-win-next win)) delta))
		(funcall setsizefn win (+ (funcall getsizefn win) delta)))

	    (if (and (xwem-win-p (xwem-win-prev win))
		     (>= (- (funcall getsizefn (xwem-win-prev win)) delta) minsize))
		(progn
		  (funcall setsizefn (xwem-win-prev win)
			   (- (funcall getsizefn (xwem-win-prev win)) delta))
		  (if height-p
		      (setf (xwem-win-y win) (- (xwem-win-y win) delta))
		    (setf (xwem-win-x win) (- (xwem-win-x win) delta)))
		  (funcall setsizefn win (+ (funcall getsizefn win) delta)))
	      
	      ;; If we are here than changing window size possible
	      ;; causes changing parent size.
	      (let ((opht (funcall getsizefn par))
		    delta1)
		(if (<= opht (+ delta wsize))
		    (setq delta1 (* opht opht 2))

		  (setq delta1 (/ (* delta opht 100) (* (- opht wsize delta) 100))))
		
		(if height-p
		    (setf (xwem-win-height par) (+ opht delta1))
		  (setf (xwem-win-width par) (+ opht delta1)))

		(funcall setsizefn win (+ wsize delta1))
		(funcall setsizefn par opht)
		))))
	))))

;;;###autoload
(defun xwem-window-enlarge (n &optional height-p window)
  "Make WINDOW N pixels bigger.
If HEIGHT-P is non-nil then enlarge vertically.
If WINDOW is ommited then selected window will be used."
  (xwem-window-change-size (or window (xwem-win-selected)) n height-p))

;;;###autoload
(defun xwem-window-shrink (n &optional height-p window)
  "Make WINDON N pixels smaller.
If HEIGHT-P is non-nil then shrink vertically.
If WINDOW is ommited then selected window will be used."
  (xwem-window-change-size (or  window (xwem-win-selected)) (- n) height-p))

(defun xwem-win-focus (owin nwin)
  "Focus NWIN. To be used in `xwem-win-switch-hook'."
  (xwem-focus-set nwin))

;;;###autoload
(defun xwem-win-make-cl-list (win)
  "Makes list of CLs that belongs to WIN."
  (let (rcls)
    (mapc (lambda (cl)
            (when (xwem-cl-managed-p cl win)
              (setq rcls (cons cl rcls))))
          xwem-clients)
    (nreverse rcls)))

;;;###autoload
(defun xwem-win-make-cl-list-sort-by-recency (win)
  "Return list of clients managed in WIN, sorted by recency field."
  (let ((cls (xwem-win-make-cl-list win)))
    (sort cls (lambda (cl1 cl2)
		(let ((rc1 (xwem-cl-recency cl1))
		      (rc2 (xwem-cl-recency cl2)))

		  (if (or (not rc1) (not rc2))
		      (not (null rc1))
		    
		    (cond ((> (nth 0 rc1) (nth 0 rc2))
			   t)
			  ((< (nth 0 rc1) (nth 0 rc2))
			   nil)
			  (t (cond ((> (nth 1 rc1) (nth 1 rc2))
				    t)
				   ((< (nth 1 rc1) (nth 1 rc2))
				    nil)
				   (t (cond ((> (nth 2 rc1) (nth 2 rc2))
					     t)
					    ((< (nth 2 rc1) (nth 2 rc2))
					     nil)
					    (t t))))))))))))
	  
;;;###autoload
(defun xwem-init-win ()
  "Initialize xwem windows. Now just installs `xwem-win-switch-hook'"
  (add-hook 'xwem-win-switch-hook 'xwem-win-focus)
  )

(provide 'xwem-win)

;;; xwem-win.el ends here
