;;; Copyright (C) William D Clinger (2016).
;;;
;;; Permission is hereby granted, free of charge, to any person
;;; obtaining a copy of this software and associated documentation
;;; files (the "Software"), to deal in the Software without
;;; restriction, including without limitation the rights to use,
;;; copy, modify, merge, publish, distribute, sublicense, and/or
;;; sell copies of the Software, and to permit persons to whom the
;;; Software is furnished to do so, subject to the following
;;; conditions:
;;;
;;; The above copyright notice and this permission notice shall be
;;; included in all copies or substantial portions of the Software.
;;;
;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
;;; OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
;;; OTHER DEALINGS IN THE SOFTWARE.

;;; For convenient interoperation with SRFI 13,
;;; cursors ought to be the same as indexes.

;;; Some of the FIXME comments mark procedures that really ought
;;; to do more checking for "is an error" situations.

;;; Slightly modified by vyzo to use fixnum arithmetic

(declare (fixnum))

(define (string-cursor? x)
  (and (fixnum? x) (##fx>= x 0)))

(define (string-cursor-start s) 0)
(define (string-cursor-end s) (string-length s))
(define (string-cursor-next s curs) (+ curs 1))                         ; FIXME
(define (string-cursor-prev s curs) (- curs 1))                         ; FIXME
(define (string-cursor-forward s curs n) (+ curs n))                    ; FIXME
(define (string-cursor-back s curs n) (- curs n))                       ; FIXME
(define (string-cursor=? curs1 curs2) (= curs1 curs2))
(define (string-cursor<? curs1 curs2) (< curs1 curs2))
(define (string-cursor>? curs1 curs2) (> curs1 curs2))
(define (string-cursor<=? curs1 curs2) (<= curs1 curs2))
(define (string-cursor>=? curs1 curs2) (>= curs1 curs2))
(define (string-cursor-diff s start end) (- end start))                 ; FIXME
(define (string-cursor->index s curs) curs)
(define (string-index->cursor s idx) idx)

(define string->list/cursors string->list)
(define string->vector/cursors string->vector)

(define string-ref/cursor string-ref)
(define substring/cursors substring)
(define string-copy/cursors string-copy)

;;; The SRFI 13 procedures return #f sometimes, so they can't be the same
;;; even if cursors are the same as indexes.
;;; Furthermore string-index-right and string-skip-right return the
;;; successor of the cursor for the character found.

(define string-index
  (case-lambda
   ((s pred)
    (string-index s pred 0 (string-length s)))
   ((s pred start)
    (string-index s pred start (string-length s)))
   ((s pred start end)
    (or (srfi-13:string-index s pred start end)
        end))))

(define string-index-right
  (case-lambda
   ((s pred)
    (string-index-right s pred 0 (string-length s)))
   ((s pred start)
    (string-index-right s pred start (string-length s)))
   ((s pred start end)
    (let ((i (srfi-13:string-index-right s pred start end)))
      (if i (+ i 1) start)))))

(define (string-skip s pred . rest)
  (apply string-index s (lambda (x) (not (pred x))) rest))

(define (string-skip-right s pred . rest)
  (apply string-index-right s (lambda (x) (not (pred x))) rest))

;;; FIXME: inefficient

(define string-contains-right
  (case-lambda
   ((s1 s2)
    (string-contains-right s1 s2 0 (string-length s1) 0 (string-length s2)))
   ((s1 s2 start1)
    (string-contains-right s1 s2
                           start1 (string-length s1) 0 (string-length s2)))
   ((s1 s2 start1 end1)
    (string-contains-right s1 s2 start1 end1 0 (string-length s2)))
   ((s1 s2 start1 end1 start2)
    (string-contains-right s1 s2 start1 end1 start2 (string-length s2)))
   ((s1 s2 start1 end1 start2 end2)
    (if (= start2 end2)
        end1
        (let loop ((i #f)
                   (j (string-contains s1 s2 start1 end1 start2 end2)))
          (if (and j (< j end1))
              (loop j (string-contains s1 s2 (+ j 1) end1 start2 end2))
              i))))))

(define string-for-each-cursor
  (case-lambda
   ((proc s)
    (string-for-each-cursor proc s 0 (string-length s)))
   ((proc s start)
    (string-for-each-cursor proc s start (string-length s)))
   ((proc s start end)
    (do ((i start (+ i 1)))
        ((>= i end))
      (proc i)))))

(define string-replicate
  (case-lambda
   ((s from to start end)
    (string-replicate (substring s start end) from to))
   ((s from to start)
    (string-replicate (substring s start (string-length s)) from to))
   ((s from to)
    (let* ((n (- to from))
           (len (string-length s)))
      (cond ((= n 0)
             "")
            ((or (< n 0)
                 (= len 0))
             (assertion-violation 'string-replicate
                                  (errmsg 'msg:illegalargs)
                                  s from to))
            (else
             (let* ((from (mod from len)) ; make from non-negative
                    (to (+ from n)))
               (do ((replicates '() (cons s replicates))
                    (replicates-length 0 (+ replicates-length len)))
                   ((>= replicates-length to)
                    (substring (apply string-append replicates)
                               from to))))))))))

(define string-split
  (case-lambda
   ((s delimiter grammar limit start end)
    (string-split (substring s start end) delimiter grammar limit))
   ((s delimiter grammar limit start)
    (string-split (substring s start (string-length s))
                  delimiter grammar limit))
   ((s delimiter)
    (string-split s delimiter 'infix #f))
   ((s delimiter grammar)
    (string-split s delimiter grammar #f))
   ((s delimiter grammar limit)
    (define (complain)
      (assertion-violation 'string-split
                           (errmsg 'msg:illegalargs)
                           s delimiter grammar limit))
    (let* ((limit (or limit (string-length s)))
           (splits
            (cond ((= 0 (string-length delimiter))
                   (string-split-into-characters s limit))
                  (else
                   (string-split-using-word s delimiter limit)))))
      (case grammar
       ((infix strict-infix)
        (if (= 0 (string-length s))
            (if (eq? grammar 'infix)
                '()
                (complain))
            splits))
       ((prefix)
        (if (and (pair? splits)
                 (= 0 (string-length (car splits))))
            (cdr splits)
            splits))
       ((suffix)
        (if (and (pair? splits)
                 (= 0 (string-length (car (last-pair splits)))))
            (reverse (cdr (reverse splits)))
            splits))
       (else
        (complain)))))))

(define (string-split-into-characters s limit)
  (let ((n (string-length s)))
    (cond ((> n (+ limit 1))
           (append (string-split-into-characters (substring s 0 limit) limit)
                   [(substring s limit n)]))
          (else
           (map string (string->list s))))))

;;; FIXME: inefficient

(define (string-split-using-word s sep limit)
  (cond ((= 0 limit)
         (list s))
        (else
         (let ((i (string-contains s sep)))
           (if i
               (cons (substring s 0 i)
                     (string-split-using-word
                      (substring s (+ i (string-length sep)) (string-length s))
                      sep
                      (- limit 1)))
               (list s))))))

(define (string-remove pred s . args)
  (apply string-filter
         (lambda (c) (not (pred c)))
         s
         args))
