console.info("[SOCIETY] pigRacing.js loaded");

const debugSpeeds = false;

const pigColors = ["Red", "Blue", "Yellow", "Green"];

const stashyHashy = (input) => {
  let hash = 5;
  for (let i = 0; i < input.length; i++) {
    hash = (hash * 9) ^ input.codePointAt(i);
  }
  return Math.abs(Math.round(hash / 512));
};

const adjustment = (enabled) => (enabled ? 0.05 : 0);

const getSpeeds = (seed, level) => {
  let speeds = [];
  let hashNumber;
  let boostFirstSprint = false;
  let sleepyFirstSprint = false;
  let boostSecondSprint = false;
  let sleepySecondSprint = false;
  pigColors.forEach((pig) => {
    hashNumber = Math.abs(
      stashyHashy(global.getSeasonFromLevel(level) + pig) + seed
    );
    boostFirstSprint = hashNumber % 3 === 0;
    sleepyFirstSprint = hashNumber % 4 === 0;
    boostSecondSprint = hashNumber % 5 === 0;
    sleepySecondSprint = hashNumber % 6 === 0;
    if (debugSpeeds) {
      console.log(`${pig}: Boost: ${boostFirstSprint} - ${boostSecondSprint}`);
      console.log(
        `${pig}: Sleepy: ${sleepyFirstSprint} - ${sleepySecondSprint}`
      );
    }
    speeds.push([
      Math.random() *
        (1.2 +
          adjustment(boostFirstSprint) -
          (0.7 - adjustment(sleepyFirstSprint))) +
        1,
      Math.random() *
        (1.2 +
          adjustment(boostSecondSprint) -
          (0.7 - adjustment(sleepySecondSprint))) +
        1,
    ]);
  });
  return speeds;
};

const clearPigPaint = (player) => {
  const pigElements = ["", "Change", "Trophy"];
  let removedElements = {};
  let id = "";
  // Spawn and clear instance of paint element to prevent warnings that they don't exist
  pigColors.forEach((pig) => {
    pigElements.forEach((element) => {
      id = `pig${pig}${element}`;
      removedElements[id] = { type: "text" };
    });
  });
  player.paint(removedElements);
  pigColors.forEach((pig) => {
    pigElements.forEach((element) => {
      id = `pig${pig}${element}`;
      removedElements[id] = { remove: true };
    });
  });
  player.paint(removedElements);
};

const initialPaint = (player, bet, pig) => {
  player.paint({
    screen: {
      type: "rectangle",
      y: -50,
      w: 268,
      h: 256,
      alignX: "center",
      alignY: "bottom",
      texture: "society:textures/gui/pig_racing/horse_racing_screen.png",
    },
    pigRed: {
      type: "rectangle",
      x: -110,
      y: -170,
      z: 20,
      w: 32,
      h: 32,
      alignX: "center",
      alignY: "bottom",
      texture: "society:textures/gui/pig_racing/pig_red.png",
    },
    pigBlue: {
      type: "rectangle",
      x: -110,
      y: -132,
      z: 20,
      w: 32,
      h: 32,
      alignX: "center",
      alignY: "bottom",
      texture: "society:textures/gui/pig_racing/pig_blue.png",
    },
    pigYellow: {
      type: "rectangle",
      x: -110,
      y: -94,
      z: 20,
      w: 32,
      h: 32,
      alignX: "center",
      alignY: "bottom",
      texture: "society:textures/gui/pig_racing/pig_yellow.png",
    },
    pigGreen: {
      type: "rectangle",
      x: -110,
      y: -54,
      z: 20,
      w: 32,
      h: 32,
      alignX: "center",
      alignY: "bottom",
      texture: "society:textures/gui/pig_racing/pig_green.png",
    },
    betCoin: {
      type: "item",
      item: bet.id,
      x: -100,
      y: -197,
      h: 32,
      w: 32,
      alignX: "center",
      alignY: "bottom",
    },
    betCount: {
      type: "text",
      text: `x${bet.count}`,
      scale: 3,
      color: "#000000",
      x: -70,
      y: -207,
      alignX: "center",
      alignY: "bottom",
    },
    betPig: {
      type: "rectangle",
      y: -212,
      x: 112,
      z: 20,
      w: 32,
      h: 32,
      alignX: "center",
      alignY: "bottom",
      texture: `society:textures/gui/pig_racing/pig_${pig.toLowerCase()}.png`,
    },
  });
};

const getCountDownPaint = (count) => {
  return {
    type: "rectangle",
    y: -70,
    z: 10,
    w: 128,
    h: 128,
    alignX: "center",
    alignY: "bottom",
    texture: `society:textures/gui/pig_racing/${count}.png`,
  };
};

