;;   Copyright (c) Nicola Mometto, Rich Hickey & contributors.
;;   The use and distribution terms for this software are covered by the
;;   Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
;;   which can be found in the file epl-v10.html at the root of this distribution.
;;   By using this software in any fashion, you are agreeing to be bound by
;;   the terms of this license.
;;   You must not remove this notice, or any other, from this software.

(ns cljs.tools.reader.impl.commons
  (:refer-clojure :exclude [char])
  (:require
   [cljs.tools.reader.reader-types :refer [peek-char read-char reader-error]]
   [cljs.tools.reader.impl.utils :refer [numeric? newline? char]]))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; helpers
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defn ^boolean number-literal?
  "Checks whether the reader is at the start of a number literal"
  [^not-native reader initch]
  (or (numeric? initch)
      (and (or (identical? \+ initch) (identical?  \- initch))
           (numeric? (peek-char reader)))))

(defn read-past
  "Read until first character that doesn't match pred, returning
   char."
  [pred ^not-native rdr]
  (loop [ch (read-char rdr)]
    (if ^boolean (pred ch)
      (recur (read-char rdr))
      ch)))

(defn skip-line
  "Advances the reader to the end of a line. Returns the reader"
  [^not-native reader]
  (loop []
    (when-not (newline? (read-char reader))
      (recur)))
  reader)

(def int-pattern #"^([-+]?)(?:(0)|([1-9][0-9]*)|0[xX]([0-9A-Fa-f]+)|0([0-7]+)|([1-9][0-9]?)[rR]([0-9A-Za-z]+)|0[0-9]+)(N)?$")
(def ratio-pattern #"([-+]?[0-9]+)/([0-9]+)")
(def float-pattern #"([-+]?[0-9]+(\.[0-9]*)?([eE][-+]?[0-9]+)?)(M)?")

(defn- match-int
  [s]
  (let [m (vec (re-find int-pattern s))]
    (if-not (nil? (m 2))
      0
      (let [^boolean negate? (identical? "-" (m 1))
            a (cond
               (not (nil? (m 3))) [(m 3) 10]
               (not (nil? (m 4))) [(m 4) 16]
               (not (nil? (m 5))) [(m 5) 8]
               (not (nil? (m 7))) [(m 7) (js/parseInt (m 6))]
               :else              [nil nil])
            n (a 0)]
        (when-not (nil? n)
          (let [bn (js/parseInt n (a 1))
                bn (if negate? (* -1 bn) bn)]
            (when-not (js/isNaN bn)
              bn)))))))

(defn- match-ratio
  [s]
  (let [m (vec (re-find ratio-pattern s))
        numerator (m 1)
        denominator (m 2)
        numerator (if (re-find #"^\+" numerator)
                    (subs numerator 1)
                    numerator)]
    (/ (-> numerator   js/parseInt) ;;; No ratio type in cljs
       (-> denominator js/parseInt)))); So will convert to js/Number

(defn- match-float
  [s]
  (let [m (vec (re-find float-pattern s))]
    (if-not (nil? (m 4)) ;; for BigDecimal "10.03M", as all parsed to js/Number
      (js/parseFloat (m 1))
      (js/parseFloat s))))

(defn ^boolean matches? [pattern s]
  (let [[match] (re-find pattern s)]
    (identical? match s)))

(defn match-number [s]
  (if (matches? int-pattern s)
    (match-int s)
    (if (matches? float-pattern s)
      (match-float s)
      (when (matches? ratio-pattern s)
        (match-ratio s)))))

(defn parse-symbol
  "Parses a string into a vector of the namespace and symbol"
  [token]
  (when-not (or (identical? "" token)
                (true? (.test #":$" token))
                (true? (.test #"^::" token)))
    (let [ns-idx (.indexOf token "/")
          ns (when (pos? ns-idx)
               (subs token 0 ns-idx))]
      (if-not (nil? ns)
        (let [ns-idx (inc ns-idx)]
          (when-not (== ns-idx (count token))
            (let [sym (subs token ns-idx)]
              (when (and (not (numeric? (nth sym 0)))
                         (not (identical? "" sym))
                         (false? (.test #":$" ns))
                         (or (identical? sym "/")
                             (== -1 (.indexOf sym "/"))))
                [ns sym]))))
        (when (or (identical? token "/")
                  (== -1 (.indexOf token "/")))
          [nil token])))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; readers
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defn read-comment
  [rdr & _]
  (skip-line rdr))

(defn throwing-reader
  [msg]
  (fn [rdr & _]
    (reader-error rdr msg)))
