(module localization racket/base
  
  (require racket/contract/base
           racket/file
           (only-in racket/runtime-path define-runtime-path)
           racket/string racket/format
           syntax/modread
	   (for-syntax racket/base))
  
  (provide/contract (current-language (parameter/c symbol?))
                    (current-country (parameter/c symbol?))
                    (current-locale-details (parameter/c (listof symbol?)))
                    (declare-bundle! (-> (listof symbol?) (listof pair?) any))
                    (load-bundle! (->* ((listof symbol?)) #:rest any/c any))
                    (store-bundle! (-> (listof symbol?) any))
                    (localized-template (-> symbol? any/c any)))
  
  (provide re-read-locale)
  
  (define get-from-locale
    (lambda (what)
      (let ((locale (current-locale)))
	(if (string=? locale "")
	    (case what
	      ((language) 'en) ;; Default language: English
	      ((country) 'us) ;; Default country: US
	      (else null))
	    (let ((len (string-length locale)))
	      (case what
		((language)
		 (if (>= len 2)
		     (string->symbol (substring locale 0 2))
		     'en))
		((country)
		 (if (>= len 5)
		     (string->symbol (substring locale 3 5))
		     'us))
		(else ;; details
		 (if (> len 6)
		     (list (string->symbol (substring locale 6)))
		     null))))))))
  
  ;; The association list in which bundles will be stored
  (define *localization-bundles*
    (make-hash))
  
  (define current-language
    (make-parameter (get-from-locale 'language)))
  
  (define current-country
    (make-parameter (get-from-locale 'country)))
  
  (define current-locale-details
    (make-parameter (get-from-locale 'details)))
  
  (define (make-name bundle-specifier)
    (string->symbol
     (string-append "srfi-29:"
                    (~v bundle-specifier))))
  
  (define (declare-bundle! bundle-specifier bundle-assoc-list)
    (hash-set! *localization-bundles* bundle-specifier bundle-assoc-list))
  
  (define (store-bundle! bundle-specifier)
    (put-preferences (list (make-name bundle-specifier))
                     (list (hash-ref *localization-bundles* bundle-specifier)))
    #t)
  
  (define (load-bundle-from-preference! bundle-specifier)
    (let/ec k
      (declare-bundle! bundle-specifier
                       (get-preference (make-name bundle-specifier)
                                       (lambda () (k #f))))
      #t))
  
  ;; If you change (current-locale), you don't have to set current-*
  ;; by hand, you can simply call this procedure, and it will update
  ;; those parameters to the values in the new locale.
  (define (re-read-locale)
    (current-language (get-from-locale 'language))
    (current-country (get-from-locale 'country))
    (current-locale-details (get-from-locale 'details)))
  
  ;; System bundles are here:
  (define-runtime-path system-bundles "bundles")
  
  (define (with-reader-params thunk)
    ;; Use `with-module-reading-parameterization' to get
    ;; most defaults...
    (with-module-reading-parameterization 
     (lambda ()
       ;; ... but disable `#reader':
       (parameterize ([read-accept-reader #f])
         (thunk)))))
  
  ;; load-bundle! accpect an alternate-path to search bundle
  (define (load-bundle! bundle-specifier . alternate-path)
    (or (load-bundle-from-preference! bundle-specifier)
        (let* ((filename (case (length bundle-specifier)
                           ((1) (symbol->string (car bundle-specifier)))
                           ((2) (build-path (symbol->string (cadr bundle-specifier))
                                            (symbol->string (car bundle-specifier))))
                           (else (build-path (symbol->string (cadr bundle-specifier))
                                             (symbol->string (caddr bundle-specifier))
                                             (symbol->string (car bundle-specifier))))))
               (path (build-path (if (null? alternate-path)
                                     system-bundles
                                     (car alternate-path))
                                 filename)))
          (and (file-exists? path)
               (declare-bundle! bundle-specifier
                                (with-reader-params
                                 (lambda ()
                                   (with-input-from-file path read))))
               #t))))
  (define (rdc ls)
    (if (null? (cdr ls))
        '()
        (cons (car ls) (rdc (cdr ls)))))
  
  ;;Retrieve a localized template given its package name and a template name
  (define (localized-template package-name template-name)
    (let loop ((specifier (list package-name
                                (current-language)
                                (current-country))))
      (and (not (null? specifier))
           (let ((bundle (hash-ref *localization-bundles* specifier #f)))
             (cond ((and bundle (assq template-name bundle)) => cdr)
                   ((null? (cdr specifier)) #f)
                   (else (loop (rdc specifier))))))))
  )
