Code viewer for World: AI Art Gallery
// import * as THREE from "three";

var rawHTML = `
    <style>
        #promptInput, #sendPrompt, #inputArea {
            display: none;
        }
        #apiKey, #inputArea {
          position: absolute;
          top: 50%; 
          left: 50%;
          transform: translate(-50%, -50%); 
          z-index: 100; /* Ensures it's above other elements */
          background: #a2a2a2a2; 
          padding: 20px; 
          border-radius: 10px;
          box-shadow: 0 0 10px rgba(0,0,0,0.5); 
        }
        #apiInput, #promptInput {
          width: 300px;
          height: 30px;
          font-size: 20px;
          padding: 5px;
          border-radius: 5px;
          border: 1px solid #000;
        }
        #promptInput {
          width: 600px;
          height: 30px;
          font-size: 20px;
          padding: 5px;
          border-radius: 5px;
          border: 1px solid #000;
        }

        #setApi {
          width: 100px;
          height: 40px;
          font-size: 20px;
          border-radius: 5px;
          padding: 5px;
          border: 1px solid #000;
          cursor: pointer;
        }
    </style>
    <div id="inputArea">
        <input type="text" id="promptInput" placeholder="Enter image prompt" />
        <button id="sendPrompt">Generate Image</button>
    </div>
    <div id="apiKey">
        <input type="text" id="apiInput" placeholder="Enter Api Key" />
        <button id="setApi">Set Key</button>
    </div>`;

document.body.innerHTML += rawHTML;

// Prompt input setup
const apiKeyInput = document.getElementById("apiKey");
const apiInput = document.getElementById("apiInput");
const setApiButton = document.getElementById("setApi");

// Event listener for the set API key button
setApiButton.addEventListener("click", function () {
  if (apiInput.value == "") return; // no empty API keys
  apikey = apiInput.value;
  apiKeySet = true;
  apiKeyInput.style.display = "none"; // Hide the prompt input
  viewInput.style.display = "none";
  getMusic();
});

// Basic setup
var camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  1,
  1000
);

var scene = new THREE.Scene();
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// skybox setup

var loader = new THREE.CubeTextureLoader();
var texture = loader.load([
  "uploads/yammers/DaylightRight.png",
  "uploads/yammers/DaylightLeft.png",
  "uploads/yammers/DaylightTop.png",
  "uploads/yammers/DaylightBottom.png",
  "uploads/yammers/DaylightFront.png",
  "uploads/yammers/DaylightBack.png",
]);

scene.background = texture;

// Lighting setup
var ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);

var directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(1, 1, 1).normalize();
scene.add(directionalLight);

var directionalLight2 = new THREE.DirectionalLight(0xffffff, 1);
directionalLight2.position.set(-1, -1, -1).normalize();
scene.add(directionalLight2);

// Floor setup
var floorGeometry = new THREE.PlaneGeometry(1000, 1000, 10, 10);
var floorMaterial = new THREE.MeshLambertMaterial({ color: 0x808080 });

// For a custom textured
var textureLoader = new THREE.TextureLoader();
var floorTexture = textureLoader.load("uploads/yammers/floor-Copy.jpg"); // Replace with path to texture

floorTexture.wrapS = floorTexture.wrapT = THREE.RepeatWrapping;
floorTexture.repeat.set(50, 50);

var floorMaterial = new THREE.MeshLambertMaterial({ map: floorTexture });
var floor = new THREE.Mesh(floorGeometry, floorMaterial);

floor.rotation.x = -Math.PI / 2;
floor.position.y = -0.5;
scene.add(floor);

function createGalleryWall(width, height, depth, position, texturePath) {
  var geometry = new THREE.BoxGeometry(width, height, depth);
  var textureLoader = new THREE.TextureLoader();
  var wallTexture = textureLoader.load(texturePath);

  wallTexture.wrapS = wallTexture.wrapT = THREE.RepeatWrapping;
  wallTexture.repeat.set(1, 1);

  var material = new THREE.MeshLambertMaterial({ map: wallTexture });
  var wall = new THREE.Mesh(geometry, material);

  wall.position.set(position.x, position.y, position.z);
  scene.add(wall);

  return wall;
}

