 (ns db-quiz-prep.prepare
   (:require [db-quiz-prep.sparql :as sparql]
             [clojure.java.io :as io]
-            [clojure.edn :as edn]))
+            [clojure.edn :as edn]
+            [stencil.core :refer [render-string]]))
+
+; ----- Private functions -----
+
+(def ^:private degrees->radians
+  "Convert angle in degrees to radians"
+  (partial * (/ Math/PI 180)))
+
+(defn- round
+  "Round a `number` to a given `precision` decimal places."
+  [precision number]
+  (let [rounding-factor (Math/pow 10 precision)]
+    (/ (Math/round (* number rounding-factor))
+       rounding-factor)))
+
+(def ^:private round-2
+  "Round to 2 decimal places"
+  (partial round 2))
 
 (defn- sign
+  "Tell the sign of the `number`."
   [number]
   (if (neg? number) - +))
 
+; ----- Public functions -----
+
 (defn bisection
+  "Use bisection method to find the root of function `f` in the interval [a, b].
+  `tolerance` the maximum deviation from 0.
+  `nmax` is the maximum number of iteration of the bisection before it fails."
   ([n f a b tolerance nmax]
    (if (<= n nmax)
      (let [c (/ (+ a b) 2)
            fc (f c)]
-       (if (< (/ (- b a) 2) tolerance)
+       (if (< (Math/abs (float (/ (- b a) 2))) tolerance)
          c
          (if (= (sign fc) (sign (f a)))
            (recur (inc n) f c b tolerance nmax)

           (mapv parse-int [indegree frequency]))
         (sparql/execute-unlimited-select-query config query))))

(defn materialize-difficulty
  [config]
  (let [indegree-histogram (get-indegree-histogram config)]
    indegree-histogram))

(defn materialize-questions
  "Materialize questions based on provided configuration"
  [config]
  (let [update-template (slurp (io/resource "materialize_questions.mustache"))]
    (sparql/execute-unlimited-update config materialize-questions)))

(defn execute
  ""
  [{{:keys [page-size]
      :or {page-size 10000}} :sparql-endpoint
     :keys [data start-from]
     :or {start-from 0}
     :as config}]
  (let [config' (merge config {:page-size page-size
                               :start-from start-from
                               :params data})]
    ;(materialize-questions config')
    (materialize-difficulty config')))

(comment
  (def config (edn/read-string (slurp "config.edn")))
  (def indegrees (execute config))
  (def f (partial estimate-b indegrees))
  (float (bisection f -10 0 0.00001 100))
  )

   (bisection 1 f a b tolerance nmax)))

(defn estimate-b
  "Estimates parameter b of an exponential function based on `indegree-histogram`."
  [indegree-histogram b]
  (letfn [(sum-map [f] (reduce + (map f indegree-histogram)))]
    (- (* (sum-map (fn [[x _]] (* x (Math/exp (* 2 b x)))))
          (/ (sum-map (fn [[x y]] (* y (Math/exp (* b x)))))
             (sum-map (fn [[x _]] (Math/exp (* 2 b x))))))
       (sum-map (fn [[x y]] (* y x (Math/exp (* b x))))))))

(defn compute-a
  "Computes parameter a of an exponential function with parameter `b`
  based on `indegree-histogram`."
  [indegree-histogram b]
  (letfn [(sum-map [f] (reduce + (map f indegree-histogram)))]
    (/ (sum-map (fn [[x y]] (* y (Math/exp (* b x)))))
       (sum-map (fn [[x _]] (Math/exp (* 2 b x)))))))

(defn get-indegree-limit
  "Get limiting indegree for exponential function parameterized with `a` and `b` at `angle` in degrees."
  [a b angle]
  (round-2 (/ (Math/log (/ (Math/tan (degrees->radians angle))
                           (* a b)))
              b)))

(defn get-indegree-histogram
  "Compute indegree histogram based on the provided configuration.