const countDown = (player, server) => {
  const delayFactor = 7;
  player.paint({
    number: getCountDownPaint("3"),
  });
  server.scheduleInTicks(delayFactor, () => {
    player.paint({
      number: getCountDownPaint("2"),
    });
  });
  server.scheduleInTicks(delayFactor * 2, () => {
    player.paint({
      number: getCountDownPaint("1"),
    });
  });
  server.scheduleInTicks(delayFactor * 3, () => {
    player.paint({
      number: {
        type: "rectangle",
        y: -70,
        z: 10,
        w: 128,
        h: 128,
        alignX: "center",
        alignY: "bottom",
        texture: `society:textures/gui/pig_racing/go.png`,
      },
    });
  });
  server.scheduleInTicks(delayFactor * 4, () => {
    player.paint({
      number: { remove: true },
    });
  });
};

const paintPig = (player, server, xOffset, speeds, halfwayPoint, ranking) => {
  const yOffsets = [-170, -132, -94, -54];
  let pigElement = {};
  const finishLine = 214;
  let id = "";
  let halfWay = 0;
  let secondWindCalc = 0;
  let secondWindChange = false;
  let calculatedOffset = 0;
  let raceCompleted = false;

  pigColors.forEach((color, index) => {
    id = `pig${color}`;
    halfWay = speeds[index][0] * halfwayPoint;
    secondWindCalc = halfWay + speeds[index][1] * (xOffset - halfwayPoint) + 1;
    raceCompleted = secondWindCalc >= finishLine;
    calculatedOffset =
      speeds[index][0] * xOffset >= halfWay
        ? raceCompleted
          ? finishLine
          : secondWindCalc
        : speeds[index][0] * xOffset;
    if (
      Math.floor(speeds[index][0] * xOffset) === Math.floor(halfWay) &&
      Math.abs(speeds[index][1] - speeds[index][0]) >= 0.1
    ) {
      secondWindChange = speeds[index][1] > speeds[index][0];
      server.runCommandSilent(
        `playsound ${
          secondWindChange ? "trials:breeze_idle" : "trials:spawner_summon"
        } player @a ${player.x} ${player.y} ${player.z}`
      );
      pigElement[`${id}Change`] = {
        type: "rectangle",
        color: "#FFFFFF",
        z: 12,
        x: -110 + halfWay,
        y: yOffsets[index] - 8,
        alignX: "center",
        alignY: "bottom",
        texture: `society:textures/gui/pig_racing/${
          secondWindChange ? "boost" : "sleepy"
        }.png`,
      };
    }
    pigElement[id] = { remove: true };
    player.paint(pigElement);
    pigElement[id] = {
      type: "rectangle",
      x: -110 + calculatedOffset,
      y: yOffsets[index],
      z: 11,
      w: 32,
      h: 32,
      alignX: "center",
      alignY: "bottom",
      texture: `society:textures/gui/pig_racing/pig_${color.toLowerCase()}.png`,
    };
    if (raceCompleted) {
      !ranking.includes(color) && ranking.push(color);
      pigElement[`${id}Trophy`] = {
        type: "rectangle",
        y: yOffsets[index],
        x: finishLine - 130 - 16,
        z: 10,
        w: 32,
        h: 32,
        alignX: "center",
        alignY: "bottom",
        texture: `society:textures/gui/pig_racing/trophy_${
          ranking.indexOf(color) + 1
        }.png`,
      };
    }
    player.paint(pigElement);
  });
};

const handleSPResults = (player, server, betPig, betItem, ranking) => {
  let newItem = betItem;
  newItem.count = betItem.count * 2;
  const wonRace = ranking[0] === betPig;
  if (wonRace) {
    player.give(Item.of(newItem));
    server.runCommandSilent(
      `playsound advancementplaques:ui.toast.goal_complete player @a ${player.x} ${player.y} ${player.z}`
    );
  } else {
    server.runCommandSilent(
      `playsound stardew_fishing:fish_escape player @a ${player.x} ${player.y} ${player.z}`
    );
  }
  player.paint({
    result: {
      type: "rectangle",
      y: -70,
      z: 20,
      w: 128,
      h: 128,
      alignX: "center",
      alignY: "bottom",
      texture: `society:textures/gui/pig_racing/${
        wonRace ? "win" : "lost"
      }.png`,
    },
  });
};

