(ns clj-time.spec
  "This namespace requires Clojure 1.9 or later. It defines a set of predicates plus a set of spec defs with associated generators."
  (:require [clojure.spec.alpha :as spec]
            [clojure.spec.gen.alpha :as gen]
            [clj-time.types :refer [date-time? local-date-time? local-date? time-zone?]]
            [clj-time.core :refer [date-time]]
            [clj-time.coerce :refer [to-date-time to-long]])
  (:import [org.joda.time DateTime DateTimeZone LocalDate LocalDateTime]
           [org.joda.time.base BaseDateTime]
           [java.util TimeZone]))

(def all-time-zones
  (delay
    (set
      (keep #(try (DateTimeZone/forTimeZone (TimeZone/getTimeZone ^String %))
                  (catch Throwable t nil))
            (TimeZone/getAvailableIDs)))))

(defn ^:dynamic *time-zones*
  "Dynamically bind this to choose which time zones to use in generators."
  []
  (gen/one-of [(gen/return DateTimeZone/UTC)
               (spec/gen @all-time-zones)]))

(spec/def ::past (spec/int-in (to-long (date-time 2001 1 1 00 00 00))
                              (to-long (date-time 2010 12 31 00 00 00))))

(spec/def ::past-and-future (spec/int-in (to-long (date-time 2011 1 1 00 00 00))
                                         (to-long (date-time 2030 12 31 23 59 59))))

(spec/def ::future (spec/int-in (to-long (date-time 2031 1 1 0 00 00))
                                (to-long (date-time 2040 12 31 23 59 59))))

(defn ^:dynamic *period*
  "Dynamically bind this to choose the range of your generated dates."
  []
  (spec/gen ::past-and-future))

(spec/def ::time-zone
          (spec/with-gen time-zone? *time-zones*))

(spec/def ::date-time
          (spec/with-gen date-time?
                         #(gen/fmap (fn [ms] (DateTime. ms DateTimeZone/UTC))
                                    (*period*))))

(spec/def ::local-date
          (spec/with-gen local-date?
                         #(gen/fmap (fn [ms] (LocalDate. ms))
                                    (*period*))))

(spec/def ::local-date-time
          (spec/with-gen local-date-time?
                         #(gen/fmap (fn [ms] (LocalDateTime. ms))
                                    (*period*))))
