#lang racket/base
(require racket/contract/base
         racket/path
         "private/modhelp.rkt")

(define (force-relto relto dir? #:path? [path? #t])
  (let ([relto (if (and (pair? relto)
                        (eq? (car relto) 'submod))
                   (cadr relto)
                   relto)]
        [submod (if (and (pair? relto)
                         (eq? (car relto) 'submod))
                    (cddr relto)
                    null)])
    (cond [(path-string? relto)
           (values (and path?
                        (if dir?
                            (let-values ([(base n d?) (split-path relto)])
                              (when d?
                                (error 'resolve-module-path-index
                                       "given a directory path instead of a file path: ~e" relto))
                              (if (eq? base 'relative)
                                  (or (current-load-relative-directory) (current-directory))
                                  base))
                            relto))
                   submod)]
          [(pair? relto) (values relto submod)]
          [(not dir?)
           (values
            (and path?
                 (error 'resolve-module-path-index
                        "can't resolve \"self\" with non-path relative-to: ~e" relto))
            submod)]
          [(procedure? relto) (force-relto (relto) dir? #:path? path?)]
          [else (values (and path? (current-directory)) submod)])))

(define (path-ss->rkt p)
  (if (path-has-extension? p #".ss")
      (path-replace-extension p #".rkt")
      p))

(define (combine-submod v p)
  (if (null? p)
      v
      (list* 'submod v p)))

(define (flatten base orig-p)
  (let loop ([accum '()] [p orig-p])
    (cond
     [(null? p) (combine-submod base (reverse accum))]
     [(equal? (car p) "..")
      (if (null? accum)
          (error 'resolve-module-path "too many \"..\"s: ~s"
                 (combine-submod base orig-p))
          (loop (cdr accum) (cdr p)))]
     [else (loop (cons (car p) accum) (cdr p))])))

(define (resolve-module-path s [relto #f])
  ;; relto should be a complete path, #f, or procedure that returns a
  ;; complete path
  (define (get-dir) (force-relto relto #t))
  (cond [(symbol? s)
         ;; use resolver handler:
         (resolved-module-path-name
          (module-path-index-resolve
           (module-path-index-join s #f)))]
        [(string? s)
         ;; Parse Unix-style relative path string
         (define-values (dir submod) (get-dir))
         (path-ss->rkt
          (apply build-path dir (explode-relpath-string s)))]
        [(and (or (not (pair? s)) (not (list? s))) (not (path? s)))
         #f]
        [(or (path? s) (eq? (car s) 'file))
         (let ([p (if (path? s) s (expand-user-path (cadr s)))])
           (define-values (d submod) (get-dir))
           (path-ss->rkt
            (path->complete-path
             p 
             (if (path-string? d)
                 d
                 (or (current-load-relative-directory)
                     (current-directory))))))]
        [(or (eq? (car s) 'lib)
             (eq? (car s) 'quote)
             (eq? (car s) 'planet))
         ;; use resolver handler in this case, too:
         (define-values (d submod) (force-relto relto #f #:path? #f))
         (resolved-module-path-name
          (module-path-index-resolve
           (module-path-index-join s #f)))]
        [(eq? (car s) 'submod)
         (define r (cond
                    [(or (equal? (cadr s) ".")
                         (equal? (cadr s) ".."))
                     (define-values (d submod) (force-relto relto #f))
                     (combine-submod d submod)]
                    [else (resolve-module-path (cadr s) relto)]))
         (define base-submods (if (and (or (equal? (cadr s) ".") 
                                           (equal? (cadr s) ".."))
                                       (pair? r)) 
                                  (cddr r)
                                  null))
         (define base (if (pair? r) (cadr r) r))
         (flatten base (append base-submods 
                               (if (equal? (cadr s) "..") (cdr s) (cddr s))))]
        [else #f]))

(define (resolve-module-path-index mpi [relto #f])
  ;; relto must be a complete path
  (let-values ([(path base) (module-path-index-split mpi)])
    (if path
        (resolve-module-path path (resolve-possible-module-path-index base relto))
        (let ()
          (define sm (module-path-index-submodule mpi))
          (define-values (dir submod) (force-relto relto #f))
          (combine-submod (path-ss->rkt dir) (if (and sm submod)
                                                 (append submod sm)
                                                 (or sm submod)))))))

(define (resolve-possible-module-path-index base [relto #f])
  (cond [(module-path-index? base)
         (resolve-module-path-index base relto)]
        [(and (resolved-module-path? base)
              (path? (resolved-module-path-name base)))
         (resolved-module-path-name base)]
        [relto relto]
        [else #f]))


(define rel-to-path-string/c
  (or/c path-string? (cons/c 'submod (cons/c path-string? (listof symbol?)))))

(define rel-to-path-string/thunk/#f
  (or/c rel-to-path-string/c (-> rel-to-path-string/c) false/c))

(provide/contract
 [resolve-module-path (->* (module-path?)
                           (rel-to-path-string/thunk/#f)
                           (or/c path? symbol? 
                                 (cons/c 'submod (cons/c (or/c path? symbol?) 
                                                         (listof symbol?)))))]
 [resolve-module-path-index (->* ((or/c symbol? module-path-index?))
                                 (rel-to-path-string/thunk/#f)
                                 (or/c path? symbol? 
                                       (cons/c 'submod (cons/c (or/c path? symbol?)
                                                               (listof symbol?)))))])
