#!/usr/bin/env ruby

trap("INT") do
  quit
end

def quit
  hfile = File.join(ENV['HOME'], ".goforthanddie_history")
  File.open(hfile, ?w) do |f|
    f.puts(Readline::HISTORY.to_a)
    f.flush
  end
  exit
end

def getlist(stack, markermode, markerautoclear)
  if markermode
    h = []
    wm = false
    loop do
      break if stack.empty?
      t = stack.pop
      wm = t == :mark
      break if wm
      h << t
    end
    stack.push(:mark) if wm && !markerautoclear
    h.reverse
  else
    n = stack.pop
    stack.pop(n)
  end
end

def handle(str, stack, markermode, markerautoclear, destructiveprint)
  str.split.each do |token|
    case token
    when /\A[+-]?[0-9]+\Z/
      stack.push(token.to_i)
    when "+", "add", "plus"
      a, b = stack.pop(2)
      stack.push(a + b)
    when ".+", ".add", ".plus", ".sum"
      h = getlist(stack, markermode, markerautoclear)
      stack.push(h.sum)
    when "-", "sub", "subtract", "minus"
      a, b = stack.pop(2)
      stack.push(a - b)
    when "*", "mul", "multiply", "times"
      a, b = stack.pop(2)
      stack.push(a * b)
    when "/", "div", "divide"
      a, b = stack.pop(2)
      stack.push(a / b)
    when "%", "mod", "modulate", "rem", "remainder"
      a, b = stack.pop(2)
      stack.push(a % b)
    when "d", "roll"
      n = stack.pop
      d = stack.pop
      n.times { stack.push(rand(d) + 1) }
    when "c", "copy", "dup"
      h = stack.pop
      stack.push(h)
      stack.push(h)
    when ".c", ".copy", ".dup"
      h = getlist(stack, markermode, markerautoclear)
      stack.push(*h)
      stack.push(:mark) if markermode && !markerautoclear
      stack.push(*h)
    when "s", "swap"
      a, b = stack.pop(2)
      stack.push(b, a)
    when "r", "rot", "rotate"
      a, b, c = stack.pop(3)
      stack.push(b, c, a)
    when ".r", ".rot", ".rotate"
      c = stack.pop
      h = getlist(stack, markermode, markerautoclear)
      stack.push(*h.rotate(c))
    when "o", "over"
      a, b = stack.pop(2)
      stack.push(a, b, a)
    when "p", "print"
      t = stack.pop
      puts t
      stack.push(t) unless destructiveprint
    when ".ex", ".explode"
      x = stack.pop
      h = getlist(stack, markermode, markerautoclear)

      lm = 0
      loop do
        m = h.count(x)
        break if m == lm
        (m-lm).times { h << (rand(x) + 1) }
        lm = m
      end
      stack.push(*h)
    when ".k", ".kh", ".keep", ".keephigh", ".keephighest"
      k = stack.pop
      h = getlist(stack, markermode, markerautoclear)
      h.sort!
      stack.push(*h.pop(k))
    when ".kl", ".keeplow", ".keeplowest"
      k = stack.pop
      h = getlist(stack, markermode, markerautoclear)
      h.sort!
      stack.push(*h.shift(k))
    when ".>", ".gt", ".greaterthan"
      c = stack.pop
      h = getlist(stack, markermode, markerautoclear)
      stack.push(*h.select{ |d| d > c })
    when ".>=", ".gteq", ".greaterthanorequalto"
      c = stack.pop
      h = getlist(stack, markermode, markerautoclear)
      stack.push(*h.select{ |d| d >= c })
    when ".=", ".eq", ".equalto"
      c = stack.pop
      h = getlist(stack, markermode, markerautoclear)
      stack.push(*h.select{ |d| d == c })
    when ".<=", ".lteq", ".lessthanorequalto"
      c = stack.pop
      h = getlist(stack, markermode, markerautoclear)
      stack.push(*h.select{ |d| d <= c })
    when ".<", ".lt", ".lessthan"
      c = stack.pop
      h = getlist(stack, markermode, markerautoclear)
      stack.push(*h.select{ |d| d < c })
    when ".count"
      h = getlist(stack, markermode, markerautoclear)
      stack.push(h.count)
    when "!", "drop"
      stack.pop
    when ".!", ".drop"
      getlist(stack, markermode, markerautoclear)
    when "m", "mark"
      stack.push(:mark)
    when /\A'(.*)/
      file = File.readlines(File.join(ENV['HOME'], ".goforthanddie"))
      cmd = file.map { |line| line.split(?:) }.to_h[$1]
      if cmd.nil?
        puts "You haven't told me how to #{$1}!"
      else
        markermode, markerautoclear, destructiveprint = 
          handle(cmd, stack, markermode, markerautoclear, destructiveprint)
      end
    when "~s", "~stack"
      puts stack.join(" ")
    when "~mm", "~markermode"
      markermode = !markermode
    when "~mac", "~markerautoclear"
      markerautoclear = !markerautoclear
    when "~dp", "~destructiveprint"
      destructiveprint = !destructiveprint
    when "~state"
      print "Stack: "
      pp stack
      print "Marker Mode:       #{markermode ? "ON " : "OFF"}  "
      puts "Marker Auto-Clear: #{markerautoclear ? "ON " : "OFF"}"
      puts "Destructive Print: #{destructiveprint ? "ON " : "OFF"}"
    when "~user"
      puts "User definitions:"
      file = File.
        readlines(File.join(ENV['HOME'], ".goforthanddie")).
        map { |line| line.split(?:) }
      namemax = file.map {|defn| defn[0].length}.max + 1
      file.each do |name, defn|
        puts "  %#{namemax}s: #{defn}" % "'#{name}"
      end
    when "~q", "~quit"
      quit
    else
      puts "I don't know how to #{token}!"
    end
  end
  [markermode, markerautoclear, destructiveprint]
end

stk = []
mm = true
mac = false
dp = true

if $stdin.tty?
  begin
    require "readline"
  rescue LoadError
    Gem.install "readline"
    Gem.install "readline-ext"
    retry
  end

  hfile = File.join(ENV['HOME'], ".goforthanddie_history")
  if File.exist?(hfile)
    File.readlines(hfile).each do |line|
      Readline::HISTORY << line.strip
    end
  end

  while buf = Readline.readline("> ", true)
    mm, mac, dp = handle(buf, stk, mm, mac, dp)
  end

  quit
else
  mm, mac, dp = handle($stdin.read, stk, mm, mac, dp)
end