(** An Ur/Web solution to the TechEmpower web framework benchmarks.
  * For more information:
  * Benchmark specs: http://www.techempower.com/benchmarks/#section=code
  * Ur/Web: http://www.impredicative.com/ur/
  *)

open Json


(** * Utility functions *)

(** The spec requires including particular HTTP response headers that the
  * minimal Ur/Web HTTP servers don't return, so this function is responsible
  * for adding them. *)
fun addHeaders () =
  n <- now;
  setHeader (blessResponseHeader "Date") (timef "%a, %d %b %Y %H:%M:%S GMT" n);
  setHeader (blessResponseHeader "Server") "Ur/Web"

(** This function handles processing of a "queries" string parameter to a few of
  * the benchmarks.  The URL representation is mandated by the spec, so we need
  * to do some manual parsing to handle malformed inputs. :-( *)
fun parseQueries s =
    case read s of
        None => 1
      | Some n => if n > 500 then 500
                  else if n < 1 then 1
                  else n

(** Most of the benchmarks return results as JSON.
  * Here's a handy function to automate wrapping an arbitrary JSONable value as
  * a response. *)
fun returnJson [a] (_ : json a) (j : a) : transaction page =
  addHeaders ();
  returnBlob (textBlob (toJson j)) (blessMime "application/json")

(** Here's a similar, simpler function for returning plain text. *)
fun returnText (t : string) : transaction page =
  addHeaders ();
  returnBlob (textBlob t) (blessMime "text/plain")

(** Finally, an analogous wrapper for HTML *)
fun returnHtml (p : page) : transaction page =
  addHeaders ();
  return p


(** * Test type 1: JSON serialization *)

(** Let's teach the JSON library about a new record type. *)
type json_t = {Message : string}
val json_conversion : json json_t = json_record {Message = "message"}

fun json () =
    returnJson {Message = "Hello, World!"}


(** * Test type 2: Single database query *)

(** This test introduces a database table that a few other tests also use. *)
table world : {Id : int, RandomNumber : int} PRIMARY KEY Id

(** Let's tell the JSON library which record field names to use in
  * serialization. *)
type world_t = {Id : int, RandomNumber : int}
val world_conversion : json world_t =
    json_record {Id = "id", RandomNumber = "randomNumber"}

(** Helper function to look up the entry associated with an ID *)
fun world_find n =
  oneRow1 (SELECT World.Id, World.RandomNumber FROM world
           WHERE World.Id = {[n]})

(** In various tests, we'll be generating random IDs for this table.
  * Here's a quick way to do it, limiting the legal ID range. *)
val random_id =
  n <- rand;
  return ((n % 10000) + 1)

(** Finally, test 2 itself! *)
fun db () =
  n <- random_id;
  row <- world_find n;
  returnJson row


(** * Test type 3: Multiple database queries *)

fun queries s =
  rows <- List.tabulateM (fn _ => n <- random_id; world_find n) (parseQueries s);
  returnJson rows


(** * Test type 4: Fortunes *)

(** A new table, specific to this test *)
table fortune : {Id : int, Message : string} PRIMARY KEY Id

(** Here's the additional fortune mandated by the spec. *)
val new_fortune =
    {Id = 0, Message = "Additional fortune added at request time."}

(** Actual page handler *)
fun fortunes () =
  fs <- queryL1 (SELECT Fortune.Id, Fortune.Message FROM fortune);
  fs' <- return (List.sort (fn x y => x.Message > y.Message)
                           (new_fortune :: fs));
  returnHtml <xml>
    <head><title>Fortunes</title></head>
    <body><table>
      <tr><th>id</th><th>message</th></tr>
      {List.mapX (fn f => <xml><tr>
        <td>{[f.Id]}</td><td>{[f.Message]}</td>
      </tr></xml>) fs'}
    </table></body>
  </xml>


(** * Test type 5: Database updates *)

fun updates s =
  rows <- List.tabulateM (fn _ => n <- random_id; world_find n)
                         (parseQueries s);
  rows' <- List.mapM (fn r => n <- random_id;
                         return (r -- #RandomNumber ++ {RandomNumber = n}))
                     rows;
  List.app (fn r => dml (UPDATE world SET RandomNumber = {[r.RandomNumber]}
                         WHERE Id = {[r.Id]})) rows';
  returnJson rows'


(** * Test type 6: Plaintext *)

fun plaintext () =
  returnText "Hello, World!"
