;; 
;;  Copyright (c) 2005-2013 uim Project https://github.com/uim/uim
;;
;;  All rights reserved.
;;
;;  Redistribution and use in source and binary forms, with or
;;  without modification, are permitted provided that the
;;  following conditions are met:
;;
;;  1. Redistributions of source code must retain the above
;;     copyright notice, this list of conditions and the
;;     following disclaimer.
;;  2. 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.
;;  3. Neither the name of authors nor the names of its
;;     contributors may 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 COPYRIGHT OWNER 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.
;;

(require 'uim-var)
(require 'uim-keymap)
(when uim-xemacs
  (require 'overlay)
  (load "mule-util"))

(require 'uim-debug)
(require 'uim-util)
(require 'uim-key)

(require 'uim-preedit)
(require 'uim-candidate)
(require 'uim-helper)


;;
;; Add uim-mode to minor modes
;;
(defun uim-init-minor-mode ()
  ;; register uim-mode to minor-mode alist
  (if (not (assq 'uim-mode minor-mode-alist))
      (setq minor-mode-alist
	    (cons
	     (cons 'uim-mode '(uim-mode-line-string))
	     minor-mode-alist)))

  ;; register keymap of uim.el to minor-mode keymap alist
  (if (not (assq 'uim-mode minor-mode-map-alist))
      (setq minor-mode-map-alist
	    (cons
	     (cons 'uim-mode nil)
	     minor-mode-map-alist))))


;;
;; Get context ID
;;   If there are any used IDs, use one of them preferentially
;;
(defun uim-get-context-id ()
  (let (id)
    (if (not (null uim-context-id-recycle))
	(progn
	  (setq id (car uim-context-id-recycle))
	  (setq uim-context-id-recycle (cdr uim-context-id-recycle)))
      (setq id uim-context-id-max)
      (setq uim-context-id-max (+ uim-context-id-max 1)))
    id))

;;
;; Release context ID
;;   The released ID is added to the used-ID's list
;;
(defun uim-release-context-id (id)
  (setq uim-context-id-recycle (cons id uim-context-id-recycle)))

;;
;; Validation IM
;;
(defun uim-check-im (im)
  (if (assoc im uim-im-alist)
      t
    (message (format "uim.el: invalid IM engine: %s" im))))

;;
;; Get default IM engine name.
;;
(defun uim-get-default-engine ()
  (if (and uim-default-im-engine
	   (uim-check-im uim-default-im-engine))
      uim-default-im-engine
    nil))


;;
;; Update default IM name of the uim-el-agent.
;;   (for uim-im-switcher 
;;       "Change whole desktop" and "Change this text area only")
;;
(defun uim-update-default-engine (engine)

  (when (not (equal uim-default-im-engine engine))
    (setq uim-default-im-engine engine)
    (run-hooks 'uim-update-default-engine-hook)
    ))

;;
;; Update IM name currently used in this buffer.
;;   (for uim-im-switcher "Change this text area only")
;;
(defun uim-update-current-engine (engine)

  (setq engine (car engine))

  (when (not (equal uim-current-im-engine engine))
    (setq uim-current-im-engine engine) 
    (run-hooks 'uim-update-current-engine-hook))

  ;; update current decoding code   
  (let ((newcode (uim-get-emacs-encoding uim-current-im-engine)))
    (when (not (string= newcode uim-decoding-code))
      (setq uim-decoding-code newcode)
      (uim-change-process-encoding uim-decoding-code)))
  )


;;	
;; Create new context
;;
(defun uim-context-create ()
  (let (id)
    ;; get new context ID
    (setq id (uim-get-context-id))

    ;;    Default context's encoding is determined by 
    ;;  the name of default IM 
    (uim-do-send-recv-cmd 
     (format "%d NEW %s" id (uim-get-uim-encoding (uim-get-default-engine))))
    id))


;;
;; Delete context
;;   The context is kept until the buffer is killed
;;
(defun uim-delete-context ()
  (uim-do-send-recv-cmd (format "%d RELEASE" uim-context-id))
  (uim-release-context-id uim-context-id))


;;
;; Reset context
;;
(defun uim-reset-context ()
  (uim-do-send-recv-cmd (format "%d RESET" uim-context-id)))


;;
;; Get serial number for the next message
;;
(defun uim-get-communication-serial-number ()
  (if (= uim-communication-serial-number 65535)
      (setq uim-communication-serial-number 0))
  (setq uim-communication-serial-number
	(+ uim-communication-serial-number 1)))

;;
;; Focused 
;;
(defun uim-focused ()
  (if uim-preedit-keymap-enabled
      (progn
	;; If preedit strings and/or candidates are displayed,
	;; other keymaps of minor-mode-map-alist should be disabled.
	;; Since minor-mode-map-alist is not a buffer local variable, 
	;; we must re-enable the other keymaps of minor-mode-map-alist
	;; when the focus has moved to other buffer.
	;; So, we also need to re-disable other keymaps of minor-mode-map-alist
	;; when the focus has move to a buffer which have
	;; preedit strings and/or candidates.
	(uim-disable-other-minor-mode-map)
	(uim-enable-preedit-keymap))
    (uim-enable-mode-keymap))

  (uim-change-process-encoding uim-decoding-code)
  (setq uim-focused-buffer (current-buffer))
  (uim-do-send-recv-cmd (format "%d FOCUSED" uim-context-id))
  )

;;
;; Unfocused
;;
(defun uim-unfocused ()
  (setq uim-focused-buffer nil)
  ;; don't send a message to uim-el-agent if it has been dead
  (if uim-el-agent-process
      (uim-do-send-recv-cmd (format "%d UNFOCUSED" uim-context-id)))
  ;; Enable other keymaps of minor-mode-map-alist.  See uim-focused.
  (if uim-preedit-keymap-enabled
      (uim-enable-other-minor-mode-map))
  )


;;
;; Force disable uim-mode
;;
(defun uim-force-off ()
  ;; cleanup displayed preedit and candidate
  (setq uim-last-key-vector nil)
  (uim-process-agent-output '(("e")))
;  (uim-reset-context)
  (uim-release-context-id uim-context-id)
  (run-hooks 'uim-force-inactivate-hook)
  (if uim-mode
      (uim-mode-off))
  )

;;
;; Detect switching of current focused buffer
;;   This function is hooked on 'post-command-hook and 
;;  called after every operation
;;
(defun uim-post-command ()

  ;; detect switching of current buffer
  (when (not (eq uim-recent-buffer (current-buffer)))

    ;; update recent bufferto detect next change
    (setq uim-recent-buffer (current-buffer))
    
    ;; remove Uim focus from previous buffer
    (when (and (bufferp uim-focused-buffer)
	     (buffer-name uim-focused-buffer))
	(save-current-buffer
	  (set-buffer uim-focused-buffer)
	(when (and uim-initialized uim-mode)
	    (uim-unfocused)
	    )))
	    
    ;; set Uim focus to current buffer if it has Uim context
    (when (and uim-initialized uim-mode)
	  (uim-focused)
	  )
    )
  )


;;
;; Update IM label
;;
(defun uim-update-im-label ()
  (uim-do-send-recv-cmd (format "%d LABEL" uim-context-id)))


;;
;; Overwrite properties of current context
;;
(defun uim-prop-activate (proplist)
  (mapcar 
   '(lambda (x)
      (uim-do-send-recv-cmd (format "%d PROP %s" uim-context-id x)))
   proplist))

;;
;; Update process encoding 
;; 
(defun uim-change-process-encoding (outcode)
  (set-process-coding-system uim-el-agent-process outcode 'iso-8859-1)
  )

;; 
;; Get IM information from uim-im-alist
;;
(defun uim-get-im-info (im field)
  (nth field (or (assoc im uim-im-alist)
		 (assoc "direct" uim-im-alist))))

(defun uim-get-uim-lang (im)
  (uim-get-im-info im 1))

(defun uim-get-emacs-lang (im)
  (uim-get-im-info im 2))

(defun uim-get-emacs-encoding (im)
  (uim-get-im-info im 3))

(defun uim-get-uim-encoding (im)
  (uim-get-im-info im 4))


;;
;; Change IM engine.
;; 
(defun uim-change-im (im)
  ;; change decoding method temporarily to receive encoded IM label etc.
  (uim-change-process-encoding (uim-get-emacs-encoding im))
  (uim-do-send-recv-cmd 
   (format "%d CHANGE %s" uim-context-id im))

  (if uim-default-im-prop
      (uim-prop-activate uim-default-im-prop)))


;;
;; Set each IM's output encoding 
;;
(defun uim-set-encoding (im encoding)
  (uim-do-send-recv-cmd 
   (format "0 SETENC %s %s" im encoding)))



;;
;; Initialize each buffer 
;;   Called when a context has been created
;;
(defun uim-init-buffer ()

  ;; if buffer has no context
  (setq uim-context-id (uim-context-create))

  ;; redraw Uim related objects when window has been resized
  (when uim-xemacs
    (make-local-hook 'window-configuration-change-hook))
  (add-hook 'window-configuration-change-hook 'uim-window-changed nil 'local)

  (when uim-xemacs
    (make-local-hook 'kill-buffer-hook))
  (add-hook 'kill-buffer-hook 'uim-kill-buffer nil 'local)

  ;; hide candidate/preedit when buffer-save has been called
  (add-hook 'local-write-file-hooks 
	    '(lambda ()
	       (if (or uim-preedit-displayed uim-candidate-displayed)
		   (uim-process-agent-output '(("e"))))))

  ;; change IM engine to uim-default-im-engine
  (if (and uim-default-im-engine
	   (uim-check-im uim-default-im-engine))
      (uim-change-im uim-default-im-engine))

  ;; set properties
  (if uim-default-im-prop
      (uim-prop-activate uim-default-im-prop))

  (run-hooks 'uim-buffer-init-hook)

  (setq uim-initialized t)
  )


(defun uim-im-init ()
  (when (not uim-im-initialized)
    (uim-init-im-alist)
    (uim-init-im-encoding)
    (setq uim-im-initialized t)
    ))


;;
;; Activate uim
;;
(defun uim-mode-on ()

  (if uim-mode
      (message "uim.el: uim-mode is already activated. (buffer %s)" 
	       (current-buffer)))

  ;; Initialize IM encoding
  (uim-im-init)

  ;; override some functions
  (uim-this-command-keys-override)
  (if (= emacs-major-version 21)
      (uim-read-char-exclusive-override))

  (if (not buffer-read-only)
      (progn 

	;; initialize context and buffer
	(when (not uim-initialized)
	  (uim-init-buffer))

	;; enable and update keymap
	(uim-enable-mode-keymap)

	;; focus to current context
	(uim-focused)

	;; update mode-line
	(force-mode-line-update)

	;; really enable minor-mode
	(setq uim-mode t))

    ;; don't activate if the current buffer is read-only
    (message "uim.el: This buffer is read-only.")
    )
  )


;;
;; Deactivate Uim
;;
(defun uim-mode-off ()
  
  (if (not uim-mode)
      (message "uim.el: uim-mode is already inactivated."))

  (uim-unfocused)

  (uim-process-agent-output '(("e")))

  ;; update mode-line
  (force-mode-line-update)

  ;; Disable uim-mode
  (setq uim-mode nil)

  ;; restore original functions
  (uim-this-command-keys-restore)
  (if (= emacs-major-version 21)
      (uim-read-char-exclusive-restore))

  )


;;
;; Release context when buffer is killed or major-mode changed
;;   This function is called by uim-initialized buffer only
;;
(defun uim-kill-buffer ()

  ;; unfocus
  (if (eq uim-focused-buffer (current-buffer))
      (uim-unfocused))

  ;; delete context
  (uim-delete-context)
  )


;;
;; Reset context
;;
(defun uim-change-major-mode ()

  (when uim-initialized
       (uim-unfocused)
       (uim-delete-context))
  )


;;
;; start uim-el-agent process
;;
(defun uim-el-agent-start ()
  (let (proc
	(buffer (get-buffer-create uim-el-agent-buffer-name)) )

    (if (not uim-el-helper-agent-process)
	(setq uim-el-helper-agent-process (uim-el-helper-agent-start)))

    ;; erase buffer
    (save-current-buffer
      (set-buffer buffer) (erase-buffer))

    (message "uim.el: starting uim-el-agent...")

    (setq proc (start-process "uim-el-agent" buffer uim-el-agent))

    (if (not proc)
	(error "uim.el: Couldn't invoke uim-el-agent."))
    
    ;; don't ask kill
    (process-kill-without-query proc)

    ;; wait "OK"
    (let ((patience uim-startup-timeout) (ok nil))
      (save-current-buffer
	(set-buffer buffer)
	(while (not ok)
	  (accept-process-output proc 0 uim-el-agent-accept-timeout)
	  (goto-char 1)
	  (end-of-line)
	  (if (string= "OK" (buffer-substring 1 (point)))
	      (setq ok t)
	    (setq patience (- patience (/ uim-el-agent-accept-timeout 1000)))
	    (if (<= patience 0)
		(progn
		  (kill-process (process-name proc))
		  (error "uim.el: uim-el-agent is unreponsive.  Giving up.")))))
	(erase-buffer)))
	
    (setq uim-el-agent-buffer buffer)

    (set-process-sentinel proc 'uim-process-sentinel)

    (message "uim.el: starting uim-el-agent... done")

    proc 
    ))


;;
;; Stop uim-el-agent
;;
(defun uim-el-agent-stop (proc)
  (if (and proc
	   (process-status proc))
      (interrupt-process proc)))


;;
;; Restore undo list
;;
(defun uim-restore-undo ()
  (when uim-buffer-undo-list-saved
    (buffer-enable-undo)
    (setq buffer-undo-list uim-buffer-undo-list)
    (setq uim-buffer-undo-list-saved nil))
  )


;;
;; Save undo list
;;
(defun uim-save-undo ()
  (when (not uim-buffer-undo-list-saved)
    (setq uim-buffer-undo-list buffer-undo-list)
    (buffer-disable-undo)
    (setq uim-buffer-undo-list-saved 't))
  )


;;
;; Update IM list
;;
(defun uim-update-imlist (imlist)
  (setq uim-im-alist 
	(mapcar '(lambda (x) 
		   (let ((im (nth 0 x))
			 (lang-uim (nth 2 x)))
		     (cons im
			   (or (assoc lang-uim uim-lang-code-alist)
			       (assoc "Other" uim-lang-code-alist)))))
		imlist))
  )


;;
;; Update property label
;;
(defun uim-update-label (label)
  (let ((mode-str ""))
    (mapcar
     '(lambda (x)
	(cond ((string= (nth 0 x) "im-mode")
	       (setq mode-str (concat mode-str (nth 2 x))))
	      ((string= (nth 0 x) "im-name")
	       (setq uim-im-indication-id (nth 1 x))
	       (setq uim-im-name-str (nth 3 x)))
	      )
	)
     label)

    (setq uim-im-mode-str mode-str)

    (setq uim-mode-line-string
	  (concat (if (or uim-show-im-name uim-show-im-mode) " ")
		  (if uim-show-im-name
		      uim-im-name-str "")
		  (if uim-show-im-mode
		      (format "[%s]" uim-im-mode-str) "")))
    )

  (run-hooks 'uim-update-label-hook)  
  )



;;
;; Check lock status of buffer file
;; 
(defun uim-buffer-locked-p ()
  (if (buffer-file-name)
      (let ((locked (file-locked-p (buffer-file-name))))
	(not (or (not locked)     ;; nil when unlocked
		 (eq locked t))))))  ;; locked by this process

	    

;;
;; Cehck the buffer is editable or not
;;
(defun uim-buffer-not-editable ()
  (or (uim-buffer-locked-p)
      (not (verify-visited-file-modtime (current-buffer)))
      buffer-read-only))


;; 
;; Called when the window has been resized
;;
(defun uim-window-changed ()
  (let (candidate-displayed)
  
    (when (and (not uim-candidate-in-echo-region)
	       uim-candidate-displayed
	       uim-candidate-display-inline)
      (setq candidate-displayed uim-candidate-displayed)

      (uim-do-send-recv-cmd (format "%d HIDE" uim-context-id))

      (when (and candidate-displayed
		 (uim-check-candidate-space))
	(uim-do-send-recv-cmd (format "%d SHOW" uim-context-id))))))


;;
;; Reset all uim.el buffer local variables
;;
(defun uim-init-all-local-var ()
   (mapcar
    '(lambda (x)
       (set (car x) (cdr x)))
    uim-local-var))

;; 
;; Sentinel of uim-el-agent 
;;   STOP state is not supported yet
;; 
(defun uim-process-sentinel (proc stat)

  (message "uim.el: %s" stat)

  (setq uim-el-agent-process nil)

  (let ((old-buffer (current-buffer))
	(bufs (buffer-list)))
    (while bufs
      (save-current-buffer
	(set-buffer (car bufs))
	(when uim-initialized
	  ;; disable uim
	  (uim-force-off)
	  ;; reset all local variables
	  (uim-init-all-local-var)
	  (setq uim-im-initialized nil)
	  ))
      (setq bufs (cdr bufs)))
    ))


;;
;; Send command to uim-el-agent
;; 
(defun uim-send-cmd (cmd serial)

  ;; check process status and start if it's not running
  (if (not uim-el-agent-process)
      (setq uim-el-agent-process (uim-el-agent-start)))

  (if uim-el-agent-process
      (progn
	(save-current-buffer
	  (set-buffer uim-el-agent-buffer)
	  (erase-buffer))

        (uim-debug (format "%s"
                           (concat (format "%s " serial) cmd)))
	
	(process-send-string uim-el-agent-process
			     (concat (format "%s " serial) cmd "\n"))
	t)
    nil))


;;
;; Delete the first line from buffer
;;
(defun uim-delete-oneline-from-buffer (buf)
  (save-current-buffer
    (set-buffer buf)
    (beginning-of-buffer)
    (delete-region 1 (progn (end-of-line) (point)))
    (if (= (following-char) ?\n)
	(delete-char 1))))


;;
;; Get 1st one line from buffer
;;   skip non S-expression line
;;
(defun uim-get-oneline-from-buffer (buf)
  (save-current-buffer
    (set-buffer buf)
    (when (> (buffer-size) 0)
      (goto-char 1)
      (skip-chars-forward " ")
      ;; check the first line is beginning with bracket
      (if (= (following-char) ?\( )
	  (progn
	    (end-of-line)
	    (if (looking-at "\n")
		(buffer-substring 1 (point))
	      nil))
	;; if the first line isn't S-expression, skip and return nil
	(uim-delete-oneline-from-buffer buf)
	nil))))


;; 
;; Wait reply from uim-el-agent
;;   Check serial number 
;;
(defun uim-wait-recv (serial)

  (let (rstr
	(helper-filter (process-filter uim-el-helper-agent-process)))

    (set-process-filter uim-el-helper-agent-process nil)

    (let ((start (nth 1 (current-time)))
	  (accept uim-el-agent-accept-timeout))
      (catch 'read-loop
	(while (and (process-status uim-el-agent-process)
		    (< (abs (- start (nth 1 (current-time))))
		      uim-el-agent-timeout))
	  
	  (accept-process-output uim-el-agent-process 0 accept)

	  (if (setq rstr (uim-get-oneline-from-buffer uim-el-agent-buffer))
	      (if (= (car (read rstr)) serial)
		  (throw 'read-loop t)
		(uim-delete-oneline-from-buffer uim-el-agent-buffer)))
	  ;; double the accepting time if waiting again
	  (setq accept (* accept 2)))))

    (save-current-buffer
      (set-buffer uim-el-helper-agent-buffer)
      (when (> (buffer-size) 0)
	(setq uim-helper-message 
	      (concat uim-helper-message
		      (buffer-substring 1 (point-max))))
	(erase-buffer)))
    (set-process-filter uim-el-helper-agent-process helper-filter)

    (unwind-protect
    (cond ((not (process-status uim-el-agent-process))
	   ;; uim-el-agent is dead
	   (message "uim.el: state of uim-el-agent has been changed (died?)"))
	  
	  ((not rstr)
	   (message "uim.el: ***TIMEOUT*** no response from uim-el-agent")
	   ;; stop uim-el-agent (uim-el-agent will be restarting on-demand)
	   (uim-el-agent-stop uim-el-agent-process))

	  (t
	   ;; received
	   ;;   rstr: ( serial# context-id (...) )
	   (uim-process-agent-output (cddr (read rstr)))
	   )
	      )
      (uim-helper-message-processor)
      )
    )
  )


;;
;; Send message to uim-el-agent and wait return
;;
(defun uim-do-send-recv-cmd (cmd)
  (setq uim-last-cmd cmd) ;; keep last command
  (let ((serial (uim-get-communication-serial-number)))
    (if (uim-send-cmd cmd serial)
	(uim-wait-recv serial))))


;; for XEmacs
(defun uim-overwrite-font-face (start end)
  (let ((facelist '()) tail face)
    (catch 'face-loop
      (while t
	(setq tail
	      (next-single-property-change start 'face (current-buffer) end))
	(if (setq face (get-text-property start 'face))
	    (if (atom face)
		(put-text-property start tail 'face 
				   (cons face nil))))
	(setq start tail)
	(if (= start end)
	    (throw 'face-loop t))))
    )
  )


(defun uim-freeze-buffer ()
  (when (not uim-buffer-frozen)
    (setq uim-after-change-functions after-change-functions)
    (setq after-change-functions nil)

    (when (boundp 'timer-idle-list)
      (setq uim-timer-idle-list timer-idle-list)
      (setq timer-idle-list nil))

    (when (boundp 'fontification-functions)
      (setq uim-fontification-functions fontification-functions)
      (setq fontification-functions nil))

    (setq uim-buffer-frozen t))
  )


(defun uim-unfreeze-buffer ()
  (when uim-buffer-frozen ;; avoid multiple unfreeze 
    (setq after-change-functions uim-after-change-functions)
    (setq uim-after-change-functions nil)

    (when (boundp 'timer-idle-list)
      (setq timer-idle-list uim-timer-idle-list)
      (setq uim-timer-idle-list nil))

    (when (boundp 'fontification-functions)
      (setq fontification-functions uim-fontification-functions)
      (setq uim-fontification-functions nil))

    (setq uim-buffer-frozen nil)))

 

;;
;; Reset some global parameters for inputted key processing
;;
(defun uim-reset-input-parameter ()
  (setq uim-translated-key-vector nil)
  (setq uim-untranslated-key-vector nil)
  (setq uim-prefix-arg nil)
  (setq uim-prefix-arg-vector nil)
  (setq uim-merge-next nil))


;;
;; Process inputted key
;;
(defun uim-process-input (&optional arg event reg1 reg2)
  (interactive (list current-prefix-arg 
		     last-input-event
		     ))

  (let (new-key-vector send-vector send-vector-raw issue-vector
        send issue mouse wait discard
	(critical t))

    (unwind-protect
	(progn

	  (if uim-xemacs
	      (setq zmacs-region-stays nil))

	  (if (not (or uim-translated-key-vector uim-untranslated-key-vector))
	      (setq uim-keystroke-displaying nil))

	  (setq new-key-vector (uim-this-command-keys-vector))

	  (if (or current-prefix-arg
		  uim-merge-next)
	      (let (vector-list)
		(setq vector-list (uim-separate-prefix-vector new-key-vector))
		(setq new-key-vector (car vector-list))
		(setq uim-prefix-arg-vector (nth 1 vector-list))

		;; Workaround for FSF Emacs-20/21
		;;  Key event beginning with C-u and terminating with 
		;;  ESC-something invokes uim-process-input with twice 
		;;  at a time.
		;;  In such a case, uim.el merges these two inputs.
		(if (not uim-merge-next)
		    (progn
		      ;; normal
		      (setq uim-prefix-arg current-prefix-arg)
		      (if (and uim-emacs
			       (not window-system)
			       (<= emacs-major-version 21)
			       (>= (length new-key-vector) 2))
			  ;; workaround
			  (setq uim-merge-next t)))

		  ;; uim-merge-next is valid
		  (setq uim-merge-next nil)
		  (setq uim-translated-key-vector nil)
		  (setq uim-untranslated-key-vector nil))
		
		))

	  (if uim-xemacs
	      (setq uim-original-input-event (copy-event last-input-event)))

	  (setq uim-untranslated-key-vector
		(vconcat uim-untranslated-key-vector new-key-vector))
	  
	  (let (translated-list translated map)
	    
	    (setq translated-list 
		  (uim-translate-key uim-untranslated-key-vector))
	    (setq translated (nth 0 translated-list))
	    (setq map (nth 1 translated-list))

	    (cond ((stringp translated)
		   (setq issue t)
		   (setq uim-translated-key-vector translated)
		   (setq uim-untranslated-key-vector nil))

		  ((vectorp translated)
		   (setq send t) 
		   (setq uim-translated-key-vector 
			 (vconcat uim-translated-key-vector translated))
		   (setq uim-untranslated-key-vector nil))

		  (map
		   (setq wait t))

		  (t
		   (setq send t))
		  ))

	  (when send

	    (setq send-vector-raw (vconcat uim-translated-key-vector
					   uim-untranslated-key-vector))

	    (setq send-vector 
		  (if uim-emacs 
		      send-vector-raw
		    (if uim-xemacs
			(uim-convert-keystr-to-uimagent-vector (key-description send-vector-raw))
		      nil)))

	    (cond (uim-merge-next
		   (setq send nil))

		  ((or (and uim-emacs 
			    (eventp event)
			    (memq (event-basic-type event) 
				  '(mouse-1 mouse-2 mouse-3 mouse-4 mouse-5)))
		       (and uim-xemacs 
			    (string-match "button\\(1\\|2\\|3\\|4\\|5\\)" 
					  (key-description send-vector-raw))))
		   (setq send nil)
		   (setq mouse t))

		  ((or (and uim-preedit-keymap-enabled
			    (not window-system)
			    (not uim-use-single-escape-on-terminal)
			    (uim-is-single-escape send-vector-raw)))
		   (setq send nil)
		   (setq wait t))

		  ((or current-prefix-arg
		       uim-prefix-arg)
		   (setq send nil)
		   (setq issue t))
		  
		  ((or (eq (car-safe (aref send-vector 0)) 'menu-bar)
		       (eq (car-safe (aref send-vector 0)) 'tool-bar))
		   (setq send nil)
		   (if (not uim-preedit-keymap-enabled)
		       (setq issue t)
		     (if (and uim-preedit-keymap-enabled
			      (keymapp (uim-key-binding send-vector)))
			 (setq wait t)
		       (setq discard t))))

		  ((and (or (>= (length send-vector) 3)
			    (and (= (length send-vector) 2)
				 (not (or (eq (aref send-vector 0) 27)
					  (eq (aref send-vector 0) 'escape))))))
		   (setq send nil)
		   (if (not uim-preedit-keymap-enabled)
		       (setq issue t)))

		  ))


	  (when send

	    (setq uim-last-key-vector send-vector-raw)

	    (when uim-emacs
	      (cond ((equal send-vector [127])   (setq send-vector [backspace]))
		    ((equal send-vector [27 27]) (setq send-vector [27]))
		    ((equal send-vector [28])    (setq send-vector [C-\\]))
		    ((equal send-vector [29])    (setq send-vector [C-\]]))
		    ((equal send-vector [30])    (setq send-vector [C-~]))
		    ((equal send-vector [31])    (setq send-vector [C-_]))
		    ))
	    
	    (when uim-xemacs 

	      (if (equal (make-vector 2 (uim-xemacs-make-event [(escape)]))
			 send-vector-raw)
		  (setq send-vector-raw
			(vector (uim-xemacs-make-event [(escape)]))))

	      (setq send-vector-raw (uim-translate-escape-meta send-vector-raw))

	      (setq send-vector 
		    (uim-convert-keystr-to-uimagent-vector 
		     (key-description send-vector-raw))))

	    (setq uim-wait-next-key nil)
	    (uim-do-send-recv-cmd (format "%d %s" 
					  uim-context-id send-vector))
	    (setq wait uim-wait-next-key)
		
	    (when (not uim-wait-next-key)
	      (uim-reset-input-parameter)))
	    
	  (when mouse
	    (uim-process-mouse-event event)
	    (uim-reset-input-parameter))

	  (when issue
	    (let (issue-vector-raw)
	      (setq issue-vector-raw (vconcat uim-translated-key-vector
					      uim-untranslated-key-vector))
	      
	      (setq issue-vector 
		    (if uim-emacs 
			issue-vector-raw
		      (if uim-xemacs
			  (uim-convert-char-to-symbolvector 
			   (key-description issue-vector-raw)))))
	      
	      (setq uim-last-key-vector issue-vector-raw)
      
	      (setq wait
		    (uim-process-key-vector issue-vector-raw uim-prefix-arg))  

	      (when (not wait)
		(uim-reset-input-parameter))))

	  (when wait

	    (if (and (memq last-command (list 'universal-argument
					      'digit-argument
					      'negative-argument))
		     (current-message))
		(setq uim-keystroke-displaying t))

	    (if (not uim-keystroke-displaying)
		(cond (uim-emacs
		       (let (key)
			 (when (setq key (with-timeout (echo-keystrokes nil)
					   (read-key-sequence-vector nil)))
			   (setq unread-command-events
				 (nconc (listify-key-sequence key) 
					unread-command-events)))
			 
			 (setq uim-keystroke-displaying (not key))))
		      (uim-xemacs
		       (setq uim-keystroke-displaying (sit-for echo-keystrokes)))))

	    ;; display "ESC-" or something
	    (if uim-keystroke-displaying
		(let (message-log-max)
		  (message (concat (key-description 
				    (vconcat uim-prefix-arg-vector
					     (if uim-xemacs
						 (uim-translate-escape-meta uim-translated-key-vector)
					       uim-translated-key-vector)
					     (if uim-xemacs
						 (uim-translate-escape-meta uim-untranslated-key-vector)
					       uim-untranslated-key-vector)))
				   (if uim-xemacs " ")
				   "-"))))

	    (if uim-emacs
		(setq uim-deactivate-mark nil))

	    (if uim-xemacs
		(setq zmacs-region-stays t))
	    )

	  (when discard
	    (uim-reset-input-parameter))


	  ;; display "ESC ESC ESC" or something
	  (when (and (or send issue)
		     (not wait)
		     uim-keystroke-displaying)

	    (let (message-log-max msg)
	      (setq msg (if uim-prefix-arg-vector
			    (concat " " (key-description uim-prefix-arg-vector) " ")
			  ""))

	      (mapcar '(lambda (x)
			 (setq msg (concat msg
					   (key-description (vector x)) " ")))
		 
		      (append (if uim-xemacs
				  (uim-translate-escape-meta send-vector-raw)
				uim-last-key-vector) nil))
	      (message msg)))

	  (if uim-emacs
	      (setq deactivate-mark uim-deactivate-mark))

	  (setq critical nil))

      (when critical
	(uim-reset-input-parameter)))
    )
  )



(defun uim-enter-preedit-mode ()
  ;; change keymap and freeze faces at first time
  (uim-enable-preedit-keymap)
  (uim-disable-other-minor-mode-map)
  (when (= (minibuffer-depth) 0)
    (uim-freeze-buffer)
    (setq uim-buffer-read-only buffer-read-only)
    (setq buffer-read-only t))
  )

(defun uim-leave-preedit-mode ()
  (uim-disable-preedit-keymap)
  (setq buffer-read-only uim-buffer-read-only)
  (uim-unfreeze-buffer)
  )



;; process return expression from uim-el-agent 
(defun uim-process-agent-output (str)
  (catch 'process-agent-output
    (let ((inhibit-quit t)
	  preedit-existed
	  candidate-existed
	  key commit preedit candidate default im label imlist helpermsg
	  )

      (uim-debug (format "%s" str))
      
      (let ((modified (buffer-modified-p)))

	;; remove candidate anyway
	(when uim-candidate-displayed
	  (setq candidate-existed t)
	  (let ((inhibit-read-only t))
	    (uim-remove-candidate))
	  (setq uim-candidate-displayed nil)
	  (uim-goto-char uim-preedit-start))

	;; remove preedit anyway
	(when uim-preedit-displayed
	  (setq preedit-existed t)
	  (let ((inhibit-read-only t))
	    (uim-remove-preedit))
	  (setq uim-preedit-displayed nil))

	;; restore cursor point
	(when (and uim-preedit-keymap-enabled uim-original-cursor)
	  (goto-char uim-original-cursor))

	;; restore modified flag
	(set-buffer-modified-p modified))

      (mapcar 
       '(lambda (x) 
	  (let ((rcode (car x))
		(rval (cdr x)))
	
	    (cond ((string= rcode "n") ;; uim returns key code
		   (if uim-last-key-vector
		       (setq key uim-last-key-vector)
		     (setq key (car rval)))
		   )
		  ((string= rcode "s") ;; commit string
		   (setq commit (append commit (list (car rval))))
		   )
		  ((string= rcode "c") ;; candidate data
		   (setq candidate rval)
		   )
		  ((string= rcode "p") ;; preedit
		   (setq preedit rval)
		   )
		  ((string= rcode "d") ;; default engine
		   (setq default (car rval))
		   )
		  ((string= rcode "i") ;; current im
		   (setq im rval)
		   )
		  ((string= rcode "l") ;; label
		   (setq label rval)
		   )
		  ((string= rcode "h") ;; helper message
		   (setq helpermsg (append helpermsg rval))
		   )
		  ((string= rcode "L") ;; IM list
		   (setq imlist rval)
		   )
		  )
	    )) 
       str)

      (when helpermsg
	(uim-helper-send-message helpermsg))

      (when (and default (not (uim-get-default-engine)))
	(uim-update-default-engine default))

      (when im
	(uim-update-current-engine im))

      (when label
	(uim-update-label label))

      (when imlist
	(uim-update-imlist imlist))


      (if commit

	  ;; insert committed strings
	  (let ((inhibit-read-only t)
		(buffer-frozen uim-buffer-frozen))
	    (if uim-buffer-frozen
		(uim-unfreeze-buffer))

	    (mapcar
	     '(lambda (x)
		;; enable buffer-undo temporarily
		(when uim-buffer-undo-list-saved
		  (setq buffer-undo-list nil)
		  (buffer-enable-undo))

		(let ((buffer-undo-list-tmp buffer-undo-list))
		  (unwind-protect
		      (progn
			(setq buffer-undo-list nil)
			(insert x))
		    (when buffer-undo-list
		      (setq buffer-undo-list
 			    (append (cons nil 
					  (uim-delete-atom buffer-undo-list))
				    buffer-undo-list-tmp))

		      )))

		;; disable buffer-undo temporarily
		(when uim-buffer-undo-list-saved
		    
		  (setq uim-buffer-undo-list
			(append buffer-undo-list uim-buffer-undo-list))
		  (setq buffer-undo-list nil)
		  (buffer-disable-undo)))
	     commit)

	    (if auto-fill-function
		(funcall auto-fill-function))

	    (if buffer-frozen
		(uim-freeze-buffer))
	    )
	)


      (if key
	  (let (keyproc-done
		(buffer-undo-list-saved uim-buffer-undo-list-saved))
	    (when buffer-undo-list-saved
	      (uim-restore-undo))
	    ;; process raw key
	    ;;  C-o is also processed here ... orz
	    (let ((inhibit-read-only t))
	      (unwind-protect
		  (let ((have-shift nil))
		    ;; remove shift if possible
		    (if (and (uim-check-shift key)
			     (not (uim-key-binding key)))
			;; with shift key but no key bind
			(let* ((keytmp (uim-remove-shift key))
			       (translated (nth 0 (uim-translate-key keytmp))))
			  ;; lookup function-key-map
			  (if translated
			      (setq key translated)
			    (when (uim-key-binding keytmp)
                              (setq key keytmp)
                              (setq have-shift t)))))

                    (if (let ((this-command-keys-shift-translated have-shift))
                          (uim-process-key-vector key uim-prefix-arg))
                        (setq uim-wait-next-key t))
		    (setq keyproc-done t))
		(when (not keyproc-done)
		  (uim-leave-preedit-mode))
		)
	      ;; following expressions will not be evaluated
	      ;; when an error occurs in this function...
	      )
	    (if (not uim-mode)
		(throw 'process-agent-output t))
	    ;; save undo hisotry again
	    (when buffer-undo-list-saved
	      (uim-save-undo))))

      (setq uim-original-cursor (point))


      (if (or preedit candidate)
	  ;; process preedit/candidate
	  (let ((modified (buffer-modified-p)))

	    ;; save undo list if not saved
	    (when (not uim-buffer-undo-list-saved)
	      (uim-flush-concat-undo)
	      (uim-save-undo))

	    ;; change keymap and freeze faces at first time
	    (when (not uim-preedit-keymap-enabled)
	      (uim-enter-preedit-mode))

	    (setq uim-preedit-start uim-original-cursor)
	    (setq uim-preedit-overlap 0)
	    (setq uim-preedit-current-sentence-start uim-preedit-start)
	    (setq uim-preedit-cursor uim-preedit-start)

	    ;; show preedit
	    (when preedit
	      (setq uim-preedit-displayed t)
	      (let ((inhibit-read-only t))
		(uim-insert-preedit preedit)
		))

	    (uim-goto-char uim-preedit-cursor)

	    (setq uim-candidate-cursor (point))

	    ;; show candidate
	    (when (and candidate
		       (uim-check-candidate-space)
		       (eq uim-focused-buffer (current-buffer)))
	      (setq uim-candidate-displayed t)
	      (setq uim-candidate-start uim-preedit-current-sentence-start)
	      (setq uim-candidate-vofs uim-preedit-overlap)
	      (let ((inhibit-read-only t))
		(uim-show-candidate candidate)))
	  
	    (uim-goto-char uim-candidate-cursor)

	    (set-buffer-modified-p modified)
	    )

	;; no preedit/candidate

	;; restore undo-list if saved
	(when uim-buffer-undo-list-saved
	  (uim-restore-undo))

	;; no raw-key and no preedit/candidate
	(when uim-preedit-keymap-enabled
	  (uim-leave-preedit-mode))

	)

      ;; scroll buffer after candidate removed
      ;;  recenter if buffer is force scrolled at candidate displaying
      (when (and candidate-existed
		 (not uim-candidate-displayed) 
		 uim-window-force-scrolled) 
	(setq uim-window-force-scrolled nil)
	(save-excursion
	  (goto-char uim-window-force-scrolled-original)
	  (recenter 0))
	(setq uim-window-force-scrolled-original nil))

      (if (not uim-send-recv-again)
	  (when label 
	    (setq uim-send-recv-again t)
	    (uim-update-im-label))
	(setq uim-send-recv-again nil))
      ))
  )





;;
;; Get available IM list from uim-el-agent
;;
(defun uim-get-im-list ()
  (uim-do-send-recv-cmd (format "0 LIST")))

(defun uim-init-im-alist ()
  (uim-get-im-list))

;;
;; Initialize IM list and encoding
;;
(defun uim-init-im-encoding ()

  ;; set Uim side encoding to agent
  (mapcar 
   '(lambda (x)
      (let ((im (car x)))
	(uim-set-encoding im (uim-get-uim-encoding im))))
   uim-im-alist))

;;
;; Called when entering into the mini-buffer
;;
(defun uim-minibuffer-enter ()
  nil)

;;
;; Called when leaving from the mini-buffer
;;
(defun uim-minibuffer-exit ()
  (if (and uim-initialized
	   uim-mode)
	;;(uim-mode-off)
      (uim-force-off)
    ))



;;
;; Initialize uim
;;
(defun uim-init ()

  ;; initialize minor-mode
  (uim-init-minor-mode)
  (uim-enable-mode-keymap)

  ;; initialize keymap
  (uim-init-keymap)

  ;; add hook to detect status change of buffer
  (add-hook 'post-command-hook 'uim-post-command)

  ;; disable uim when major-mode has changed
  (add-hook 'change-major-mode-hook 'uim-change-major-mode)

  ;; add hooks to detect minibuffer entering and leaving
  (add-hook 'minibuffer-setup-hook 'uim-minibuffer-enter)
  (add-hook 'minibuffer-exit-hook 'uim-minibuffer-exit)

  ;; advice to support kbd macro
  (defadvice call-last-kbd-macro (around uim-call-last-kbd-macro activate)
    (let ((current-uim-mode uim-mode))
      (setq uim-mode nil)
      (unwind-protect
	  ad-do-it
	(setq uim-mode current-uim-mode))))
  )



;;
;; Toggle uim
;;

;;;###autoload
(defun uim-mode (&optional arg)
  "Toggle uim mode.
With argument ARG, turn uim mode on if ARG > 0.
uim mode facilitates internationalized input through the uim library."
  (interactive)
  (if arg
      (if (> arg 0)
	  (if (not uim-mode) (uim-mode-on))
	(if uim-mode (uim-mode-off)))
    (if uim-mode
	(uim-mode-off)
      (uim-mode-on))))

;; compat
;;;###autoload
(defun uim-mode-switch ()
  (interactive)
  (if uim-mode
      (uim-mode-off)
    (uim-mode-on)))

;;
;; Toggle candidate display style
;;
(defun uim-switch-candidate-display-mode (&optional arg)
  (interactive)

  (if (not (local-variable-p 'uim-candidate-display-inline (current-buffer)))
      (make-variable-buffer-local 'uim-candidate-display-inline))
  
  (if (not uim-candidate-displayed)
      (if (and arg (integerp arg))
	  (if (> arg 0)
	      (setq uim-candidate-display-inline t)
	    (setq uim-candidate-display-inline nil))

	;; interactive
	(if uim-candidate-display-inline
	    (progn
	      (message "uim.el: mini-buffer candidate display mode")
	      (setq uim-candidate-display-inline nil))
	  (message "uim.el: inline candidate display mode")
	  (setq uim-candidate-display-inline t))))
  )


;;
;; Switch IM engine
;;
(defun uim-im-switch (&optional im)
  (interactive)

  (if (not uim-mode)
      (uim-mode-on))

  (when uim-initialized
    (if (not im)
	(let (alist)
	  (setq alist (mapcar '(lambda (x) 
				 (cons (car x) (car x))) 
			      uim-im-alist))
	  (save-window-excursion
	    (setq im (cdr (assoc
			   (completing-read (concat "IM Engine: ") alist nil t)
			   alist) )))))
    (if (uim-check-im im)
	(uim-change-im im))))

;;
;; Show uim.el version
;;
(defun uim-el-version ()
  (interactive)
  (message uim-el-version))



;; Initialize uim.el 
(uim-init)

(run-hooks 'uim-load-hook)

(provide 'uim)

