Clone
defmodule PiDay.Game do
  import Ecto.Query
  alias PiDay.Repo
  alias PiDay.Game.{Player, Score}

  # --- Players ---

  def create_player(attrs) do
    %Player{}
    |> Player.changeset(attrs)
    |> Repo.insert()
  end

  def get_player(id), do: Repo.get(Player, id)

  def get_player_by_token(token) do
    Repo.get_by(Player, session_token: token)
  end

  # --- Scores ---

  def record_score(attrs) do
    result =
      %Score{}
      |> Score.changeset(attrs)
      |> Repo.insert()

    case result do
      {:ok, score} ->
        update_total_score(score.player_id)
        Phoenix.PubSub.broadcast(PiDay.PubSub, "leaderboard", {:score_updated, score})
        {:ok, score}

      error ->
        error
    end
  end

  defp update_total_score(player_id) do
    total =
      Score
      |> where(player_id: ^player_id)
      |> select([s], sum(s.score))
      |> Repo.one() || 0

    Player
    |> where(id: ^player_id)
    |> Repo.update_all(set: [total_score: total])
  end

  def leaderboard(limit \\ 20) do
    Player
    |> where([p], p.total_score > 0)
    |> order_by([p], desc: p.total_score)
    |> limit(^limit)
    |> Repo.all()
  end

  def top_scores(game_type, limit \\ 10) do
    Score
    |> where(game_type: ^game_type)
    |> order_by(desc: :score)
    |> limit(^limit)
    |> preload(:player)
    |> Repo.all()
  end

  # First 2000 digits of pi after "3."
  @pi_digits "14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593344612847564823378678316527120190914564856692346034861045432664821339360726024914127372458700660631558817488152092096282925409171536436789259036001133053054882046652138414695194151160943305727036575959195309218611738193261179310511854807446237996274956735188575272489122793818301194912983367336244065664308602139494639522473719070217986094370277053921717629317675238467481846766940513200056812714526356082778577134275778960917363717872146844090122495343014654958537105079227968925892354201995611212902196086403441815981362977477130996051870721134999999837297804995105973173281609631859502445945534690830264252230825334468503526193118817101000313783875288658753320838142061717766914730359825349042875546873115956286388235378759375195778185778053217122680661300192787661119590921642019893809525720106548586327886593615338182796823030195203530185296899577362259941389124972177528347913151557485724245415069595082953311686172785588907509838175463746493931925506040092770167113900984882401285836160356370766010471018194295559619894676783744944825537977472684710404753464620804668425906949129331367702898915210475216205696602405803815019351125338243003558764024749647326391419927260426992279678235478163600934172164121992458631503028618297455570674983850549458858692699569092721079750930295532116534498720275596023648066549911988183479775356636980742654252786255181841757467289097777279380008164706001614524919217321721477235014144197356854816136115735255213347574184946843852332390739414333454776241686251898356948556209921922218427255025425688767179049460165346680498862723279178608578438382796797668145410095388378636095068006422512520511739298489608412848862694560424196528502221066118630674427862203919494504712371378696095636437191728746776465757396241389086583264599581339047802759009946576407895126946839835259570982582262052248940772671947826848260147699090264013639443745530506820349625245174939965143142980919065925093722169646151570985838741059788595977297549893016175392846813826868386894277415599185592524595395943104997252468084598727364469584865383673622262609912460805124388439045124413654976278079771569143599770012961608944169486855584840635"

  def pi_digits, do: @pi_digits

  def check_pi_digit(position, digit) when is_integer(position) and position >= 0 do
    expected = String.at(@pi_digits, position)
    expected == digit
  end

  # --- Math problems for Slice the Pi ---

  @problem_types [
    :circumference, :area, :arc_length, :sector_area,
    :sphere_volume, :sphere_surface, :cylinder_volume,
    :radians, :trig_value, :diameter_from_area
  ]

  def generate_math_problem do
    type = Enum.random(@problem_types)
    generate_problem(type)
  end

  defp generate_problem(:circumference) do
    r = Enum.random(1..20)
    answer = Float.round(2 * :math.pi() * r, 2)
    make_problem("Circumference of a circle with radius #{r}?", answer)
  end

  defp generate_problem(:area) do
    r = Enum.random(1..15)
    answer = Float.round(:math.pi() * r * r, 2)
    make_problem("Area of a circle with radius #{r}?", answer)
  end

  defp generate_problem(:arc_length) do
    r = Enum.random(2..12)
    angle = Enum.random([30, 45, 60, 90, 120, 180])
    answer = Float.round(2 * :math.pi() * r * (angle / 360), 2)
    make_problem("Arc length: radius=#{r}, angle=#{angle}\u00B0?", answer)
  end

  defp generate_problem(:sector_area) do
    r = Enum.random(2..10)
    angle = Enum.random([30, 45, 60, 90, 120, 180])
    answer = Float.round(:math.pi() * r * r * (angle / 360), 2)
    make_problem("Sector area: radius=#{r}, angle=#{angle}\u00B0?", answer)
  end

  defp generate_problem(:sphere_volume) do
    r = Enum.random(1..8)
    answer = Float.round(4 / 3 * :math.pi() * r * r * r, 2)
    make_problem("Volume of a sphere with radius #{r}?", answer)
  end

  defp generate_problem(:sphere_surface) do
    r = Enum.random(1..10)
    answer = Float.round(4 * :math.pi() * r * r, 2)
    make_problem("Surface area of a sphere with radius #{r}?", answer)
  end

  defp generate_problem(:cylinder_volume) do
    r = Enum.random(1..8)
    h = Enum.random(2..10)
    answer = Float.round(:math.pi() * r * r * h, 2)
    make_problem("Volume of cylinder: radius=#{r}, height=#{h}?", answer)
  end

  defp generate_problem(:radians) do
    {deg, rad_num, rad_den} = Enum.random([{30, 1, 6}, {45, 1, 4}, {60, 1, 3}, {90, 1, 2}, {120, 2, 3}, {180, 1, 1}, {270, 3, 2}, {360, 2, 1}])
    answer = Float.round(:math.pi() * rad_num / rad_den, 2)
    make_problem("Convert #{deg}\u00B0 to radians?", answer)
  end

  defp generate_problem(:trig_value) do
    {label, value} =
      Enum.random([
        {"sin(30\u00B0)", 0.5},
        {"cos(60\u00B0)", 0.5},
        {"sin(90\u00B0)", 1.0},
        {"cos(0\u00B0)", 1.0},
        {"sin(45\u00B0)", 0.71},
        {"cos(45\u00B0)", 0.71},
        {"sin(0\u00B0)", 0.0},
        {"cos(90\u00B0)", 0.0},
        {"tan(45\u00B0)", 1.0},
        {"sin(60\u00B0)", 0.87},
        {"cos(30\u00B0)", 0.87}
      ])

    answer = Float.round(value, 2)
    wrong = generate_trig_wrong(answer)
    %{question: "What is #{label}?", answer: answer, choices: Enum.shuffle([answer | wrong])}
  end

  defp generate_problem(:diameter_from_area) do
    d = Enum.random(2..16)
    area = Float.round(:math.pi() * (d / 2) * (d / 2), 2)
    answer = d * 1.0
    wrong = [d + 2.0, d - 1.0, d * 1.5] |> Enum.map(&Float.round(&1, 2))
    %{question: "Circle has area #{area}. What is the diameter?", answer: answer, choices: Enum.shuffle([answer | wrong])}
  end

  defp make_problem(question, answer) do
    %{question: question, answer: answer, choices: Enum.shuffle([answer | generate_wrong_answers(answer)])}
  end

  defp generate_wrong_answers(correct) when correct == 0.0 do
    [0.5, 1.0, -1.0]
  end

  defp generate_wrong_answers(correct) do
    offsets = [0.5, 1.5, 1.0 + :math.pi() / correct]
    Enum.map(offsets, fn mult -> Float.round(correct * mult, 2) end)
    |> Enum.uniq()
    |> Enum.reject(&(&1 == correct))
    |> Enum.take(3)
    |> case do
      list when length(list) < 3 ->
        list ++ Enum.map(1..(3 - length(list)), fn i -> Float.round(correct + i * 1.11, 2) end)

      list ->
        list
    end
  end

  defp generate_trig_wrong(correct) do
    candidates = [0.0, 0.25, 0.5, 0.71, 0.87, 1.0, 1.41, 1.73, -0.5, -1.0]
    candidates
    |> Enum.reject(&(&1 == correct))
    |> Enum.shuffle()
    |> Enum.take(3)
  end

  # --- Trivia Questions ---

  @trivia [
    %{q: "What is the 3rd digit of Pi after the decimal?", a: "1", choices: ["1", "4", "5", "9"]},
    %{q: "Who first proved that Pi is irrational?", a: "Johann Lambert", choices: ["Johann Lambert", "Leonhard Euler", "Isaac Newton", "Archimedes"]},
    %{q: "What is Euler's identity: e^(i\u03C0) + 1 = ?", a: "0", choices: ["0", "1", "\u03C0", "i"]},
    %{q: "Pi Day (3/14) is also whose birthday?", a: "Albert Einstein", choices: ["Albert Einstein", "Isaac Newton", "Stephen Hawking", "Nikola Tesla"]},
    %{q: "Approximately how many digits of Pi have been computed?", a: "Over 100 trillion", choices: ["Over 100 trillion", "About 1 billion", "About 10 billion", "Over 1 quadrillion"]},
    %{q: "What ancient civilization first estimated Pi?", a: "Babylonians", choices: ["Babylonians", "Romans", "Chinese", "Aztecs"]},
    %{q: "The ratio of a circle's circumference to its diameter is:", a: "Pi", choices: ["Pi", "2\u03C0", "Tau", "Phi"]},
    %{q: "What is Tau (\u03C4)?", a: "2\u03C0", choices: ["2\u03C0", "\u03C0/2", "\u03C0\u00B2", "\u221A\u03C0"]},
    %{q: "The Golden Ratio (Phi) is approximately:", a: "1.618", choices: ["1.618", "3.14", "2.718", "1.414"]},
    %{q: "What is e (Euler's number) approximately?", a: "2.718", choices: ["2.718", "3.14", "1.618", "2.236"]},
    %{q: "What shape has the largest area for a given perimeter?", a: "Circle", choices: ["Circle", "Square", "Equilateral triangle", "Regular hexagon"]},
    %{q: "How many radians in a full circle?", a: "2\u03C0", choices: ["2\u03C0", "\u03C0", "360", "4\u03C0"]},
    %{q: "What is i\u00B2 (imaginary unit squared)?", a: "-1", choices: ["-1", "1", "i", "0"]},
    %{q: "Archimedes estimated Pi by inscribing polygons with how many sides?", a: "96", choices: ["96", "12", "36", "360"]},
    %{q: "What is the sum of angles in a triangle (degrees)?", a: "180", choices: ["180", "360", "90", "270"]},
    %{q: "What is \u221A2 approximately?", a: "1.414", choices: ["1.414", "1.618", "1.732", "1.234"]},
    %{q: "The number Pi is:", a: "Irrational and transcendental", choices: ["Irrational and transcendental", "Rational", "Irrational but algebraic", "Imaginary"]},
    %{q: "What is the formula for a circle's area?", a: "\u03C0r\u00B2", choices: ["\u03C0r\u00B2", "2\u03C0r", "\u03C0d", "\u03C0r\u00B3"]},
    %{q: "sin\u00B2(x) + cos\u00B2(x) = ?", a: "1", choices: ["1", "0", "\u03C0", "2"]},
    %{q: "What is 0! (zero factorial)?", a: "1", choices: ["1", "0", "Undefined", "\u221E"]},
    %{q: "The Pythagorean theorem states: a\u00B2 + b\u00B2 = ?", a: "c\u00B2", choices: ["c\u00B2", "ab", "2c", "(a+b)\u00B2"]},
    %{q: "What is the derivative of sin(x)?", a: "cos(x)", choices: ["cos(x)", "-sin(x)", "tan(x)", "-cos(x)"]},
    %{q: "What mathematical constant appears in the normal distribution?", a: "Both Pi and e", choices: ["Both Pi and e", "Only Pi", "Only e", "Neither"]},
    %{q: "How is Pi defined geometrically?", a: "Circumference \u00F7 Diameter", choices: ["Circumference \u00F7 Diameter", "Area \u00F7 Radius", "Diameter \u00F7 Radius", "Perimeter \u00F7 Side"]},
    %{q: "Who used the symbol \u03C0 for the first time for this constant?", a: "William Jones", choices: ["William Jones", "Leonhard Euler", "Isaac Newton", "Carl Gauss"]},
    %{q: "What is the integral of 1/x?", a: "ln|x| + C", choices: ["ln|x| + C", "x\u00B2 + C", "1/x\u00B2 + C", "e^x + C"]},
    %{q: "What is the volume of a sphere formula?", a: "(4/3)\u03C0r\u00B3", choices: ["(4/3)\u03C0r\u00B3", "4\u03C0r\u00B2", "\u03C0r\u00B2h", "2\u03C0r\u00B3"]},
    %{q: "The Fibonacci sequence approaches what ratio?", a: "The Golden Ratio", choices: ["The Golden Ratio", "Pi", "e", "The Silver Ratio"]},
    %{q: "What is the billionth digit of Pi?", a: "9", choices: ["9", "1", "7", "3"]},
    %{q: "Buffon's Needle experiment can estimate:", a: "Pi", choices: ["Pi", "e", "The Golden Ratio", "Gravity"]}
  ]

  def get_trivia_question do
    q = Enum.random(@trivia)
    %{question: q.q, answer: q.a, choices: Enum.shuffle(q.choices)}
  end

  # --- Projectile Pi ---

  def generate_projectile_target do
    # Target at Pi-related distances
    target_x = Enum.random([
      Float.round(:math.pi() * 10, 1),
      Float.round(:math.pi() * 20, 1),
      Float.round(:math.pi() * 15, 1),
      Float.round(:math.pi() * 25, 1),
      Float.round(:math.pi() * 30, 1)
    ])

    %{target_x: target_x, gravity: 9.81}
  end
end