SIZE = 30
N = 15

class Xword
	attr_accessor :x, :y, :grid, :num
	def initialize
		@x = 0
		@y = 0
		@grid = Array.new(15) { Array.new(15) }
		@num = Array.new(15) { Array.new(15) }
		number
	end

	def letter
		grid[@y][@x]
	end

	def set(a)
		set_at(@y, @x, a)
	end

	def set_at(j, i, a)
		grid[j][i] = a
	end

	def symmetric(j, i)
		[[j, i], [14-j, 14-i], [14-j, i], [j, 14-i]]
	end

	def start_across?(j, i)
		(grid[j][i] != "#") &&
			(i == 0 || grid[j][i-1] == "#") && 
			(i < 14 && grid[j][i+1] != "#")
	end

	def start_down?(j, i)
		(grid[j][i] != "#") &&
			(j == 0 || grid[j-1][i] == "#") && 
			(j < 14 && grid[j+1][i] != "#")
	end

	def number
		n = 1
		grid.each_with_index do |col, j|
			col.each_with_index do |row, i|
				if start_across?(j,i) || start_down?(j,i)
					num[j][i] = n
					n += 1
				else
					num[j][i] = nil
				end
			end
		end
	end
end

@app = Shoes.app :width => 800, :height => 630 do

	def bounds(y,x)
		[x * SIZE, y * SIZE, SIZE, SIZE]
	end

	def inner_bounds(y,x)
		x, y, h, w = bounds(y, x)

		[x+1, y+1, h-2, w-2]
	end

	def letter_pos(y,x)
		[y * SIZE, x * SIZE]
	end

	def each_square
		0.upto(N-1) {|j| 0.upto(N-1) {|i| yield [j,i] }}
	end

	def square(y = @xw.y, x = @xw.x)
		stroke gray(32)
		l = @xw.grid[y][x]
		n = @xw.num[y][x]
		if l == "#"
			fill "#000"
			rect *(bounds(y,x))
		else
			fill rgb(255, 255, 255)
			rect *(bounds(y,x))
			b, a = letter_pos(y,x)
			# problem 1: as letters and numbers are added, the cursor
			# movement slows down dramatically
			# problem 2: the if statement causes segfaults when adding a black 
			# square
			if n	
			  para n, :left => a - 2, :top => b - 2, :font => '8px' 
			end
			if l
				para l, :left => a + 7, :top => b + 5, :font => '14px bold'
			end
		end
	end

	def refresh_all
		each_square {|j,i| square(j, i)}
		active
	end

	def active
		stroke red
		nofill
		@high = rect(*inner_bounds(@xw.y, @xw.x))
	end

	def inactive
		@high.remove
	end

	def move_active(y,x)
		inactive
		@xw.y = (@xw.y + y) % N
		@xw.x = (@xw.x + x) % N
		active
	end

	def forward
		move_active(0,1)
	end

	def backward
		move_active(0,-1)
	end

	def set(letter)
		del
		@xw.set(letter)
		square
		forward
	end

	def set_at(y, x, letter)
		@xw.set_at(y, x, letter)
		square(y, x)
	end

	def black(y = @xw.y, x = @xw.x)
		@xw.symmetric(y, x).each {|col, row|
			set_at col, row, "#"
		}
		forward
		Thread.new { renumber }
	end

	def clear_black(y = @xw.y, x = @xw.x)
		@xw.symmetric(y, x).each {|col, row|
			set_at col, row, ""
		}
		Thread.new { renumber }
	end

	def backspace
		backward
		del
	end

	def del
		if @xw.letter == "#"
			clear_black
		end

		@xw.set("")
	end

	def renumber
		@xw.number
		refresh_all
	end

	keypress {|key|
		inactive
		case key
		when :left; append { move_active(0,-1) }
		when :right; append { move_active(0,1) }
		when :up; append { move_active(-1,0) }
		when :down; append { move_active(1,0) }
		when :control_q; exit
		when "A".."Z"; append { set(key.upcase) }
		when "a".."z"; append { set(key.upcase) }
		when " "; append { black }
		when :backspace; append { backspace }
		when :delete; append { del }
		end
	}

	# ----------- main ----------------
	@xw = Xword.new
	@high = nil
	@xw.x = @xw.y = 0

	background rgb(0,32,0)
	refresh_all
end