var wall1 = createGalleryWall(
  200,
  70,
  20,
  { x: 0, y: 20, z: -100 },
  "uploads/yammers/artWall.jpg"
);
var wall2a = createGalleryWall(
  85,
  70,
  20,
  { x: 60, y: 20, z: 100 },
  "uploads/yammers/artWall.jpg"
);
var wall2b = createGalleryWall(
  85,
  70,
  20,
  { x: -60, y: 20, z: 100 },
  "uploads/yammers/artWall.jpg"
);
var wall3 = createGalleryWall(
  20,
  70,
  200,
  { x: -100, y: 20, z: 0 },
  "uploads/yammers/artWall.jpg"
);
var wall4 = createGalleryWall(
  20,
  70,
  200,
  { x: 100, y: 20, z: 0 },
  "uploads/yammers/artWall.jpg"
);

// Wall setup for the generated image
var allFrames = [];

var frameConfigs = [
  { position: { x: -40, y: 27.5, z: -89 }, rotationY: 0 },
  { position: { x: 40, y: 27.5, z: -89 }, rotationY: 0 },
  { position: { x: 89, y: 27.5, z: -40 }, rotationY: -Math.PI / 2 },
  { position: { x: -89, y: 27.5, z: 40 }, rotationY: Math.PI / 2 },
  { position: { x: -89, y: 27.5, z: -40 }, rotationY: -Math.PI / 2 },
  { position: { x: 89, y: 27.5, z: 40 }, rotationY: Math.PI / 2 },
  { position: { x: -52.5, y: 27.5, z: 89 }, rotationY: 0 },
  { position: { x: 52.5, y: 27.5, z: 89 }, rotationY: 0 },
];

// create the frames
frameConfigs.forEach((config, index) => {
  // Create a new material for each wall
  var frameMaterial = new THREE.MeshLambertMaterial({
    color: 0xffffff,
    side: THREE.DoubleSide,
    map: textureLoader.load("uploads/yammers/frame.jpg"), // Load the default texture
  });

  var frameGeometry = new THREE.PlaneGeometry(50, 50);
  var frame = new THREE.Mesh(frameGeometry, frameMaterial);

  // Set position and rotation
  frame.position.set(config.position.x, config.position.y, config.position.z);
  frame.rotation.y = config.rotationY;

  // Add border
  addBorderToFrame(frame, 0.5, 0x000000);

  // Add to the scene
  scene.add(frame);

  allFrames.push(frame);
});

// adds a frame to the frames
function addBorderToFrame(frame, thickness, color) {
  const frameWidth = frame.geometry.parameters.width;
  const frameHeight = frame.geometry.parameters.height;
  const borderMaterial = new THREE.MeshLambertMaterial({ color: color });

  // Top border
  const topBorderGeometry = new THREE.BoxGeometry(
    frameWidth,
    thickness,
    thickness
  );
  const topBorder = new THREE.Mesh(topBorderGeometry, borderMaterial);
  topBorder.position.y = frameHeight / 2;
  frame.add(topBorder);

  // Bottom border
  const bottomBorder = new THREE.Mesh(topBorderGeometry, borderMaterial);
  bottomBorder.position.y = -frameHeight / 2;
  frame.add(bottomBorder);

  // Left border
  const sideBorderGeometry = new THREE.BoxGeometry(
    thickness,
    frameHeight + thickness * 2,
    thickness
  );
  const leftBorder = new THREE.Mesh(sideBorderGeometry, borderMaterial);
  leftBorder.position.x = -frameWidth / 2;
  frame.add(leftBorder);

  // Right border
  const rightBorder = new THREE.Mesh(sideBorderGeometry, borderMaterial);
  rightBorder.position.x = frameWidth / 2;
  frame.add(rightBorder);
}

