(defn- char->value
  "convert the given char c to a value used to calculate the cusip check sum"
  [c]
  (let [int-char (int c)]
    (cond
      (and (>= int-char (int \0)) (<= int-char (int \9))) (- int-char 48)
      (and (>= int-char (int \A)) (<= int-char (int \Z))) (- int-char 55)
      (= c \*) 36
      (= c \@) 37
      (= c \#) 38
     :else nil)))

(defn- calc-sum
  "Calculate cusip sum. nil is returned for an invalid cusip."
  [cusip]
  (reduce
    (fn [sum [i c]]
      (if-let [v (char->value c)]
        (let [v (if (= (mod i 2) 1) (* v 2) v)]
          (+ sum (int (+ (/ v 10) (mod v 10)))))
        (reduced nil)))
    0
    (map-indexed vector (subs cusip 0 8))))

(defn calc-cusip-checksum
  "Given a valid 8 or 9 digit cusip, return the 9th checksum digit"
  [cusip]
  (when (>= (count cusip) 8)
    (let [sum (calc-sum cusip)]
      (when sum
        (mod (- 10 (mod sum 10)) 10)))))

(defn is-valid-cusip9?
  "predicate validating a 9 digit cusip."
  [cusip9]
  (when-let [checksum (and (= (count cusip9) 9)
                           (calc-cusip-checksum cusip9))]
    (= (- (int (nth cusip9 8)) 48)
       checksum)))

(defn rosetta-output
  "show some nice output for the Rosetta Wiki"
  []
  (doseq [cusip ["037833100" "17275R102" "38259P508" "594918104" "68389X106" "68389X105" "EXTRACRD8"
                 "EXTRACRD9" "BADCUSIP!" "683&9X106" "68389x105" "683$9X106" "68389}105" "87264ABE4"]]
    (println cusip (if (is-valid-cusip9? cusip) "valid" "invalid"))))