Source: card-script.js

/**
 * @file JavaScript Code for card.html - Last Modified: 06/11/2023
 * @author Ezgi Bayraktaroglu
 * @author Helen Lin
 * @author Nakul Nandhakumar
 * @author Joshua Tan
 * @author Khanh Le
 * @author Samuel Au
 */

/* TODO: The scope of these variables may be adjusted later */
import { addFortune } from "./saved-readings-script.js";

/**
 * A reference to the fortuneText when prediction occurs.
 * @type {string}
 */
let fortuneText = "";

/**
 * A reference to the number of cards the user wants to select
 * @type {number}
 */
let selectCount;

/**
 * A reference to the output interal for typing the fortune to the screen
 * 
 */
let typeOutputInterval;

/**
 * Set the number of cards to appear to be 6
 * @type {number}
 */
let cardCount = 6;

/**
 * A reference to a button to get the tarot card predictions
 * @type {HTMLElement | null}
 */
const predictButton = document.getElementById('getTarot');

/**
 * A reference to a button to save the fortune to localStorage
 * @type {HTMLElement | null}
 */
const saveButton = document.getElementById('saveFortune');

/**
 * A reference to a button to save the fortune to localStorage
 * @type {HTMLElement | null}
 */
const saveReadingsButton = document.getElementById('savedReadingsPage');

/**
 * Array containing the id strings of all selected cards
 * @type {string[]}
 */
let selectBuffer = [];

/**
 * A reference to back button HTMlElement on card.html
 * @type {HTMLElement | null}
 */
const returnToMenuButton = document.getElementById('returnMenu');

/**
 * A reference to reset button to get new fortune
 * @type {HTMLElement | null}
 */
const resetButton = document.getElementById('reset');

/**
 * A reference to all card images
 * @type {HTMLCollection<img> | null}
 */
const tarotCards = document.getElementsByClassName('card');

window.addEventListener('load', () => {
  init();
  //setTimeout(playSparkle, 500);
});

/**
 * Function containing all intial setup functions for generating cards
 * and event listeners for the buttons on the page
 */
function init() {
  // Add event listener to all the tarot cards so they can be selected once again
  for (let i = 0; i < tarotCards.length; i++) {
    tarotCards[i].index = i;
    tarotCards[i].addEventListener("click", chooseCard);

    // Remove select class so that old card does not get centered
		tarotCards[i].classList.remove("select");

    //Remove Box Shadow Styling if there is any
    tarotCards[i].style.boxShadow = null;
  }

  // Clear Selected Card Buffer
  selectBuffer = [];

  // Reset all the cards to be facing down again
  for (let i = 0; i < tarotCards.length; i++) {
    tarotCards[i].src = `assets/card-page/backside.png`;
  }

  /* Get category from local storage */
  let category = JSON.parse(localStorage.getItem("category"));

  /* Set selectCount value from category */
  switch (category) {
    case "School":
    case "Love":
    case "Life":
    default:
      selectCount = 1;
      break;
  }
  /* Set the backs of the cards based off the category*/
  for (let i = 0; i < tarotCards.length; i++) {
    if(category === 'Love'){
      tarotCards[i].src = `assets/card-page/love_back.png`;
    }
    else if(category === 'School'){
      tarotCards[i].src = `assets/card-page/school_back.png`;
    }
    else if(category === 'Life'){
      tarotCards[i].src = `assets/card-page/life_back.png`;
    }
    
  }

  typePrediction(`Please Select ${selectCount} Card.`);

  /* Play sound when pressing buttons */
   //playClickSound();

  /* Add event listener for predicting fortune button */
  predictButton.addEventListener("click", generatePrediction);
  predictButton.style.opacity = 1.0;
    
  // Remove save button
  saveButton.removeEventListener("click", saveFortune);
  saveButton.style.opacity = 0.5;

  /* Add event listener for return to menu button to go back to menu page */
  returnToMenuButton.addEventListener("click", returnToMenu);

  /* Animate the cards into position */
  wooshCards();

  /* Add event listener to save readings button to go to saved readings page*/
  if (saveReadingsButton != null)
    saveReadingsButton.addEventListener("click", goToSavedReadings);

  /* Hide the the new fortune button and add event listener to it */
  resetButton.removeEventListener("click", init);
   
 
  resetButton.style.opacity = 0.5;
}