var stand = createGalleryWall(2, 10, 2, { x: 0, y: 5, z: -25 }, "uploads/yammers/artWall.jpg");

// Add a border to the stand

scene.add(stand);

// Button setup for the prompt input
var buttonGeometry = new THREE.BoxGeometry(2, 2, 2);
var buttonMaterial = new THREE.MeshLambertMaterial({ color: 0xff0000 });
var button = new THREE.Mesh(buttonGeometry, buttonMaterial);

button.rotation.y = Math.PI / 2;
button.position.set(0, 11, -25);

scene.add(button);
// Raycaster for interaction with the button
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();

var isPointerLocked = false;

// Mouse click event listener for interaction with the button
function onMouseClick(event) {
  document.exitPointerLock(); // Exit pointer lock when text box is focused
  isPointerLocked = false;

  // Convert mouse position to normalized device coordinates (-1 to +1) for both components
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

  // Update the raycaster
  raycaster.setFromCamera(mouse, camera);

  // Check if the ray intersects with the button
  var intersects = raycaster.intersectObject(button);
  if (intersects.length > 0) {
    togglePromptInput(); // Toggle the visibility of the prompt input
  }
}

function showPromptInput() {
  promptInput.style.display = "block"; // Show the prompt input
  promptInput.focus(); // Focus on the input
}

// Function to toggle the visibility of the prompt input for image generation
function togglePromptInput() {
  if (promptInput.style.display === "block") {
    promptInput.style.display = "none"; // Hide the prompt input
    viewInput.style.display = "none";
  } else {
    promptInput.style.display = "block"; // Show the prompt input
    viewInput.style.display = "block";
    promptInput.focus(); // Focus on the input if it's being shown
  }
}

document.addEventListener("click", onMouseClick, false); // Add the mouse click event listener to the document

// Pointer Lock Controls setup
camera.rotation.set(0, 0, 0);
var pitchObject = new THREE.Object3D();
pitchObject.add(camera);

var yawObject = new THREE.Object3D();
yawObject.position.y = 10;
yawObject.add(pitchObject);

var velocity = new THREE.Vector3();
var canJump = false; // A flag to check if the character can jump
var jumpHeight = 7.5; // Adjust as needed

scene.add(yawObject);

const PI_2 = Math.PI / 2; // Constant for the pitch object

// This requests pointer lock when the user clicks on the page
document.addEventListener(
  "click",
  function () {
    if (apiKeySet == false) {
      return; // Don't allow movement when the API key hasn't been set
    }

    document.body.requestPointerLock();
  },
  false
);
// Mouse movement event listeners
document.addEventListener("mousemove", onMouseMove, false);
var isPointerLocked = false;

// Function to handle mouse movement when pointer lock is enabled
function onMouseMove(event) {
  if (!isPointerLocked) return;

  var movementX =
    event.movementX || event.mozMovementX || event.webkitMovementX || 0;
  var movementY =
    event.movementY || event.mozMovementY || event.webkitMovementY || 0;

  yawObject.rotation.y -= movementX * 0.002;
  pitchObject.rotation.x -= movementY * 0.002;
  pitchObject.rotation.x = Math.max(
    -PI_2,
    Math.min(PI_2, pitchObject.rotation.x)
  );
}

document.addEventListener("pointerlockchange", lockChange, false);
document.addEventListener("mozpointerlockchange", lockChange, false);
document.addEventListener("webkitpointerlockchange", lockChange, false);

// Function to handle pointer lock change
function lockChange() {
  isPointerLocked = document.pointerLockElement === document.body;
}

// Movement variables
var moveForward = false;
var moveBackward = false;
var moveLeft = false;
var moveRight = false;

// Key event listeners
document.addEventListener("keydown", onKeyDown, false);
document.addEventListener("keyup", onKeyUp, false);

