;;;
;;; Copyright (c) 2003-2013 uim Project http://code.google.com/p/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 HOLDERS 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 "util.scm")

;; config
(define enable-emacs-like-key-prefix? #t)

;; valid-key-symbols is defined in uim-key.c

(define key-symbol?
  (lambda (sym)
    (member sym valid-key-symbols)))

(define intern-key-symbol
  (lambda (key-str)
    (let ((sym (string->symbol key-str)))
      (if (memq sym valid-key-symbols)
	  sym
	  #f))))

;
(define shift-key-mask
  (lambda (state)
    (= (bitwise-and state 1) 1)))
(define control-key-mask
  (lambda (state)
    (= (bitwise-and state 2) 2)))
(define alt-key-mask
  (lambda (state)
    (= (bitwise-and state 4) 4)))
(define meta-key-mask
  (lambda (state)
    (= (bitwise-and state 8) 8)))
(define super-key-mask
  (lambda (state)
    (= (bitwise-and state 64) 64)))
(define hyper-key-mask
  (lambda (state)
    (= (bitwise-and state 128) 128)))

(define modifier-key-mask
  (lambda (state)
    (> state 0)))

(define modifier-key?
  (lambda (key key-state)
    (or
     (eq? key 'Shift_key)
     (eq? key 'Control_key)
     (eq? key 'Alt_key)
     (eq? key 'Meta_key)
     (eq? key 'Super_key)
     (eq? key 'Hyper_key))))

(define key-state-alist
  '((Shift_key   . 1)
    (Control_key . 2)
    (Alt_key     . 4)
    (Meta_key    . 8)
    (Super_key   . 64)
    (Hyper_key   . 128)))

(define emacs-like-prefix-alist
  '(("S" . Shift_key)
    ("C" . Control_key)
    ("A" . Alt_key)
    ("M" . Meta_key)
    ("Z" . Super_key)
    ("H" . Hyper_key)
    ("I" . IgnoreCase)
    ("J" . IgnoreShift)
    ("K" . IgnoreRegularShift)))

(define tag-prefix-alist
  '(("Shift"              . Shift_key)
    ("Control"            . Control_key)
    ("Alt"                . Alt_key)
    ("Meta"               . Meta_key)
    ("Super"              . Super_key)
    ("Hyper"              . Hyper_key)
    ("IgnoreCase"         . IgnoreCase)
    ("IgnoreShift"        . IgnoreShift)
    ("IgnoreRegularShift" . IgnoreRegularShift)))

(define translator-prefix?
  (lambda (symbol)
    (or (eq? symbol 'IgnoreCase)
	(eq? symbol 'IgnoreShift)
	(eq? symbol 'IgnoreRegularShift))))

;;
(define intern-key-prefix
  (lambda (symbol-str alist)
    (let ((pair (assoc symbol-str alist)))
      (and pair
	   (cdr pair)))))

(define parse-tag-prefix-symbol
  (lambda (parsed char-list)
    (let ((prefix (if (string=? parsed "")
		      #f
		      (intern-key-prefix parsed tag-prefix-alist))))
      (if (not (null? char-list))
	  (let* ((head (car char-list))
		 (head-char (string->charcode head))
		 (rest (cdr char-list)))
	    (if (or (ichar-alphabetic? head-char)
		    (ichar-numeric? head-char)
		    (string=? head "_"))
		(parse-tag-prefix-symbol (string-append parsed head) rest)
		(cons prefix char-list)))
	  (cons prefix ())))))

(define parse-tag-prefix
  (lambda (str)
    (if (not (string=? str ""))
	(let* ((char-list (reverse (string-to-list str)))
	       (head (car char-list)))
	  (if (string=? head "<")
	      (let* ((parsed (parse-tag-prefix-symbol "" (cdr char-list)))
		     (prefix (car parsed))
		     (rest (cdr parsed)))
		(if (and (not (null? rest))
			 (string=? (car rest) ">"))
		    (cons prefix
			  (if (null? (cdr rest))
			      ""
			      (apply string-append (cdr rest))))
		    (cons #f str)))
	      (cons #f str)))
	(cons #f str))))

(define parse-emacs-like-prefix
  (lambda (str)
    (let* ((char-list (reverse (string-to-list str)))
	   (prefix-str (and (<= 2 (length char-list))
			(string=? (nth 1 char-list) "-")
			(car char-list)))
	   (prefix (intern-key-prefix prefix-str emacs-like-prefix-alist))
	   (rest (if prefix
		     (apply string-append (cddr char-list))
		     str)))
      (cons prefix rest))))

(define parse-key-prefix
  (lambda (str)
    (let* ((parsed-as-emacs (parse-emacs-like-prefix str))
	   (emacs-prefix (car parsed-as-emacs)))
      (or (and enable-emacs-like-key-prefix?
	       emacs-prefix
	       parsed-as-emacs)
	  (parse-tag-prefix str)))))

(define parse-key-str
  (lambda (str translators key key-state)
    (let ((str-len (string-length str)))
      (cond
       ((= str-len 0)
	(list "" translators key key-state))
       ((= str-len 1)
	(list "" translators (string->charcode str) key-state))
       ((<= 2 str-len)
	(let* ((parsed (parse-key-prefix str))
	       (prefix (car parsed))
	       (rest (cdr parsed)))
	  (cond
	   ((modifier-key? prefix 0)
	    (let ((key-state (bitwise-ior key-state
					  (cdr (assq prefix key-state-alist)))))
	      (parse-key-str rest translators key key-state)))
	   ((translator-prefix? prefix)
	    (let* ((translator
		    (cond
		     ((eq? prefix 'IgnoreCase)
		      (lambda (key key-state)
			(let ((translated-key (ichar-downcase key)))
			  (list translated-key key-state))))
		     ((eq? prefix 'IgnoreShift)
		      (lambda (key key-state)
			(let ((translated-key-state
			       (bitwise-and key-state
					    (bitwise-not 1))))
			  (list key translated-key-state))))
		     ((eq? prefix 'IgnoreRegularShift)
		      (lambda (key key-state)
			(let ((translated-key-state
			       (if (ichar-graphic? key)
				   (bitwise-and key-state
						(bitwise-not 1))
				   key-state)))
			  (list key translated-key-state))))))
		   (translators (cons translator
				      translators)))
	      (parse-key-str rest translators key key-state)))
	   (else
	    (let* ((key-symbol (intern-key-symbol str))
		   (key (or key-symbol
			    key))
		   (rest (if key-symbol
			     ""
			     rest)))
	      (list rest translators key key-state))))))))))

(define apply-translators
  (lambda (translators key key-state)
    (if (null? translators)
	(list translators key key-state)
	(let* ((translator (car translators))
	       (rest-translators (cdr translators))
	       (translated (translator key key-state))
	       (translated-key (car translated))
	       (translated-state (cadr translated)))
	  (apply-translators
	   rest-translators
	   translated-key
	   translated-state)))))

;; Generates key predicate
;; (make-single-key-predicate "<Control>j")
(define make-single-key-predicate
  (lambda (source)
    (cond
     ((string? source)
      (let* ((key-str source)
	     (parsed (parse-key-str key-str () -1 0))
	     (translated (apply apply-translators (cdr parsed)))
	     (translators  (nth 1 parsed))
	     (target-key   (nth 1 translated))
	     (target-state (nth 2 translated)))
	(lambda (key key-state)
	  (let* ((translated (apply-translators translators key key-state))
		 (key       (nth 1 translated))
		 (key-state (nth 2 translated)))
	    (and (eqv? key target-key)
		 (eqv? key-state target-state))))))
     ((symbol? source)
      (let ((predicate-sym source))
	(lambda (key key-state)
	  ((symbol-value predicate-sym) key key-state))))
     (else
      (let ((maybe-predicate source))
	maybe-predicate)))))

;; Generates or'ed key predicate
;; (make-key-predicate '("<Control>j" "<Alt>k" "<Control>L"))
(define make-key-predicate
  (lambda (sources)
    (cond
     ((list? sources)
      (let ((predicates (map make-single-key-predicate sources)))
	(lambda (key key-state)
	  (apply proc-or
		 (map (lambda (predicate)
			(apply predicate (list key key-state)))
		      predicates)))))
     (else
      (let ((source sources))
	(make-single-key-predicate source))))))

(define modify-key-strs-implicitly
  (lambda (key-strs)
    (cond
     ((list? key-strs)
      (map modify-key-strs-implicitly key-strs))
     ((string? key-strs)
      (let* ((key-str key-strs)
	     (modified-key-str (string-append
				"<IgnoreRegularShift>"
				key-str)))
	modified-key-str))
     (else
      (let ((maybe-predicate key-strs))
	maybe-predicate)))))

;; Generates or'ed key predicate and bind it into
;; toplevel-environment. Use define-key rather than calling this
;; directly.
;; (define-key-internal 'foo-key? '("<Control>j" "<Alt>k" "<Control>L"))
(define define-key-internal
  (lambda (key-predicate-sym key-strs)
    (let* ((modified-key-strs (modify-key-strs-implicitly key-strs))
	   (predicate (make-key-predicate modified-key-strs)))
      (eval (list 'define key-predicate-sym predicate)
	    (interaction-environment)))))
(define-macro define-key
  (lambda (key-predicate-sym key-strs)
    `(define-key-internal ',key-predicate-sym ,key-strs)))

(define valid-key-str?
  (lambda (key-str)
    (let* ((parsed (parse-key-str key-str () -1 0))
	   (rest        (nth 0 parsed))
	   (translators (nth 1 parsed))
	   (key         (nth 2 parsed))
	   (key-state   (nth 3 parsed)))
      (and (string? key-str)
	   (string=? rest "")
	   (not (eqv? key -1))))))

;; 'strict-key-str' stands for key-str without translator-prefixes and
;; emacs like prefix
(define valid-strict-key-str?
  (lambda (key-str)
    (let ((saved-enable-eprefix? enable-emacs-like-key-prefix?)
	  (res #f))
      (set! enable-emacs-like-key-prefix? #f)
      (let* ((parsed (parse-key-str key-str () -1 0))
	     (rest        (nth 0 parsed))
	     (translators (nth 1 parsed))
	     (key         (nth 2 parsed))
	     (key-state   (nth 3 parsed)))
	(set! res (and (string? key-str)
		       (string=? rest "")
		       (null? translators)
		       (not (eqv? key -1)))))
      (set! enable-emacs-like-key-prefix? saved-enable-eprefix?)
      res)))

;;
(define-key left-key? "left")
(define-key right-key? "right")
(define-key switch-im-key? '("<Control>Shift_key" "<Shift>Control_key"))
