<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
 <head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>AI RPG</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<style type="text/css">
body {
    margin: 0;
    background-color: black;
}
.game {
    position: absolute;
    top: 0px;
    left: 0px;
    margin: 0px;
    border: 0;
    width: 100%;
    height: 100%;
    overflow: hidden;
    display: block;
    image-rendering: optimizeSpeed;
    image-rendering: -moz-crisp-edges;
    image-rendering: -o-crisp-edges;
    image-rendering: -webkit-optimize-contrast;
    image-rendering: optimize-contrast;
    image-rendering: crisp-edges;
    image-rendering: pixelated;
    -ms-interpolation-mode: nearest-neighbor;
}
#inputdiv, #buy_menu {
    position: absolute;
    top: 0px;
    left: 0px;
    margin: 0px;
    border: 0;
    width: 100%;
    height: 100%;
    justify-content: center;
    align-items: center;
}
#inputdiv {
    z-index: 10;
    display: none; /* set to flex by javascript */
    overflow: hidden;
    background: #00000090;
}
#inputtext {
    position: absolute;
    top: 0;
    left: 0;

    background: none;
    width: 90%;
    height: 50%;
    margin-left: 5%;
    margin-right: 5%;
    border-top-style: hidden;
    border-right-style: hidden;
    border-left-style: hidden;
    border-bottom-style: hidden;
    transform: translateY(100%);
    text-align: center;
    font-size: 2em;
    font-family: Arial;
    color: white;
}

#inputtext:focus {
 outline: none;
}

#inputbuttondiv {
 position: absolute;
 display: flex;
 width: 100vw;
 justify-content: center;
 flex-wrap: wrap;
 height: 170px;
 margin-top: 40vh;
}

#inputbutton {
 font-family: Arial;
 font-size: 3em;
 color: white;
 background: none;
 border-radius: 10px;
 border: 2px solid;
 border-color: white;
 padding: 10px;
 margin: 30px;
}

#buy_menu {
 display: none; /* set in javascript when shown when error code */
 flex-direction: column;
 justify-content: center;
 word-wrap: break-word;
 align-items: center;
 text-align: center;
 height: 100vh;
 background: #000000D0;
 font-family: arial;
 gap: 20px;
 color: #fff;
 z-index: 20;
}

#buy_menu h1, #daypass {
 margin-left: 30px;
 margin-right: 30px;
}

/* Style the payment screen title */
#buy_menu h1 {
 font-size: 32px;
}

/* Style the payment button */
#payment-button, #submit-button {
 background-color: #fff;
 color: #000;
 font-size: 24px;
 padding: 10px 20px;
 border: none;
 border-radius: 5px;
 cursor: pointer;
}
#daypass {
    border: 4px solid white;
    border-radius: 25px;
    padding: 10px;
    display: flex;
    gap: 20px;
}
#codebox {
    border: 4px solid white;
    border-radius: 10px;
    padding: 10px;
    margin: 30px;
}
#visibility-toggle, #closebutton {
 border: none;
 font-size: 30px;
 background: none;
 color: white;
}
#closebutton {
 float: right;
}
/* Media queries for smaller screens */
@media screen and (max-width: 480px) {
 #buy_menu h1 {
   font-size: 24px;
   margin-bottom: 20px;
 }
 
 #payment-button {
   font-size: 20px;
   padding: 8px 16px;
 }
}
</style>
  <link rel="icon" type="image/x-icon" href="/favicon.ico">
 </head>