// Function to handle key down events
function onKeyDown(event) {
  if (isPointerLocked == false) return;
  if (promptInput.style.display === "block") return; // Don't allow movement when the prompt input is visible
  console.log(event.key);
  switch (event.keyCode) {
    case 87: // W
      moveForward = true;
      break;
    case 65: // A
      moveLeft = true;
      break;
    case 83: // S
      moveBackward = true;
      break;
    case 68: // D
      moveRight = true;
      break;
    case 32: // Spacebar
      console.log("Jumping");
      if (canJump === true) velocity.y += jumpHeight;
      canJump = false;
      break;
  }
}

// Function to handle key up events
function onKeyUp(event) {
  console.log(event.key);
  switch (event.keyCode) {
    case 87: // W
      moveForward = false;
      break;
    case 65: // A
      moveLeft = false;
      break;
    case 83: // S
      moveBackward = false;
      break;
    case 68: // D
      moveRight = false;
      break;
  }
}

// OpenAI API setup
const openaiURL = "https://api.openai.com/v1/images/generations"; // URL for DALL-E 3
const themodel = "dall-e-3"; // The OpenAI model for DALL-E 3

var apikey = "";
var apiKeySet = false; // Flag to check if the API key has been set
var theprompt = "";
//
function sendImageRequest() {
  var thedata = {
    model: themodel,
    prompt: theprompt,
    n: 1,
    size: "1024x1024",
  };

  var thedatastring = JSON.stringify(thedata);
  var proxiedUrl =
    "https://corsproxy.io/?" +
    encodeURIComponent("https://api.openai.com/v1/images/generations");

  fetch(proxiedUrl, {
    method: "POST",
    headers: {
      // Your headers here
      "Content-Type": "application/json",
      Authorization: "Bearer " + apikey,
    },
    body: JSON.stringify({
      model: themodel,
      prompt: theprompt,
      n: 1,
      size: "1024x1024",
    }),
  })
    .then((response) => response.json())
    .then((data) => successfn(data))
    .catch((error) => console.error("Error:", error));
}

// function for the replicate api

async function getImage() {
  const proxiedUrl =
    "https://corsproxy.io/?" +
    encodeURIComponent("https://api.replicate.com/v1/predictions");
  const token = apikey;
  const model =
    "39ed52f2a78e934b3ba6e2a89f5b1c712de7dfea535525255b1aa35c5565e08b";
  const prompt = theprompt;

  try {
    const response = await fetch(proxiedUrl, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: "Token " + token,
      },
      body: JSON.stringify({
        version: model,
        input: { prompt: prompt },
      }),
    });

    const data = await response.json();
    console.log(data);

    if (data.urls && data.urls.get) {
      const pollingUrl =
        "https://corsproxy.io/?" + encodeURIComponent(data.urls.get);
      await pollForOutput(pollingUrl, token);
    }
  } catch (error) {
    console.error("Error:", error);
  }
}

async function pollForOutput(url, token) {
  let hasOutput = false;
  const maxPollAttempts = 10;
  let pollAttempts = 0;

  while (!hasOutput && pollAttempts < maxPollAttempts) {
    try {
      const pollResponse = await fetch(url, {
        headers: {
          "Content-Type": "application/json",
          Authorization: "Token " + token,
        },
      });

      const pollData = await pollResponse.json();
      console.log(pollData);

      if (pollData.output) {
        hasOutput = true;
        successfn(pollData.output[0]);
      } else {
        // Wait for 5 seconds before the next poll
        await new Promise((resolve) => setTimeout(resolve, 5000));
      }
    } catch (error) {
      console.error("Error while polling:", error);
      break;
    }
  }
}

async function getMusic() {
  const proxiedUrl =
    "https://corsproxy.io/?" +
    encodeURIComponent("https://api.replicate.com/v1/predictions");
  const token = apikey;
  const model =
    "8cf61ea6c56afd61d8f5b9ffd14d7c216c0a93844ce2d82ac1c9ecc9c7f24e05";
  const prompt = "Jazzy art gallery";

  try {
    const response = await fetch(proxiedUrl, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: "Token " + token,
      },
      body: JSON.stringify({
        version: model,
        input: { prompt: prompt },
      }),
    });

    const data = await response.json();
    console.log(data);

    if (data.urls && data.urls.get) {
      const pollingUrl =
        "https://corsproxy.io/?" + encodeURIComponent(data.urls.get);
      await pollForMusic(pollingUrl, token);
    }
  } catch (error) {
    console.error("Error:", error);
  }
}