/**
 * Function that plays sound when buttons are clicked
 */
// function playClickSound() {
//   let buttons = document.getElementsByTagName("button");
//   for (let button of buttons) {
//     button.addEventListener('click', () => {
//       const sound = document.getElementById("click");
//       sound.play();
//     });
//   }
// }

/**
 * Function that changes to page back to the main menu (only after click sound 
 * effect has finished)
 */
function returnToMenu() {
  //const sound = document.getElementById("click");
  //sound.addEventListener('ended', function() {
    window.location.href = "menu.html";
  //});
}

/**
 * Function that changes the page to the save readings page (only after click 
 * sound effect has finished)
 */
function goToSavedReadings() {
  //const sound = document.getElementById("click");
  //sound.addEventListener('ended', function() {
    window.location.href = "saved.html";
  //});
}

/**
 * A function used for an event listener in order to generate the prediction
 * when the user has selected their cards
 */
async function generatePrediction() {
  /**
   * A reference to the output area for the result of the reading
   * @type {HTMLElement | null}
   */
  const predictOut = document.getElementById('output');

  // Clear fortuneText variable
  fortuneText = "";

  // Reset all the cards to be facing down again
  let category = JSON.parse(localStorage.getItem("category"));
  for (let i = 0; i < tarotCards.length; i++) {
    if(category === 'Love'){
      tarotCards[i].src = `assets/card-page/love_back.png`;
    }
    else if(category === 'School'){
      tarotCards[i].src = `assets/card-page/school_back.png`;
    }
    else if(category === 'Life'){
      tarotCards[i].src = `assets/card-page/life_back.png`;
    }
    
  }


  /* Verify items are selected */
  if (selectBuffer && selectBuffer.length === selectCount) {

    /* Select a random number between 0 and 5, pick random card from number */
    let cardNumbers = generateNonDuplicateRandomNumbers(0, 5, selectBuffer.length);

    // Store chosen cards in array to iterate over later
    let cards = [];

    // For each number in the array of cardNumbers, push a card to the cards array
    cardNumbers.forEach(function(cardNumber) {
      switch (cardNumber) {
        case 0:
          cards.push("optimistic");
          break;
        case 1:
          cards.push("hopeful");
          break;
        case 2:
          cards.push("neutral");
          break;
        case 3:
          cards.push("pessimistic");
          break;
        case 4:
          cards.push("disastrous");
          break;
        case 5:
        default:
          cards.push("unexpected");
          break;
      }
    });

    // Get the current category of the fortune telling site
    let category = JSON.parse(localStorage.getItem("category"));
    if (category == undefined)
      category = 'Life';

    // Get the JSON containing all the fortune responses
    let response = await fetch("./assets/fortunes/fortunes.json");
    let fortuneResponses = await response.json();

    for (let i = 0; i < selectBuffer.length; i++) {
        // Change the images of the cards that were selected
        tarotCards[selectBuffer[i]].src = `assets/card-page/${cards[i]}.png`;

        // Pick random fortune response within card subsection to use
        let cardResponse = Math.floor(Math.random() * 2);

      fortuneText += fortuneResponses[category][cards[i]][cardResponse];

			// Add select class to selected cards so deWoosh animation can occur
			tarotCards[selectBuffer[i]].classList.add("select");
    }
		// Animate away the unselected cards
		dewooshCards();

    // Center the selected card
    centerSelectedCard();

    /* Give the user a prediction and get the interval to stop typing is necessary */
    setTimeout(typePrediction, 250, fortuneText);

    // Remove listener for predict button
    predictButton.removeEventListener("click", generatePrediction);
    predictButton.style.opacity = 0.5;

    // Remove event listeners for each tarot card so they can be selected
    for (let i = 0; i < tarotCards.length; i++) {
      tarotCards[i].removeEventListener("click", chooseCard);
    }

    // Display reset button and add event listener
    resetButton.addEventListener("click", init);
    resetButton.style.opacity = 1.0;

    // Display save fortube button
    saveButton.addEventListener("click", saveFortune);
    saveButton.style.opacity = 1.0;

  } else {
    /* Display a message that the user selected nothing */
    typePrediction(`Please Select ${selectCount} Card.`);
  }
}