<body style="background:black">
 <div id="buy_menu">
  <h1>AI is expensive so you need to pay for it</h1>
  <p>You can play free 10 minutes a day. If you're seeing this you must buy a day pass to continue. If you already bought one, but are still seeing this, check that the code matches up. If you're unsatisfied with your service, you can get a refund via the email stripe sent you.</p>
  <div id="daypass">
   <button id="closebutton" onclick="closepayment()"><i class="fa-regular fa-xmark"></i></button>
   <p>24 Hour Pass</p>
   <button id="payment-button" onclick="do_checkout()">Buy Now</button>
  </div>
  <div id="codebox">
   <label for="pass">Day pass code:</label>
   <input type="password" name="pass" id="codeinput" onkeyup='on_codeinput_key(event);' required>
   <button id="visibility-toggle" onclick="visibility_toggle()"><i class="fa fa-eye-slash" id="visibility-toggle-eye"></i></button>
  </div>
 </div>
 <div id="inputdiv" class="inputdiv">
  <textarea onkeyup='on_textarea_key(event);' id="inputtext">
  </textarea>
  <div id="inputbuttondiv">
  <button id="inputbutton" onclick="end_dialog()"> Submit </button>
  <button id="inputbutton" onclick="end_without_saying()"> Cancel </button>
  </div>
 </div>
  <canvas class="game" id="canvas" oncontextmenu="event.preventDefault()" width="1504" height="864"></canvas>

  <script type="text/javascript">
    var Module = {
        preRun: [],
        postRun: [],
        print: (function() {
            return function(text) {
                text = Array.prototype.slice.call(arguments).join(' ');
                console.log(text);
            };
        })(),
        printErr: function(text) {
            text = Array.prototype.slice.call(arguments).join(' ');
            console.error(text);
        },
        canvas: (function() {
            var canvas = document.getElementById('canvas');
            canvas.addEventListener("webglcontextlost", function(e) { alert('FIXME: WebGL context lost, please reload the page'); e.preventDefault(); }, false);
            return canvas;
        })(),
        setStatus: function(text) { },
        monitorRunDependencies: function(left) { },
    };
    window.onerror = function(event) {
        console.log("onerror: " + event.message);
			  for(;;) {}
    };
  </script>
  <script type="text/javascript">

let game_doing_input = true;
let server_url = "";
let codeinput = document.getElementById("codeinput");
function set_my_code(new_code) {
 let final_codeinput_string = "";
 for(let i = 0; i < new_code.length; i++) {
  let cur = new_code[i];
  let cur_charcode = cur.charCodeAt(0);
  if(cur_charcode >= "a".charCodeAt(0) && cur_charcode <= "z".charCodeAt(0)) {
   final_codeinput_string += String.fromCharCode(cur_charcode + ("A".charCodeAt(0) - "a".charCodeAt(0)));
  } else {
   if((cur_charcode >= "A".charCodeAt(0) && cur_charcode <= "Z".charCodeAt(0)) || (cur_charcode >= "0".charCodeAt(0) && cur_charcode <= "9".charCodeAt(0))) {
    final_codeinput_string += cur;
   }
  }
 }
 codeinput.value = final_codeinput_string;
}
function get_my_code() {
 return codeinput.value;
}
let code_visible = false;
let input_modal = document.getElementById("inputdiv");
let pause_modal = document.getElementById("buy_menu");
let save_game_data = null;

function visibility_toggle() {
 if(code_visible) {
  code_visible = false;
  document.getElementById("codeinput").type = "password";
  document.getElementById("visibility-toggle-eye").className = "fa fa-eye-slash";
 } else {
  code_visible = true;
  document.getElementById("codeinput").type = "text";
  document.getElementById("visibility-toggle-eye").className = "fa fa-eye";
 }
}

function frame(delta) {
 let input_visible = input_modal.style.display === "flex";
 let pause_visible = pause_modal.style.display === "flex";

 if( Module.ccall('is_receiving_text_input', 'bool', [], [])) {
  if(!input_visible) {
   document.getElementById("inputtext").value = "";
   setTimeout(function(){document.getElementById("inputtext").focus();},50); // for some reason focus doesn't work immediately here
   document.getElementById("inputdiv").style.display = "flex";
  }
 }

 if( (input_visible || pause_visible) && game_doing_input)
 {
  Module.ccall('stop_controlling_input', 'void', [], []);
  game_doing_input = false;
 }
 if( !input_visible && !pause_visible && !game_doing_input)
 {
  Module.ccall('start_controlling_input', 'void', [], []);
  game_doing_input = true;
 }

 window.requestAnimationFrame(frame);
}
Module.onRuntimeInitialized = () => {
 window.requestAnimationFrame(frame);
}