const handleMPResults = (player, server, betPig, poolValue, ranking) => {
  const wonRace = ranking[0] === betPig;
  const betPigString = `betting §${global.getPigColor(betPig)}${betPig}§r!`;
  let prizePoolCoins = 0;
  ranking.forEach((rank, index) => {
    if (rank === betPig) {
      if (index === 0) {
        prizePoolCoins = global.calculateCoinsFromValue(poolValue * 2, []);
        prizePoolCoins.forEach((coin) => {
          player.give(Item.of(`${coin.count}x ${coin.id}`));
        });
        server.tell(
          Text.gray(
            `:first_place_medal:§6${
              player.username
            }§r won §6${global.formatPrice(
              poolValue * 2
            )}§r :coin: ${betPigString}`
          )
        );
      }
      if (index === 1) {
        prizePoolCoins = global.calculateCoinsFromValue(poolValue / 2, []);
        prizePoolCoins.forEach((coin) => {
          player.give(Item.of(`${Math.floor(coin.count)}x ${coin.id}`));
        });
        server.tell(
          Text.gray(
            `:second_place_medal:§6${
              player.username
            }§r won §6${global.formatPrice(
              poolValue / 2
            )}§r :coin: ${betPigString}`
          )
        );
      }
      if (index === 2) {
        prizePoolCoins = global.calculateCoinsFromValue(poolValue / 4, []);
        prizePoolCoins.forEach((coin) => {
          player.give(Item.of(`${Math.floor(coin.count)}x ${coin.id}`));
        });
        server.tell(
          Text.gray(
            `:third_place_medal:§6${
              player.username
            }§r won §6${global.formatPrice(
              poolValue / 4
            )}§r :coin: ${betPigString}`
          )
        );
      }
    }
  });
  if (wonRace) {
    server.runCommandSilent(
      `playsound advancementplaques:ui.toast.goal_complete player @a ${player.x} ${player.y} ${player.z}`
    );
  } else {
    server.runCommandSilent(
      `playsound stardew_fishing:fish_escape player @a ${player.x} ${player.y} ${player.z}`
    );
  }
  player.paint({
    result: {
      type: "rectangle",
      y: -70,
      z: 20,
      w: 128,
      h: 128,
      alignX: "center",
      alignY: "bottom",
      texture: `society:textures/gui/pig_racing/${
        wonRace ? "win" : "lost"
      }.png`,
    },
  });
};

const runPigRace = (player, server, bet, betPig, ranking, pigSpeeds) => {
  player.addItemCooldown("society:pig_race_ticket", 350);
  server.scheduleInTicks(0, () => {
    server.runCommandSilent(`stopsound ${player.username}`);
    server.runCommandSilent(
      `playsound society:blazing_drift_fever_ex music ${player.username} ${player.x} ${player.y} ${player.z}`
    );
    initialPaint(player, bet, betPig);

    countDown(player, server);
    server.scheduleInTicks(25, () => {
      const halfwayPoint = Math.floor(rnd(40, 120));
      let i = 0;
      let k = 0;
      do {
        player.server.scheduleInTicks(i, () => {
          paintPig(player, server, k, pigSpeeds, halfwayPoint, ranking);
          k++;
        });
        i++;
      } while (i < 220);
    });

    server.scheduleInTicks(300 + 20, () => {
      server.runCommandSilent(`stopsound ${player.username}`);
      clearPigPaint(player);
      player.paint({
        screen: { remove: true },
        betCoin: { remove: true },
        betCount: { remove: true },
        betPig: { remove: true },
        result: { remove: true },
      });
    });
  });
};

const validTicket = (e, bet) => {
  const { player, server, item } = e;
  const coins = [
    "numismatics:crown",
    "numismatics:sun",
    "numismatics:ancient_coin",
    "numismatics:prismatic_coin",
  ];

  if (!coins.includes(bet.id)) {
    server.runCommandSilent(
      `immersivemessages sendcustom ${player.username} {anchor:3,background:1,wrap:1,align:0,color:"#FFFFFFF",y:-60} 4 Place gold coins or higher in your offhand to bet money on your pig!`
    );
    return false;
  }
  if (!item.nbt) {
    server.runCommandSilent(
      `immersivemessages sendcustom ${player.username} {anchor:3,background:1,wrap:1,align:0,color:"#FFFFFFF",y:-60} 4 Left click to select pig to bet on`
    );
    return false;
  }
  return true;
};

ItemEvents.rightClicked("society:pig_race_ticket", (e) => {
  const { player, server, item, level } = e;
  const pigSpeeds = getSpeeds(server.worldData.worldGenOptions().seed(), level);
  const bet = player.offHandItem;
  let betPig;
  let ranking = [];

  if (!validTicket(e, bet)) return;
  item.count--;
  player.offHandItem = "minecraft:air";
  betPig = item.nbt.bet;

  runPigRace(player, server, bet, betPig, ranking, pigSpeeds);

  server.scheduleInTicks(210 + 40, () => {
    handleSPResults(player, server, betPig, bet, ranking);
  });
});

