require 'RMagick'

class Amida
  class AmidaError < RuntimeError; end
  class BadBranchError < AmidaError; end
  class BadIndexError < AmidaError; end

  def initialize(size=2)
    @size = size
    @branch = {}
    @keys = nil
  end
  attr_reader :size
  
  def add_branch(x, y)
    raise(BadBranchError) if x >= @size-1 || x < 0
    raise(BadBranchError) if y > 1.0 || y < 0.0
    while @branch.include? y
      y += 0.01
    end
    @branch[y] = x
    @keys = @branch.keys.sort
  end

  def setup_branch
    (@size-1).times do |x|
      (@size / 2).times do 
	add_branch(x, rand * 0.7 + 0.3)
      end
    end
  end
  
  def each
    @keys.each do |key|
      yield(@branch[key], key)
    end
  end
  
  def trace(idx = 0)
    raise(BadIndexError) if idx >= @size || idx < 0
    curr = idx
    y = 0.0
    yield(curr, y) if block_given?
    self.each do |v, k|
      if curr == v 
	curr = curr + 1
	yield(curr, k) if block_given?
      elsif curr == v + 1
	curr = curr - 1
	yield(curr, k) if block_given?
      end
    end
    yield(curr, 1.0) if block_given?
    return curr
  end
end

class AmidaMagick
  def initialize(width=512, height=512)
    @width = width
    @height = height
  end
  attr_reader :width, :height

  def canvas
    Magick::Image.new(@width, @height)
  end

  def draw_field(amida)
    size = amida.size

    draw = Magick::Draw.new
    draw.stroke('black')
    draw.stroke_width(2)
    y0 = ay_to_my(0)
    y1 = ay_to_my(1)
    size.times do |ax|
      x = ax_to_mx(ax, size)
      draw.line(x, y0, x, y1)
    end

    amida.each do |ax, ay|
      x0 = ax_to_mx(ax, size)
      x1 = ax_to_mx(ax + 1, size)
      y = ay_to_my(ay)
      draw.line(x0, y, x1, y)
    end
    draw
  end

  def draw_entry(amida, idx = 0)
    size = amida.size
    
    draw = Magick::Draw.new
    draw.stroke('red')
    draw.stroke_width(4)

    ary = []
    last_x = nil
    amida.trace(idx) do |ax, ay|
      x = ax_to_mx(ax, size)
      y = ay_to_my(ay)
      if last_x
	ary << last_x
	ary << ay_to_my(ay)
      end
      ary << x
      ary << y
      last_x = x
    end
    draw.fill_opacity(0)
    draw.polyline(*ary)

    draw
  end

  def add_branch(amida, x, y)
    x = mx_to_ax(x, amida.size)
    y = my_to_ay(y)
    begin
      amida.add_branch(x, y)
      return [x, y]
    rescue Amida::AmidaError
      return nil
    end
  end

  private
  def entry_x(size)
    a = 0.5 / size
    (0...size).collect do |n|
      ((a + 1.0 / size * n) * @width).to_i
    end
  end

  def amida_x_to_magick_x(ax, size)
    a = 0.5 / size
    ((a + 1.0 / size * ax) * @width).to_i
  end
  alias ax_to_mx amida_x_to_magick_x
  
  def magick_x_to_amida_x(mx, size)
    ((size * mx) / @width.to_f - 0.5).to_i
  end
  alias mx_to_ax magick_x_to_amida_x

  def amida_y_to_magick_y(ay)
    ((ay * 0.9 + 0.05) * @height).to_i
  end
  alias ay_to_my amida_y_to_magick_y

  def magick_y_to_amida_y(my)
    (my / @height.to_f - 0.05) / 0.9
  end
  alias my_to_ay magick_y_to_amida_y
end