const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
// Use a lookup table to find the index.
const lookup = typeof Uint8Array === 'undefined' ? [] : new Uint8Array(256);
for (let i = 0; i < chars.length; i++) {
    lookup[chars.charCodeAt(i)] = i;
}
const encode = (arraybuffer) => {
    let bytes = new Uint8Array(arraybuffer), i, len = bytes.length, base64 = '';
    for (i = 0; i < len; i += 3) {
        base64 += chars[bytes[i] >> 2];
        base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
        base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
        base64 += chars[bytes[i + 2] & 63];
    }
    if (len % 3 === 2) {
        base64 = base64.substring(0, base64.length - 1) + '=';
    }
    else if (len % 3 === 1) {
        base64 = base64.substring(0, base64.length - 2) + '==';
    }
    return base64;
};
const decode = (base64) => {
    let bufferLength = base64.length * 0.75, len = base64.length, i, p = 0, encoded1, encoded2, encoded3, encoded4;
    if (base64[base64.length - 1] === '=') {
        bufferLength--;
        if (base64[base64.length - 2] === '=') {
            bufferLength--;
        }
    }
    const arraybuffer = new ArrayBuffer(bufferLength), bytes = new Uint8Array(arraybuffer);
    for (i = 0; i < len; i += 4) {
        encoded1 = lookup[base64.charCodeAt(i)];
        encoded2 = lookup[base64.charCodeAt(i + 1)];
        encoded3 = lookup[base64.charCodeAt(i + 2)];
        encoded4 = lookup[base64.charCodeAt(i + 3)];
        bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
        bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
        bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
    }
    return arraybuffer;
};


function end_dialog() {
 document.getElementById("inputdiv").style.display = "none";
 Module.ccall('end_text_input', 'void', ['string'], [document.getElementById("inputtext").value]);
}

function show_paywall() {
 document.getElementById('buy_menu').style.display = "flex";
}

function hide_paywall() {
 document.getElementById('buy_menu').style.display = "none";
}

function on_codeinput_key(event) {
 set_my_code(get_my_code());
}

function save_all() {
 document.cookie = get_my_code();
 if (typeof(Storage) !== "undefined") {
  console.log("Saving full game");
  Module.ccall('dump_save_data', 'void', [], []);
  localStorage.setItem("game", encode(save_game_data));
 }
}

function load_all() {
 set_my_code(document.cookie);
 if (typeof(Storage) !== "undefined") {
  console.log("Attempting read of full game...");
  let read_data = localStorage.getItem("game");
  if (read_data != null) {
   console.log("Reading full game");
   let decoded = decode(read_data);
   Module.ccall('read_from_save_data', 'void', ['array', 'number'], [new Uint8Array(decoded), decoded.byteLength]);
  }
 }
}

function end_without_saying()
{
 document.getElementById("inputtext").value = "";
 end_dialog();
}

function on_textarea_key(event) {
 let final_textarea_string = "";
 let cur_textarea_string = document.getElementById("inputtext").value;
 let should_end = false;
 for(let i = 0; i < Math.min(cur_textarea_string.length, 800); i++)
 {
  let cur = cur_textarea_string[i];
  if(cur === "\n") {
   should_end = true;
   continue;
  }
  if(cur.charCodeAt(0) >= 255) continue; // non ascii gtfo
  if(cur === "|") continue; // used for splitting
  final_textarea_string += cur_textarea_string[i];
 }
 document.getElementById("inputtext").value = final_textarea_string;
 if(event.key === "Enter") should_end = true;
 if(event.key === "Escape")
 {
  document.getElementById("inputtext").value = "";
  should_end = true;
 }
 // have a boolean flag here and call it once so the game's end dialog function isn't called twice
 if(should_end) end_dialog();
}

