#!/usr/pkg/bin/ruby33

require 'tins/go'
include Tins::GO
require 'term/ansicolor'
include Term::ANSIColor
Term::ANSIColor.true_coloring = ENV['COLORTERM'] =~ /\A(truecolor|24bit)\z/
include Math

# Generates a palette of 256 colors based on sine waves
#
# @return [Array<Array<Integer>>] An array of arrays, each containing
#    three integers representing the RGB values of a color.
def generate_palette
  (0..255).map { |i|
    [
      128 + 128 * sin(PI * i / 32.0),
      128 + 128 * sin(PI * i / 64.0),
      128 + 128 * sin(PI * i / 128.0),
    ].map { _1.clamp(0, 255).round }
  }
end

# Resets the terminal to its original state, and assigns width $w and height
# $h.
def full_reset
  $w, $h = Tins::Terminal.cols, Tins::Terminal.lines
  $h *= 2
  print reset, clear_screen, move_home, hide_cursor
end

# Generates a screen based on a plasma.
#
# @param plasma [Array<Array<Integer>>] An array of arrays, each containing
#    three integers representing the RGB values of a color.
# @return [String] The string representation of the screen, each character
#    representing a pixel on the screen with the corresponding color.
def generate_screen(plasma)
  screen = ''
  0.step($h - 1, 2) do |y|
    0.upto($w - 1) do |x|
      screen << color(plasma[y][x]) + on_color(plasma[y + 1][x]) + ?▀
    end
  end
  screen
end

# Generates a plasma generated based on sine waves
#
# @param now [Float] A value based on the current time in seconds
# @return [Array<Array<Integer>>] An array of arrays, each containing
#    three integers representing the RGB values of a color.
def generate_plasma(now)
  plasma = Array.new($h) { [ nil ] * $w }
  0.upto($h - 1) do |y|
    0.upto($w - 1) do |x|
      x, y = x.to_f, y.to_f
      color = (
        128.0 + (128.0 * sin((x / 7.0) - 3.0 * cos(now / 2.0))) +
        128.0 + (128.0 * sin((y / 13.0) - 2.0 * sin(now))) +
        128.0 + (128.0 * sin(hypot((x - $w / 3.0), (y - $h / 2.0)) / 5.0)) +
        128.0 + (128.0 * sin((hypot(x, y) / 5.0) - sin(now / 3.0)))
      ) / 4.0
      plasma[y][x] = $palette[(color + now).round % $palette.size]
    end
  end
  plasma
end

$opts = go('n:')

$palette = generate_palette

begin
  $full_reset = true
  trap(:SIGWINCH) { $full_reset = true }

  loop do
    if $full_reset
      $full_reset = false
      full_reset
    end

    now    = Time.now.to_f / ($opts[?n]&.to_f || 1)
    plasma = generate_plasma(now)
    print move_home, generate_screen(plasma), reset

    if n = $opts[?n]&.to_f
      sleep n
    else
      print move_to_column(1), erase_in_line, show_cursor
      exit
    end
  end
rescue Interrupt
  print reset, clear_screen, move_home, show_cursor
end
