#!/usr/bin/env ruby

# NOTE: This is a naive axis-aligned bounding box solution.

class Point < Struct.new(:x, :y)
  def self.random
    Point.new(rand, rand)
  end

  def dist(p2)
    xd = self.x - p2.x
    yd = self.y - p2.y
    Math.sqrt( xd**2 + yd**2 )
  end

  def to_s
    "(#{x}, #{y})"
  end
end

class Circle < Struct.new(:center, :radius)
  def to_s
    "{center:#{center}, radius:#{radius}}"
  end
end

class AABB  # axis-aligned bounding box
  def initialize
    @p1 = Point.new
    @p2 = Point.new
  end
  
  def enclose(points)
    points.each {|point| add(point)}
  end
  
  def add(point)
    @p1.x = point.x if @p1.x.nil? or @p1.x > point.x
    @p1.y = point.y if @p1.y.nil? or @p1.y > point.y
    @p2.x = point.x if @p2.x.nil? or @p2.x < point.x
    @p2.y = point.y if @p2.y.nil? or @p2.y < point.y
  end

  def width
    @p2.x - @p1.x
  end

  def height
    @p2.y - @p1.y
  end
  
  def center
    Point.new(@p1.x + width/2.0, @p1.y + height/2.0)
  end
  
  def to_s  
    "#@p1,#@p2"
  end
end


def generate_samples(n)
  (1..n).map { Point.random }
end

def encircle(points)        # takes array of Point objects
  bbox = AABB.new
  bbox.enclose(points)
  c = bbox.center
  r = points.map {|pt| pt.dist(c)}.max
  Circle.new(c, r)
end


if $0 == __FILE__

require 'test/unit'

class TestEncircle < Test::Unit::TestCase

  EPSILON = 0.001

  def assert_in_delta(expected, actual, delta=EPSILON)
    super(expected, actual, delta)
  end

  def test_single_point
    circ = encircle( [ Point.new(0.8, 0.3) ] )
    assert_in_delta( 0.8, circ.center.x )
    assert_in_delta( 0.3, circ.center.y )
    assert_in_delta( 0.0, circ.radius )
  end

  def test_two_points
    p1, p2 = Point.new(0.25, 0.25), Point.new(0.75, 0.75)
    circ = encircle( [p1, p2] )
    assert_in_delta( 0.5, circ.center.x )
    assert_in_delta( 0.5, circ.center.y )
    assert_in_delta( circ.center.dist(p1), circ.radius )
  end

end

end