/**
 * Takes in the prediction generate and types out the
 * prediction results by updating the html content character
 * by character
 * @param {string} - Predition result to be typed out
 */
function typePrediction(prediction) {
  // Stop previous typing before tpying new string
  clearInterval(typeOutputInterval);

  // Get each character in the string passed in by separating based on empty string
  const predictionChars = prediction.split("");
  let predictionCharsIndex = 0;
  const predictOut = document.getElementById("output");
  predictOut.textContent = "";

  // Interval function used to type out one char at a time
  typeOutputInterval = setInterval(()=> {
    predictOut.textContent +=predictionChars[predictionCharsIndex];
    predictionCharsIndex++;

    //Finished Typing
    if (predictionCharsIndex === predictionChars.length) {
      clearInterval(typeOutputInterval);
    }
  }, 30);
  setTimeout(() => {
    console.log((document.querySelector('#output')).innerText);
  }, 2000);

  return typeOutputInterval;
}


/**
 * Function enables the selection of cards on the fortune generation page. When
 * a card is selected, it is added to a buffer that keeps track of selected cards
 * as space and category allows. When buffer grows too big, remove first element
 * in buffer and add the new element. Selected card has permanent box shadow
 */
function chooseCard() {
  // Gets index of tarot card inside card buffer if it exists
  const index = selectBuffer.indexOf(this.index);

  // Push card to buffer if it isn't inside, if already inside then deselect card and remove it
  if (index == -1) {
    selectBuffer.push(this.index);
    tarotCards[this.index].style.boxShadow = "0 0 10px 5px #ad08c7";

    // If buffer is too big then remove first element
    if (selectBuffer.length > selectCount) {
      tarotCards[selectBuffer[0]].style.boxShadow = null;
      selectBuffer.shift();
    }
  } else {
    tarotCards[this.index].style.boxShadow = null;

    selectBuffer.splice(index, 1);
  }
}



/**
 * Function to save a fortune to localStorage for later display on the save
 * fortunes page. Executes when the save fortune button is pressed
 */
function saveFortune() {
  // Get the current cateogry as a string
  let category = JSON.parse(localStorage.getItem("category"));

  // pass in fortune response, current cateogry, and date
  addFortune(fortuneText, category, new Date());

  // Remove listener for save fortune button
  predictButton.removeEventListener("click", generatePrediction);

  // Remove event listener for save button after being clicked once
  saveButton.removeEventListener("click", saveFortune);
  saveButton.style.opacity = 0.5;
}

/**
 * Generates an array of non-duplicate random numbers within a given range.
 *
 * @param {number} min - The minimum value of the range (inclusive).
 * @param {number} max - The maximum value of the range (inclusive).
 * @param {number} count - The number of random non-duplicate numbers to generate.
 * @returns {number[]} An array of non-duplicate random numbers.
 */
function generateNonDuplicateRandomNumbers(min, max, count) {
  var numbers = [];

  while (numbers.length < count) {
    var randomNumber = Math.floor(Math.random() * (max - min + 1)) + min;

    if (numbers.indexOf(randomNumber) === -1) {
      numbers.push(randomNumber);
    }
  }

  return numbers;
}



/**
 * Function to woosh or animate to the cards from a deck on the left
 * to being layed out for the user to pick
 * Triggered when the page is loaded.
 * Assumption: Number of Cards that could be selected from are 6
 */
function wooshCards() {
	for(let i = 0; i < tarotCards.length; i++) {
		switch (String(tarotCards[i].id)) {
			case "card1":
				tarotCards[i].style.setProperty("--changePoint", "-200%");
				tarotCards[i].style.animation = "woosh 2s";
				break;
			case "card2":
				tarotCards[i].style.setProperty("--changePoint", "-300%");
				tarotCards[i].style.animation = "woosh 2s";
				break;
			case "card3":
				tarotCards[i].style.setProperty("--changePoint", "-400%");
				tarotCards[i].style.animation = "woosh 2s";
				break;
			case "card4":
				tarotCards[i].style.setProperty("--changePoint", "-500%");
				tarotCards[i].style.animation = "woosh 2s";
				break;
			case "card5":
				tarotCards[i].style.setProperty("--changePoint", "-600%");
				tarotCards[i].style.animation = "woosh 2s";
				break;
			case "card6":
				tarotCards[i].style.setProperty("--changePoint", "-700%");
				tarotCards[i].style.animation = "woosh 2s";
				break;
			default:
				break;
		}
	}
}