const max_retries = 5;
let cur_id = 1; // zero is not a valid id, the zero value means no async request currently active
let generation_requests = []; // array of dictionaries with structure:
/*
   { id: number,
   request: XMLHTTPRequest,
   request_info: { url: string, body: string }
   retries_remaining: number, // on failure, retries over and over until out of retries
   }
 */
function do_checkout() {
 fetch(server_url + "/checkout").then((response) => response.text()).then((text) => {
  split_up = text.split("|");
  if(split_up.length !== 2) {
   console.log("Weird response from server, length isn't 2");
  } else {
   set_my_code(split_up[0])
   save_all();
   let url_to_go_to = split_up[1];
   window.location.href = url_to_go_to;
  }
 }).catch((error) => {
   console.log("Error doing checkout: " + error);
 });
}

function resend_request(r) {
    r.failed = false;
    r.request = new XMLHttpRequest();
    r.request.onerror = function(e) {
     r.failed = true;
    };
    r.request.open("POST", r.request_info.url, true);
    r.request.send(get_my_code() + "|" + r.request_info.body);
}

// server URL is decided in C
function set_server_url(p) {
 server_url = p;
}

// Returns an integer, a "handle" into the generation request. Takes in the string prompt, and api URL
// the api url must start with `http://` or https.
function make_generation_request(p, api) {

 cur_id += 1;
 let to_push = {
  "id": cur_id,
  "request": new XMLHttpRequest(),
  "retries_remaining": max_retries,
  "request_info": {"url": (' ' + api).slice(1), "body": p},
  "failed": false,
 }
 console.log("Making generation request with id " + to_push.id);
 generation_requests.push(to_push)
 resend_request(to_push)
 return cur_id;
}

// returns 0 if not done yet, 1 if succeeded, 2 if failed after too many retries (i.e server down, or no internet)
// -1 if it doesn't exist
function get_generation_request_status(id) {
 for(let i = 0; i < generation_requests.length; i++) {
  if(generation_requests[i].id == id) {
   if(generation_requests[i].failed)
   {
   }
   else
   {
    let http_status = generation_requests[i].request.status;
    if(http_status == 200) {
     if(generation_requests[i].request.responseText[0] === "0")
     {
      show_paywall();
      return 2;
     }
     else if(generation_requests[i].request.responseText[0] === "1")
     {
      return 1; // done, everything ok
     }
     else
     {
      console.log("Unknown body code " + generation_requests[i].request.responseText);
     }
    }
    else if(http_status == 0) { // not done yet
     return 0;
    }
   }

   { // errored
    if(generation_requests[i].retries_remaining > 0) {
     console.log("Retrying request");
     generation_requests[i].retries_remaining -= 1;
     resend_request(generation_requests[i]);
     return 0;
    } else {
     return 2; // too many retries, failed
    }
   }
  }
 }
 return -1;
}

function done_with_generation_request(id) {
 console.log("Removing request with id " + id);
 let new_generation_requests = [];
 for(let i = 0; i < generation_requests.length; i++) {
  if(generation_requests[i].id == id)
  {
  } else {
   new_generation_requests.push(generation_requests[i])
  }
 }
 generation_requests = new_generation_requests;
}

// doesn't fill string if not done yet, or the id doesn't exist
function get_generation_request_content(id) {
 let to_return = "";
 for(let i = 0; i < generation_requests.length; i++) {
  if(generation_requests[i].id == id) {
   to_return = generation_requests[i].request.responseText;
   break;
  }
 }
 return to_return.slice(1); // first character is a code that says if the request was successful or not
}
  </script>
  {{{ SCRIPT }}}

</body></html>