import { PlayerObject } from "../../src/PersonObjects/Player/PlayerObject";
import { Server } from "../../src/Server/Server";
import { calculateServerGrowth } from "../../src/Server/formulas/grow";
import { numCycleForGrowthCorrected } from "../../src/Server/ServerHelpers";

test("Grow is accurate", () => {
  // Tests that certain special values work out to exactly what we'd expect,
  // given the formulas. The growth multiplier maxes out at 1.0035, and the
  // increment is .03 so with a difficulty of 10 it should be .003.
  // These tests are *exact* because the whole point is that the math should
  // get exactly the right value (or as close as is possible with floating-point).
  const server = new Server({ hostname: "foo", hackDifficulty: 5, serverGrowth: 100 });
  const player = new PlayerObject();
  expect(calculateServerGrowth(server, 1, player)).toBe(1.0035);
  expect(calculateServerGrowth(server, 2, player)).toBe(1.00701225);
  server.hackDifficulty = 10;
  expect(calculateServerGrowth(server, 1, player)).toBe(1.003);
  expect(calculateServerGrowth(server, 2, player)).toBe(1.006009);
  expect(calculateServerGrowth(server, 3, player)).toBe(1.009027027);
  expect(calculateServerGrowth(server, 4, player)).toBe(1.012054108081);
});

describe("numCycleForGrowthCorrected reverses calculateServerGrowth", () => {
  // Test that numCycleForGrowthCorrected() functions properly as the inverse
  // of calculateServerGrowth().
  // calculateServerGrowth() works for *any* given number of threads, but is
  // usually passed an integer. numCycleForGrowthCorrected() *always* returns
  // an integer, the ceiling of the floating-point value. When reversing an
  // integer number of threads, it should *always* return the same integer.
  // Similarly, if we pass the next-highest representable number as a target,
  // it should *always* return the next largest integer, since the previous
  // number is no longer sufficient.

  // This is an arbitrary transcedental constant.
  const multiplier = Math.exp(1.4);
  const server = new Server({ hostname: "foo", hackDifficulty: 10 * multiplier, serverGrowth: 100 });
  server.moneyMax = 1e308; // Not available as a constructor param
  const player = new PlayerObject();
  const tests = [];
  while (server.moneyAvailable < 5e49) {
    tests.push(server.moneyAvailable);
    server.moneyAvailable = (server.moneyAvailable + 59) * calculateServerGrowth(server, 59, player);
  }
  test.each(tests)("startMoney: %f", (money: number) => {
    // Do fewer threads to save on test time
    for (let threads = 0; threads < 30; threads++) {
      const newMoney = (money + threads) * calculateServerGrowth(server, threads, player);
      const eps = newMoney ? 2 ** (Math.floor(Math.log2(newMoney)) - 52) : Number.MIN_VALUE;
      expect(numCycleForGrowthCorrected(server, newMoney, money, 1, player)).toBe(threads);
      const value = numCycleForGrowthCorrected(server, newMoney + eps, money, 1, player);
      // Write our own check because Jest is a goblin that can't provide context
      if (value !== threads + 1) {
        throw new Error(
          `value (${value}) was not equal to threads+1 (${threads + 1})\n\n` +
            `newMoney: ${newMoney} eps: ${eps} newMoney+eps: ${newMoney + eps} value: ${value} threads: ${threads}`,
        );
      }
    }
  });
  const tests2 = [];
  for (let t = 0; t <= 900000; t += 2000) {
    tests2.push([t, t * calculateServerGrowth(server, t, player)]);
  }
  test.each(tests2)("threads: %f newMoney: %f", (threads: number, newMoney: number) => {
    const eps = newMoney ? 2 ** (Math.floor(Math.log2(newMoney)) - 52) : Number.MIN_VALUE;
    expect(numCycleForGrowthCorrected(server, newMoney, 0, 1, player)).toBe(threads);
    const value = numCycleForGrowthCorrected(server, newMoney + eps, 0, 1, player);
    // Write our own check because Jest is a goblin that can't provide context
    if (value !== threads + 1) {
      throw new Error(
        `value (${value}) was not equal to threads+1 (${threads + 1})\n\n` +
          `newMoney: ${newMoney} eps: ${eps} newMoney+eps: ${newMoney + eps} value: ${value} threads: ${threads}`,
      );
    }
  });
});