<html>
    <head>
        <title>test</title>
        <link rel="stylesheet" type="text/css" href="style.css">
    </head>
    <body>
        <div id="root">
    </body>
    <script type="module">

     import init, { age_encrypt, age_decrypt } from "./rage_wasm.js";

     /// the basic idea here is to have functions which construct parts
     /// of the DOM that renders the survey to a user. These functions
     /// are all free of side effects (as far as possible, i.e. apart
     /// from creating DOM elements), and each returns the root element
     /// it just created + a callback function which will be used later
     /// to check which values the user entered in the corresponding
     /// parts of the survey.
     /// In other words, this misuses lots of lambdas to get a kind of
     /// polymorphism which would otherwise be hard to get in javascript.

     let root = document.getElementById("root");
     let surveyUrl = window.location.hash.slice(1);

     // appends multiple children to an element. returns the root
     // element to be composable
     function appendChildren (root, children) {
         for (var child of children) {
             root.appendChild (child);
         }
         return root;
     }

     function mkElement (kind, text = "") {
         let p = document.createElement(kind);
         p.innerText = text;
         return p;
     }


     /// creates an option with a label next to it.
     /// mainly for list-like things, but also the password field
     function mkOption (kind, option="", qid="") {
         let i = document.createElement("input");
         i.type = kind;
         i.name = qid;
         i.value = option;
         i.id = option;
         let l = mkElement("label", option);
         l.htmlFor = option;
         return [i,l];
     }

     // for all kinds of multiple-choise like answer spaces
     function mkListSpace(kind, options, qid) {
         let ul = mkElement("ul")
         let buttons = options.map((option) =>
             mkOption(kind, option, qid)
         );
         appendChildren (ul, buttons.map((button) => {
             let li = mkElement("li");
             appendChildren(li, button);
             return li;
         }));
         // return the answer chosen by the user. This function uses
         // null to indicate that a choice was invalid, i.e. the form
         // is not ready for submission yet.
         let getAnswer = () => {
             let selected = buttons
                 .filter((b) => b[0].checked)
                 .map((b) => b[0].value);
             if (kind === "radio")
                 return selected.length == 0 ? null : selected[0];
             if (kind === "checkbox")
                 return selected;
             console.err("PartialityError: encountered unknown list type!")
         }
         return [ul, getAnswer];
     }

     // for freeform text answers (TODO)
     function mkFreeform (placeholder) {
         let text = mkElement("textarea");
         text.placeholder = placeholder;
         return [
             text,
             () => text.value
         ];
     }

     // essentially a case - of statement over the answerspace type.
     // since there is no validation of the config json, it may turn
     // out to be a partial function in practice.
     function mkAnswerSpace(space, qid) {
         if (space === "YesOrNo") {
             return mkListSpace("radio", ["yes", "no"], qid);
         } if ("Single" in space) {
             return mkListSpace("radio", space.Single, qid);
         } if ("Multiple" in space) {
             return mkListSpace("checkbox", space.Multiple, qid);
         } if ("Freeform" in space) {
             return mkFreeform(space.Freeform);
         }
         console.err("PartialityError: encountered unknown AnswerSpace type!");
     }

     // makes a survey from a given json config object
     function mkSurvey (survey) {
         document.title = survey.title;
         // make the header with general information
         let header = document.createElement("div");
         appendChildren(header, [
             mkElement("h1", survey.title),
             mkElement("p", survey.description)
         ]);
         root.appendChild(header);

         // map question configs to their callbacks. note that
         // this iterator uses a function with side-effects, which
         // append the created objects to the DOM survey root.
         let callbacks = survey.questions.map ((question) => {
             let section = document.createElement("section");
             let [answerspace, whichselection] =
                 mkAnswerSpace(question.space, question.name);
             appendChildren(section, [
                 mkElement("h2", question.name),
                 mkElement("p", question.question),
                 //mkAnswerSpace(question.space, question.name)
                 answerspace
             ]);
             root.appendChild(section);
             return whichselection;
         });

         let footer = mkElement("section");
         let submit = mkElement("button", "Submit");
         appendChildren(footer, [
             mkElement("hr"),
             submit
         ]);
         submit.onclick = () => {
             // the callback over the complete survey just maps all
             // other callbacks to their values, i.e. calls them all
             let answers = callbacks.map((c) => c());
             console.log("answers given by user:", answers);
             // encrypt things
             let byteArray = age_encrypt(JSON.stringify(answers), survey.pubkey);

             let blobData = new Blob(
                 [byteArray],
                 { type: 'application/octet-stream' }
             );

             fetch("/upload", {
                 method: "POST",
                 body: blobData,
                 headers: {
                     "X-Survey": survey.title,
                     "Content-Type": "text/age"
                 }
             }).then(response => console.log(response.text()))

         }
         root.appendChild(footer);

         return () => {
             return callbacks.map ((c) => c())
         };
     }


     /// displays a passphrase prompt until the use enters a passphrase
     /// which age can use for successful decryption
     function askPassphrase (ciphertext, secondTry=false) {
         document.title = "Enter Passphrase";
         let div = mkElement("div");
         let button = mkElement("button", "decrypt");
         let [passphrase,label] = mkOption(
             "password",
             "please enter a passphrase to access the survey:"
         );
         passphrase.value = "";
         appendChildren(div, [
             mkElement("h1", "Passphrase"),
             appendChildren(mkElement("p"),[label]),
             passphrase,
             button
         ].concat(
             secondTry ? [mkElement("p","passphrase was incorrect!")] : []
         ));
         root.appendChild(div);
         button.onclick = () => {
             console.log("trying passphrase ...");
             let decrypted = age_decrypt (
                 ciphertext,
                 passphrase.value
             );
             /// if we can't decrypt, the passphrase was wrong
             if (decrypted === undefined) {
                 div.remove();
                 askPassphrase (ciphertext, true);
             } else {
                 let survey = JSON.parse(decrypted);
                 div.remove();
                 mkSurvey(survey);
             }
         };
     }

     async function main () {
         // initialise the web assembly parts of this
         await init();
         const Http = new XMLHttpRequest();
         const url=  surveyUrl;
         Http.open("GET", url);
         Http.responseType = "arraybuffer";
         Http.send();

         Http.onreadystatechange = (e) => {
             if (Http.readyState == 4) {
                 if (Http.status == 200) {
                     let bytearray = new Uint8Array (Http.response);
                     let string = String.fromCharCode.apply(null, bytearray);
                     let survey = null;
                     try {
                         survey = JSON.parse(string);
                     } catch (e) {
                         console.log ("survey appears to be encrypted");
                         askPassphrase(bytearray);
                     }
                     /// if the survey was unencrypted, start it here. If it
                     /// was encrypted, we need to wait for user action via
                     /// a js callback (handled in askPassphrase).
                     if (survey !== null) {
                         mkSurvey(survey);
                     }
                 // couldn't load survey json, show error message
                 } else {
                     appendChildren(root, [
                         mkElement("h1", "Error"),
                         mkElement("p", "Could not load this survey; are you sure that it exists?"),
                         appendChildren(
                             mkElement("p","attempted path: "),
                             [mkElement("tt", surveyUrl)]
                         )
                     ]);
                 }
             }
         }
     }


     main ()
    </script>
</html>