class Body

  attr_accessor :mass, :pos, :vel, :acc

  def initialize(mass = 0, pos = Vector[0,0,0], vel = Vector[0,0,0])
    @mass, @pos, @vel = mass, pos, vel
  end

  def calc(time_step, s)
    dt = time_step
    eval(s)
  end

  def ekin                         # kinetic energy
    0.5*@mass*(@vel*@vel)
  end

  def epot(body_array, eps)        # potential energy
    p = 0
    body_array.each do |b|
      unless b == self
        r = b.pos - @pos
        p += -@mass*b.mass/sqrt(r*r + eps*eps)
      end
    end
    p
  end

  def to_s
    "  mass = " + @mass.to_s + "\n" +
    "   pos = " + @pos.join(", ") + "\n" +
    "   vel = " + @vel.join(", ") + "\n"
  end

  def pp                           # pretty print
    print to_s
  end

  def ppx(body_array, eps)         # pretty print, with extra information (acc)
    STDERR.print to_s + "   acc = " + acc(body_array, eps).join(", ") + "\n"
  end

  def simple_print
    printf("%24.16e\n", @mass)
    @pos.each{|x| printf("%24.16e", x)}; print "\n"
    @vel.each{|x| printf("%24.16e", x)}; print "\n"
  end

  def simple_read
    @mass = gets.to_f
    @pos = gets.split.map{|x| x.to_f}.to_v
    @vel = gets.split.map{|x| x.to_f}.to_v
  end

end

class Nbody

  attr_accessor :time, :body

  def initialize
    @body = []
    @init_flag = true
  end

  def nb_acc
    null_vector = @body[0].pos*0
    @body.each{|bi| bi.acc = null_vector}
    @body.each_index do |i|
      bi = @body[i]
      (i+1...@body.size).each do |j|
          bj = @body[j]
          r = bj.pos - bi.pos
          r2 = r*r + @eps*@eps
          r3 = r2*sqrt(r2)
          bj.acc -= r*(bi.mass/r3)
          bi.acc += r*(bj.mass/r3)
      end
    end
  end    

  def evolve(integration_method, eps, dt, dt_dia, dt_out, dt_end,
             init_out, x_flag)
    @dt = dt                                                                 
    @eps = eps                                                               
    nsteps = 0
    e_init
    write_diagnostics(nsteps, x_flag)
    t_dia = dt_dia - 0.5*dt
    t_out = dt_out - 0.5*dt
    t_end = dt_end - 0.5*dt

    simple_print if init_out

    while @time < t_end
      send(integration_method)
      @time += dt
      nsteps += 1
      if @time >= t_dia
        write_diagnostics(nsteps, x_flag)
        t_dia += dt_dia
      end
      if @time >= t_out
        simple_print
        t_out += dt_out
      end
    end
  end

  def calc(s)
    @body.each{|b| b.calc(@dt, s.gsub(/([a-z]\w*)/, '@\&').gsub(/@dt/, 'dt'))}
  end

  def forward
    nb_acc
    calc(" old_acc = acc ")
    calc(" pos += vel*dt ")
    calc(" vel += old_acc*dt ")
  end

  def leapfrog
    if @init_flag
      nb_acc
      @init_flag = false
    end
    calc(" vel += acc*0.5*dt ")
    calc(" pos += vel*dt ")
    nb_acc
    calc(" vel += acc*0.5*dt ")
  end

  def rk2
    calc(" old_pos = pos ")
    nb_acc
    calc(" half_vel = vel + acc*0.5*dt ")
    calc(" pos += vel*0.5*dt ")
    nb_acc
    calc(" vel += acc*dt ")
    calc(" pos = old_pos + half_vel*dt ")
  end

  def rk4
    calc(" old_pos = pos ")
    nb_acc
    calc(" a0 = acc ")
    calc(" pos = old_pos + vel*0.5*dt + a0*0.125*dt*dt ")
    nb_acc
    calc(" a1 = acc ")
    calc(" pos = old_pos + vel*dt + a1*0.5*dt*dt ")
    nb_acc
    calc(" a2 = acc ")
    calc(" pos = old_pos + vel*dt + (a0+a1*2)*(1/6.0)*dt*dt ")
    calc(" vel += (a0+a1*4+a2)*(1/6.0)*dt ")
  end

  def ekin                        # kinetic energy
    e = 0
    @body.each{|b| e += b.ekin}
    e
  end

  def epot                        # potential energy
    e = 0
    @body.each{|b| e += b.epot(@body, @eps)}
    e/2                           # pairwise potentials were counted twice
  end

  def e_init                      # initial total energy
    @e0 = ekin + epot
  end

  def write_diagnostics(nsteps, x_flag)
    etot = ekin + epot
    STDERR.print <  def pp                           # pretty print
    print "     N = ", @body.size, "\n"
    print "  time = ", @time, "\n"
    @body.each{|b| b.pp}
  end

  def ppx                          # pretty print, with extra information (acc)
    print "     N = ", @body.size, "\n"
    print "  time = ", @time, "\n"
    @body.each{|b| b.ppx(@body, @eps)}
  end

  def simple_print
    print @body.size, "\n"
    printf("%24.16e\n", @time)
    @body.each{|b| b.simple_print}
  end

  def simple_read
    n = gets.to_i
    @time = gets.to_f
    for i in 0...n
      @body[i] = Body.new
      @body[i].simple_read
    end
  end

end