async function pollForMusic(url, token) {
  let hasOutput = false;
  const maxPollAttempts = 10;
  let pollAttempts = 0;

  while (!hasOutput && pollAttempts < maxPollAttempts) {
    try {
      const pollResponse = await fetch(url, {
        headers: {
          "Content-Type": "application/json",
          Authorization: "Token " + token,
        },
      });

      const pollData = await pollResponse.json();
      console.log(pollData);

      if (pollData.output) {
        hasOutput = true;
        console.log(pollData.output);
        var audio = new Audio(pollData.output["audio"]);
        audio.loop = true;
        audio.play();
      } else {
        // Wait for 5 seconds before the next poll
        await new Promise((resolve) => setTimeout(resolve, 5000));
      }
    } catch (error) {
      console.error("Error while polling:", error);
      break;
    }
  }
}

// Prompt input setup
const promptInput = document.getElementById("promptInput");
const sendPromptButton = document.getElementById("sendPrompt");
const viewInput = document.getElementById("inputArea");

promptInput.addEventListener("focus", function () {
  document.exitPointerLock(); // Exit pointer lock when text box is focused
  isPointerLocked = false; // Update your pointer lock flag
});

sendPromptButton.addEventListener("click", function () {
  sendImageRequest(promptInput.value); // Call your function to send the image request
});

promptInput.addEventListener("keypress", function (event) {
  if (event.key === "Enter") {
    // sendImageRequest(promptInput.value);
    getImage();
    // request pointer lock
    document.body.requestPointerLock();
    // stop input going to the input box
    event.preventDefault();
    isPointerLocked = true;
    promptInput.style.display = "none"; // Hide the prompt input
    viewInput.style.display = "none";
  }
});

promptInput.addEventListener("input", function () {
  theprompt = this.value;
});

function successfn(data) {
  console.log(data);
  if (allFrames.length === 0) {
    // Reset or handle the case where all FrameallFrames have been used
  }

  var imageUrl = "https://corsproxy.io/?" + encodeURIComponent(data); // Retrieving the URL of the generated image

  // Randomly pick a wall
  var randomIndex = Math.floor(Math.random() * allFrames.length);
  var selectedWall = allFrames[randomIndex];

  // Apply texture to the selected wall only
  textureLoader.load(imageUrl, function (texture) {
    selectedWall.material.map = texture;
    selectedWall.material.needsUpdate = true;
  });

  // Optionally remove the selected wall from the array
  allFrames.splice(randomIndex, 1);
}

function errorfn() {
  if (apikey == "") {
    console.log("Please enter your API key.");
  }
  console.log("Error");
}

function animate() {
  requestAnimationFrame(animate);

  if (isPointerLocked) {
    velocity.y -= 9.8 * 0.025;

    // Apply the velocity
    yawObject.translateX(velocity.x);
    yawObject.translateY(velocity.y);
    yawObject.translateZ(velocity.z);

    // Collision detection with the floor
    if (yawObject.position.y < 10) {
      velocity.y = 0;
      yawObject.position.y = 10;
      canJump = true;
    }
    var moveSpeed = 25;
    var delta = 0.016; // 60 FPS?

    // Movement
    var direction = new THREE.Vector3();
    direction.z = Number(moveForward) - Number(moveBackward);
    direction.x = Number(moveRight) - Number(moveLeft);
    direction.normalize();

    if (moveForward) yawObject.translateZ(-moveSpeed * delta);
    if (moveBackward) yawObject.translateZ(moveSpeed * delta);
    if (moveLeft) yawObject.translateX(-moveSpeed * delta);
    if (moveRight) yawObject.translateX(moveSpeed * delta);
  }

  renderer.render(scene, camera);
}

animate();