/**
 * Function to dewoosh or animate to the left into a deck the unselected cards
 * Triggered when Predict button is clicked.
 * Assumption: Number of Cards that could be selected from are 6
 */
function dewooshCards() {
	for(let i = 0; i < tarotCards.length; i++) {
		if(tarotCards[i].classList == undefined) {
			break;
		}
		if(!tarotCards[i].classList.contains("select")){
			switch (String(tarotCards[i].id)) {
        /**Each Case sets css variable --changePoint and triggers deWoosh
         * keyFrame animation in card-styles.css
         */
				case "card1":
					tarotCards[i].style.setProperty("--changePoint", "-200%");
					tarotCards[i].style.animation = "deWoosh 2s";
					tarotCards[i].style.animationFillMode = "forwards";
					break;
				case "card2":
					tarotCards[i].style.setProperty("--changePoint", "-300%");
					tarotCards[i].style.animation = "deWoosh 2s";
					tarotCards[i].style.animationFillMode = "forwards";
					break;
				case "card3":
					tarotCards[i].style.setProperty("--changePoint", "-400%");
					tarotCards[i].style.animation = "deWoosh 2s";
					tarotCards[i].style.animationFillMode = "forwards";
					break;
				case "card4":
					tarotCards[i].style.setProperty("--changePoint", "-500%");
					tarotCards[i].style.animation = "deWoosh 2s";
					tarotCards[i].style.animationFillMode = "forwards";
					break;
				case "card5":
					tarotCards[i].style.setProperty("--changePoint", "-600%");
					tarotCards[i].style.animation = "deWoosh 2s";
					tarotCards[i].style.animationFillMode = "forwards";
					break;
				case "card6":
					tarotCards[i].style.setProperty("--changePoint", "-700%");
					tarotCards[i].style.animation = "deWoosh 2s";
					tarotCards[i].style.animationFillMode = "forwards";
					break;
				default:
					break;
			}
    }
	}
}




/**
 * Function to center the selected card.
 * Triggered when Predict button is clicked.
 * ASSUMPTION: Only one Card is Selected.
 */
function centerSelectedCard() {
  for (let i=0; i < tarotCards.length; i++) {
    if (tarotCards[i].classList == undefined) {
      break;
    }

    if(tarotCards[i].classList.contains("select")) {
      switch (String(tarotCards[i].id)) {
        /**Each Case sets css variable --changePoint and triggers deWoosh
         * keyFrame animation in card-styles.css
         */
				case "card1":
					tarotCards[i].style.setProperty("--changePoint", "275%");
					tarotCards[i].style.animation = "deWoosh 2s";
					tarotCards[i].style.animationFillMode = "forwards";
					break;
				case "card2":
					tarotCards[i].style.setProperty("--changePoint", "165%");
					tarotCards[i].style.animation = "deWoosh 2s";
					tarotCards[i].style.animationFillMode = "forwards";
					break;
				case "card3":
					tarotCards[i].style.setProperty("--changePoint", "55%");
					tarotCards[i].style.animation = "deWoosh 2s";
					tarotCards[i].style.animationFillMode = "forwards";
					break;
				case "card4":
					tarotCards[i].style.setProperty("--changePoint", "-50%");
					tarotCards[i].style.animation = "deWoosh 2s";
					tarotCards[i].style.animationFillMode = "forwards";
					break;
				case "card5":
					tarotCards[i].style.setProperty("--changePoint", "-160%");
					tarotCards[i].style.animation = "deWoosh 2s";
					tarotCards[i].style.animationFillMode = "forwards";
					break;
				case "card6":
					tarotCards[i].style.setProperty("--changePoint", "-270%");
					tarotCards[i].style.animation = "deWoosh 2s";
					tarotCards[i].style.animationFillMode = "forwards";
					break;
				default:
					break;
      }
    }
  }
}

// function playSparkle() {
//   let sparkle = document.getElementById("sparkle");
//   sparkle.volume = 0.5;
//   sparkle.play();
// }