 (defn match-title
   "Attempts to match listing's title to a product in products, returning the
   matched product if successful, else nil."
-  [products listing]
-    (when-let [title (-> (or (:title listing) "")
+  [products title]
+    (when-let [title (-> (or title "")
                          (str/replace #"[_-]+" ""))]
-    (->> products
-         (filter #(re-find (:regex %) title))
-         first)))
+      (p/p :mt-filter
+           (->> products
+                (filter #(re-find (:regex %) title))
+                first))))
 
 
 (p/defnp match-listing

  [products listing]
  (when-let [manufacturer-guess
             (p/p :match-manuf (match-manufacturer (keys products)
                                 (or (:manufacturer listing)
                                     (first-word (:title listing)))))]
    (if-let [product-guess (p/p :match-title (match-title (products manufacturer-guess)
                                        listing))]
      [product-guess listing]
      [nil listing])))


  [path]
  (with-open [rdr (io/reader path)]
    (->> (line-seq rdr)
         ; We need to parse non-lazily (mapv), because otherwise with-open will
         ; close the stream before the seq is realized.
         (mapv parse-json))))

(p/defnp product-regex
  "Generates a case-insensitive regex pattern that represents the product."
  [product]
  (let [fmt-model (fn [s]
                    (-> (or s "")
                        ; [_ -] chars are equivalent and optional
                        (str/replace #"[\s_-]+" "[\\\\s_-]*")
                        ; common model prefixes are optional
                        (str/replace #"(?i)(DSC|DSLR|DMC|PEN|SLT|IS)" "(?:$1)?")))]
    (re-pattern
      ; Roughly: word{0,3} family? word? model
      (str #"(?i)^(?:[\w()]*\s*){0,3}"
           (when-let [family (:family product)]
             (str "(?:" family ")?"
                  #"\s*\w*\s*"))
           (fmt-model (:model product))
           ; Don't match if model is directly followed by a number, eg.
           ; don't accept "EOS 100" for "EOS 10".
           #"(?:[^\d]+|$)"))))

(defn prepare-product [product]
  (assoc product :regex (product-regex product)))

(defn first-word
  "Returns first word in s, defined by splitting on whitespace. Not lazy."
  ; For large strings (10000 chars or more), the lazy version using partition-by
  ; is faster.
  [s]
  (-> (or s "")
      (str/split #"\s+" 2)  ; Split at most once
      first))
