defmodule AlchemyVM.Gas do
  @cycle_cost 0.3125

  def cost(opcode), do: cpu_cycles(opcode) * @cycle_cost
  def cost(opcode, modifier), do: cpu_cycles(opcode, modifier) * @cycle_cost

  defp cpu_cycles(:i32_add), do: 1
  defp cpu_cycles(:i32_sub), do: 1
  defp cpu_cycles(:i32_mul), do: 5
  defp cpu_cycles(:i32_div_s), do: 20
  defp cpu_cycles(:i32_div_u), do: 20
  defp cpu_cycles(:i32_le_s), do: 1
  defp cpu_cycles(:i32_lt_u), do: 1
  defp cpu_cycles(:i32_gt_u), do: 1
  defp cpu_cycles(:i32_le_u), do: 1
  defp cpu_cycles(:i32_ge_u), do: 1
  defp cpu_cycles(:i32_lt_s), do: 1
  defp cpu_cycles(:i32_ge_s), do: 1
  defp cpu_cycles(:i32_gt_s), do: 1
  defp cpu_cycles(:i32_eq), do: 1
  defp cpu_cycles(:i32_ne), do: 1
  defp cpu_cycles(:i32_eqz), do: 1
  defp cpu_cycles(:i32_rotl), do: 1
  defp cpu_cycles(:i32_rotr), do: 1
  defp cpu_cycles(:i32_and), do: 1
  defp cpu_cycles(:i32_or), do: 1
  defp cpu_cycles(:i32_xor), do: 1
  defp cpu_cycles(:i32_shl), do: 1
  defp cpu_cycles(:i32_shr_u), do: 1
  defp cpu_cycles(:i32_shr_s), do: 1
  defp cpu_cycles(:i32_rem_s), do: 20
  defp cpu_cycles(:i32_rem_u), do: 20
  defp cpu_cycles(:i32_const), do: 4
  defp cpu_cycles(:i32_wrap_i64), do: 0
  defp cpu_cycles(:i32_trunc_u_f32), do: 6
  defp cpu_cycles(:i32_trunc_s_f32), do: 6
  defp cpu_cycles(:i32_trunc_u_f64), do: 6
  defp cpu_cycles(:i32_trunc_s_f64), do: 6
  defp cpu_cycles(:i32_reinterpret_f32), do: 0
  defp cpu_cycles(:i32_load), do: not_implemented(:i32_load)
  defp cpu_cycles(:i32_load8_s), do: not_implemented(:i32_load8_s)
  defp cpu_cycles(:i32_load8_u), do: not_implemented(:i32_load8_u)
  defp cpu_cycles(:i32_load16_s), do: not_implemented(:i32_load16_s)
  defp cpu_cycles(:i32_load16_u), do: not_implemented(:i32_load16_u)
  defp cpu_cycles(:i32_store), do: not_implemented(:i32_store)
  defp cpu_cycles(:i32_store8), do: not_implemented(:i32_store8)
  defp cpu_cycles(:i32_store16), do: not_implemented(:i32_store16)
  defp cpu_cycles(:i64_add), do: 1
  defp cpu_cycles(:i64_sub), do: 1
  defp cpu_cycles(:i64_mul), do: 3
  defp cpu_cycles(:i64_div_s), do: 85
  defp cpu_cycles(:i64_div_u), do: 80
  defp cpu_cycles(:i64_le_s), do: 1
  defp cpu_cycles(:i64_ge_s), do: 1
  defp cpu_cycles(:i64_lt_u), do: 1
  defp cpu_cycles(:i64_gt_u), do: 1
  defp cpu_cycles(:i64_le_u), do: 1
  defp cpu_cycles(:i64_ge_u), do: 1
  defp cpu_cycles(:i64_lt_s), do: 1
  defp cpu_cycles(:i64_gt_s), do: 1
  defp cpu_cycles(:i64_eq), do: 1
  defp cpu_cycles(:i64_ne), do: 1
  defp cpu_cycles(:i64_eqz), do: 1
  defp cpu_cycles(:i64_rotl), do: 1
  defp cpu_cycles(:i64_rotr), do: 1
  defp cpu_cycles(:i64_and), do: 1
  defp cpu_cycles(:i64_or), do: 1
  defp cpu_cycles(:i64_xor), do: 1
  defp cpu_cycles(:i64_shl), do: 1
  defp cpu_cycles(:i64_shr_u), do: 1
  defp cpu_cycles(:i64_shr_s), do: 1
  defp cpu_cycles(:i64_rem_s), do: 85
  defp cpu_cycles(:i64_rem_u), do: 80
  defp cpu_cycles(:i64_const), do: 4
  defp cpu_cycles(:i64_trunc_u_f32), do: 6
  defp cpu_cycles(:i64_trunc_s_f32), do: 6
  defp cpu_cycles(:i64_trunc_u_f64), do: 6
  defp cpu_cycles(:i64_trunc_s_f64), do: 6
  defp cpu_cycles(:i64_extend_u_i32), do: 8
  defp cpu_cycles(:i64_extend_s_i32), do: 8
  defp cpu_cycles(:i64_reinterpret_f32), do: 0
  defp cpu_cycles(:i64_reinterpret_f64), do: 0
  defp cpu_cycles(:i64_load), do: not_implemented(:i64_load)
  defp cpu_cycles(:i64_load8_s), do: not_implemented(:i64_load8_s)
  defp cpu_cycles(:i64_load8_u), do: not_implemented(:i64_load8_u)
  defp cpu_cycles(:i64_load16_s), do: not_implemented(:i64_load16_s)
  defp cpu_cycles(:i64_load16_u), do: not_implemented(:i64_load16_u)
  defp cpu_cycles(:i64_load32_s), do: not_implemented(:i64_load32_s)
  defp cpu_cycles(:i64_load32_u), do: not_implemented(:i64_load32_u)
  defp cpu_cycles(:i64_store), do: not_implemented(:i64_store)
  defp cpu_cycles(:i64_store8), do: not_implemented(:i64_store8)
  defp cpu_cycles(:i64_store16), do: not_implemented(:i64_store16)
  defp cpu_cycles(:i64_store32), do: not_implemented(:i64_store32)
  defp cpu_cycles(:f32_add), do: 4
  defp cpu_cycles(:f32_sub), do: 4
  defp cpu_cycles(:f32_mul), do: 4
  defp cpu_cycles(:f32_div), do: 11
  defp cpu_cycles(:f32_le), do: 4
  defp cpu_cycles(:f32_ge), do: 4
  defp cpu_cycles(:f32_lt), do: 4
  defp cpu_cycles(:f32_gt), do: 4
  defp cpu_cycles(:f32_eq), do: 4
  defp cpu_cycles(:f32_ne), do: 4
  defp cpu_cycles(:f32_const), do: 1
  defp cpu_cycles(:f32_min), do: 4
  defp cpu_cycles(:f32_max), do: 4
  defp cpu_cycles(:f32_copysign), do: not_implemented(:f32_copysign)
  defp cpu_cycles(:f32_nearest), do: not_implemented(:f32_nearest)
  defp cpu_cycles(:f32_trunc), do: not_implemented(:f32_trunc)
  defp cpu_cycles(:f32_floor), do: not_implemented(:f32_floor)
  defp cpu_cycles(:f32_neg), do: 5
  defp cpu_cycles(:f32_abs), do: not_implemented(:f32_abs)
  defp cpu_cycles(:f32_sqrt), do: 13
  defp cpu_cycles(:f32_ceil), do: not_implemented(:f32_ceil)
  defp cpu_cycles(:f32_convert_s_i32), do: 5
  defp cpu_cycles(:f32_convert_u_i32), do: 6
  defp cpu_cycles(:f32_convert_s_i64), do: 5
  defp cpu_cycles(:f32_convert_u_i64), do: 6
  defp cpu_cycles(:f32_demote_f64), do: 10
  defp cpu_cycles(:f32_reinterpret_i32), do: not_implemented(:f32_reinterpret_i32)
  defp cpu_cycles(:f32_load), do: not_implemented(:f32_load)
  defp cpu_cycles(:f32_store), do: not_implemented(:f32_store)
  defp cpu_cycles(:f64_add), do: 4
  defp cpu_cycles(:f64_sub), do: 4
  defp cpu_cycles(:f64_mul), do: 3
  defp cpu_cycles(:f64_div), do: 14
  defp cpu_cycles(:f64_le), do: 4
  defp cpu_cycles(:f64_ge), do: 4
  defp cpu_cycles(:f64_lt), do: 4
  defp cpu_cycles(:f64_gt), do: 4
  defp cpu_cycles(:f64_eq), do: 4
  defp cpu_cycles(:f64_ne), do: 4
  defp cpu_cycles(:f64_const), do: 1
  defp cpu_cycles(:f64_min), do: 4
  defp cpu_cycles(:f64_max), do: 4
  defp cpu_cycles(:f64_copysign), do: not_implemented(:f64_copysign)
  defp cpu_cycles(:f64_nearest), do: not_implemented(:f64_nearest)
  defp cpu_cycles(:f64_trunc), do: not_implemented(:f64_trunc)
  defp cpu_cycles(:f64_floor), do: not_implemented(:f64_floor)
  defp cpu_cycles(:f64_neg), do: 4
  defp cpu_cycles(:f64_abs), do: not_implemented(:f64_abs)
  defp cpu_cycles(:f64_sqrt), do: 18
  defp cpu_cycles(:f64_ceil), do: not_implemented(:f64_ceil)
  defp cpu_cycles(:f64_convert_s_i32), do: 4
  defp cpu_cycles(:f64_convert_u_i32), do: 5
  defp cpu_cycles(:f64_convert_s_i64), do: 4
  defp cpu_cycles(:f64_convert_u_i64), do: 5
  defp cpu_cycles(:f64_promote_f32), do: 4
  defp cpu_cycles(:f64_load), do: not_implemented(:f64_load)
  defp cpu_cycles(:f64_store), do: not_implemented(:f64_store)
  defp cpu_cycles(:if), do: 1
  defp cpu_cycles(:select), do: 5
  defp cpu_cycles(:br), do: 8
  defp cpu_cycles(:br_if), do: 9
  defp cpu_cycles(:nop), do: 0
  defp cpu_cycles(:unreachable), do: 0
  defp cpu_cycles(:return), do: 0
  defp cpu_cycles(:else), do: 0
  defp cpu_cycles(:drop), do: 4
  defp cpu_cycles(:loop), do: 8
  defp cpu_cycles(:block), do: 8
  defp cpu_cycles(:current_memory), do: not_implemented(:current_memory)
  defp cpu_cycles(:get_local), do: not_implemented(:get_local)
  defp cpu_cycles(:get_global), do: not_implemented(:get_global)
  defp cpu_cycles(:call), do: not_implemented(:call)
  defp cpu_cycles(:set_local), do: not_implemented(:set_local)
  defp cpu_cycles(:set_global), do: not_implemented(:set_global)
  defp cpu_cycles(:tee_local), do: not_implemented(:tee_local)
  defp cpu_cycles(:grow_memory), do: not_implemented(:grow_memory)

  defp cpu_cycles(:i32_popcnt, num_ones), do: 32 + num_ones
  defp cpu_cycles(:i32_clz, num_zeros), do: num_zeros * 2
  defp cpu_cycles(:i32_ctz, num_zeros), do: num_zeros * 2
  defp cpu_cycles(:i64_popcnt, num_ones), do: 64 + num_ones
  defp cpu_cycles(:i64_clz, num_zeros), do: num_zeros * 2
  defp cpu_cycles(:i64_ctz, num_zeros), do: num_zeros * 2
  defp cpu_cycles(:end, true), do: 0
  defp cpu_cycles(:end, _), do: 8

  defp not_implemented(_opcode) do
    # IO.warn("Gas cost not implemented for #{opcode}. Defaulting to cost of 1 cycle.")
    1
  end
end