ItemEvents.rightClicked("society:multiplayer_pig_race_ticket", (e) => {
  const { player, server, item, level } = e;
  const pigSpeeds = getSpeeds(server.worldData.worldGenOptions().seed(), level);
  const bettingPeriod = 20 * 60 * 2;
  const bet = player.offHandItem;
  let raceData = server.persistentData;
  let betValue;
  let betPig;
  let ranking = [];
  let players = [];
  let prizePool = 0;
  if (
    raceData.pigraceInProgress &&
    (server.getTickCount() < raceData.ageRaceStarted ||
      server.getTickCount() - raceData.ageRaceStarted > 250)
  ) {
    raceData.pigraceInProgress = false;
  }
  if (raceData.pigraceInProgress) {
    server.runCommandSilent(
      `immersivemessages sendcustom ${player.username} {anchor:3,background:1,wrap:1,align:0,color:"#FFFFFFF",y:-60} 4 There's already a pig race happening! Type /pigrace <pig> to join!`
    );
    return;
  }
  if (!validTicket(e, bet)) return;
  item.count--;
  player.offHandItem = "minecraft:air";
  betValue = betValue = global.calculateCoinValue(bet);
  betPig = item.nbt.bet;
  server.tell(
    Text.gray(
      `§6${player.username}§r started a pig race with §6${global.formatPrice(
        betValue
      )}§r :coin: on §${global.getPigColor(betPig)}${betPig}§r!`
    )
  );
  server.tell(
    Text.gray(
      `Type §6/pigrace <pig>§r with §6${global.formatPrice(
        betValue
      )}§r :coin: worth of coins`
    )
  );
  server.tell(Text.gray(`or more in your offhand to join!`));
  raceData.pigraceInProgress = true;
  raceData.ageRaceStarted = server.getTickCount();
  raceData.bets = [];
  raceData.bets.push({
    player: player.username,
    bet: betValue,
    betPig: betPig,
  });

  server.scheduleInTicks(0, () => {
    server.scheduleInTicks(bettingPeriod / 2, () => {
      server.tell(
        Text.gray(
          `Pig race starting in ${bettingPeriod / (2 * 20 * 60)} minute!`
        )
      );
    });
    server.scheduleInTicks(bettingPeriod - 10 * 20, () => {
      server.tell(Text.gray(`Pig race starting in 10 seconds!`));
    });
    server.scheduleInTicks(bettingPeriod, () => {
      raceData = server.persistentData;
      if (raceData.bets.length > 1) {
        raceData.pigraceInProgress = false;
        for (let index = 0; index < raceData.bets.length; index++) {
          prizePool += raceData.bets[index].bet;
        }
        level.players.forEach((p) => {
          for (let index = 0; index < raceData.bets.length; index++) {
            if (String(p.username) === String(raceData.bets[index].player)) {
              players.push(p);
              runPigRace(
                p,
                server,
                global.calculateCoinsFromValue(prizePool, [])[0],
                String(raceData.bets[index].betPig),
                ranking,
                pigSpeeds
              );
            }
          }
        });
        server.tell(
          Text.gray(
            `Pig race starting with a prize pool of ${global.formatPrice(
              prizePool
            )}`
          )
        );
      } else {
        player.give(bet);
        player.give(Item.of("society:multiplayer_pig_race_ticket"));
        server.tell(
          Text.gray(
            "Pig race cancelled! Multiple bets are required to start..."
          )
        );
      }
    });

    server.scheduleInTicks(210 + 40 + bettingPeriod, () => {
      level.players.forEach((p) => {
        for (let index = 0; index < raceData.bets.length; index++) {
          if (String(p.username) === String(raceData.bets[index].player)) {
            handleMPResults(
              p,
              server,
              String(raceData.bets[index].betPig),
              prizePool,
              ranking
            );
          }
        }
      });
    });
  });
});

ItemEvents.firstLeftClicked(
  ["society:pig_race_ticket", "society:multiplayer_pig_race_ticket"],
  (e) => {
    const { item, server, player } = e;
    server.runCommandSilent(
      `playsound refurbished_furniture:ui.paddle_ball.retro_click block @a ${player.x} ${player.y} ${player.z}`
    );
    let newNbt = item.getNbt() || { bet: 0 };
    const betIndex = pigColors.indexOf(newNbt.bet);
    const betPig = pigColors[betIndex === 3 ? 0 : betIndex + 1];
    newNbt.bet = betPig;
    item.nbt = newNbt;
    global.renderUiText(
      player,
      server,
      {
        pigBet: {
          type: "text",
          x: 0,
          y: -90,
          text: `Betting on §${global.getPigColor(betPig)}${betPig}§r pig!`,
          color: "#AAAAAA",
          alignX: "center",
          alignY: "bottom",
        },
        pigBetShadow: {
          type: "text",
          x: 1,
          z: -1,
          y: -89,
          text: `Betting on ${betPig} pig!`,
          color: "#000000",
          alignX: "center",
          alignY: "bottom",
        },
      },
      ["pigBet"]
    );
    player.addItemCooldown(item, 10);
  }
);
