#lang racket/base
(require racket/contract/base
         racket/class
         racket/gui/base)

(provide
 (contract-out
  [find-string-embedded
   (->* ((is-a?/c text%)
         string?
         #:recur-inside? (-> (is-a?/c editor-snip%) any/c))
        ((or/c 'forward 'backward)
         (or/c 'start number?)
         (or/c 'eof number?)
         boolean?
         boolean?
         boolean?)
        (values (is-a?/c editor<%>)
                (or/c #f number?)))]))

(define (find-string-embedded a-text
                              str
                              [direction 'forward]
                              [start 'start]
                              [end 'eof]
                              [get-start #t]
                              [case-sensitive? #t]
                              [pop-out? #f]
                              #:recur-inside? recur-inside?)
  (let/ec k
    (let loop ([a-text a-text]
               [start start]
               [end end])
      (define found (send a-text find-string-embedded str direction start end get-start case-sensitive?
                          #:recur-inside? recur-inside?))
      (define (done)
        (cond
          [(not found)
           (k a-text found)]
          [else
           (let loop ([a-text a-text]
                      [found found])
             (cond
               [(number? found)
                (k a-text found)]
               [else (loop (car found) (cdr found))]))]))
      (when found (done))
      (unless pop-out? (done))
      (define a-text-admin (send a-text get-admin))
      (unless (is-a? a-text-admin editor-snip-editor-admin<%>) (done))
      (define editor-snip (send a-text-admin get-snip))
      (define editor-snip-admin (send editor-snip get-admin))
      (unless editor-snip-admin (done))
      (define enclosing-text (send editor-snip-admin get-editor))
      (unless (is-a? enclosing-text text%) (done))
      (loop enclosing-text
            (+ (send enclosing-text get-snip-position editor-snip)
               (send editor-snip get-count))
            'eof))))

(module+ test
  (require rackunit)

  (define abcX (new text%))
  (send abcX insert "abcX")

  (define abc/abcX/abcQ (new text%))
  (send abc/abcX/abcQ insert "abc")
  (send abc/abcX/abcQ insert (new editor-snip% [editor abcX]))
  (send abc/abcX/abcQ insert "abcQ")

  (define abc//abc/abcX/abcQ//abcZ (new text%))
  (send abc//abc/abcX/abcQ//abcZ insert "abc")
  (send abc//abc/abcX/abcQ//abcZ insert (new editor-snip% [editor abc/abcX/abcQ]))
  (send abc//abc/abcX/abcQ//abcZ insert "abcZ")
  
  (let ()
    (define-values (ta pos) (find-string-embedded abcX "b" 'forward 0 #:recur-inside? (λ (x) #t)))
    (check-equal? ta abcX)
    (check-equal? pos 1))

  (let ()
    (define-values (ta pos) (find-string-embedded abcX "c" 'forward 0 #:recur-inside? (λ (x) #t)))
    (check-equal? ta abcX)
    (check-equal? pos 2))

  (let ()
    (define-values (ta pos) (find-string-embedded abcX "d" 'forward 2 #:recur-inside? (λ (x) #t)))
    (check-equal? pos #f))

  (let ()
    (define-values (ta pos) (find-string-embedded abc/abcX/abcQ "b" 'forward 0 #:recur-inside? (λ (x) #t)))
    (check-equal? ta ta)
    (check-equal? pos 1))

  (let ()
    (define-values (ta pos) (find-string-embedded abc/abcX/abcQ "b" 'forward 2 #:recur-inside? (λ (x) #t)))
    (check-equal? ta abcX)
    (check-equal? pos 1))

  (let ()
    (define-values (ta pos) (find-string-embedded abc//abc/abcX/abcQ//abcZ "X" 'forward 0  #:recur-inside? (λ (x) #t)))
    (check-equal? ta abcX)
    (check-equal? pos 3))

  (let ()
    (define-values (ta pos) (find-string-embedded abcX "Q" 'forward 0 'eof #t #t #t #:recur-inside? (λ (x) #t)))
    (check-equal? ta abc/abcX/abcQ)
    (check-equal? pos 7))

  (let ()
    (define-values (ta pos) (find-string-embedded abcX "Z" 'forward 0 'eof #t #t #t #:recur-inside? (λ (x) #t)))
    (check-equal? ta abc//abc/abcX/abcQ//abcZ)
    (check-equal? pos 7))

  (let ()
    (define-values (ta pos) (find-string-embedded abcX "c" 'forward 4 'eof #t #t #t #:recur-inside? (λ (x) #t)))
    (check-equal? ta abc/abcX/abcQ)
    (check-equal? pos 6)))
