module CodeRay
module Encoders
	# = HTML Encoder
	#
	# This is CodeRay's most important highlighter:
	# It provides save, fast XHTML generation and CSS support.
	#
	# == Usage
	#
	#  require 'coderay'
	#  puts CodeRay.scan('Some /code/', :ruby).html  #-> a HTML page
	#  puts CodeRay.scan('Some /code/', :ruby).html(:wrap => :span) #-> Some /code/
	#  puts CodeRay.scan('Some /code/', :ruby).span  #-> the same
	#  
	#  puts CodeRay.scan('Some code', :ruby).html(
	#    :wrap => nil,
	#    :line_numbers => :inline,
	#    :css => :style
	#  )
	#  #-> 1  Some code
	#
	# == Options
	#
	# === :tab_width
	# Convert \t characters to +n+ spaces (a number.)
	# Default: 8
	#
	# === :css
	# How to include the styles; can be :class or :style.
	# 
	# Default: :class
	#
	# === :wrap
	# Wrap in :page, :div, :span or nil.
	# 
	# You can also use Encoders::Div and Encoders::Span.
	# 
	# Default: :page
	# 
	# === :line_numbers
	# Include line numbers in :table, :inline or nil (no line numbers)
	# 
	# Default: nil
	# 
	# === :line_number_start
	# Where to start with line number counting.
	# 
	# Default: 1
	# 
	# === :bold_every
	# Make every +n+-th number appear bold.
	# 
	# Default: 10
	# 
	# === :hint
	# Include some information into the output using the title attribute.
	# Can be :info (show token type on mouse-over) or :debug.
	# 
	# Default: false
	class HTML < Encoder
		include Streamable
		register_for :html
		FILE_EXTENSION = 'html'
		DEFAULT_OPTIONS = {
			:tab_width => 8,
			:level => :xhtml,
			:css => :class,
			:wrap => :page,
			:line_numbers => nil,
			:line_number_start => 1,
			:bold_every => 10,
			:hint => false,
		}
		
		require 'coderay/encoders/helpers/html_helper'
		require 'coderay/encoders/helpers/html_output'
		require 'coderay/encoders/helpers/html_css'
		def initialize(*)
			super
			@last_options = nil
		end
	protected
		
		HTML_ESCAPE = {  #:nodoc:
			'&' => '&',
			'"' => '"',
			'>' => '>',
			'<' => '<',
		}
		# This is to prevent illegal HTML.
		# Strange chars should still be avoided in codes.
		evil_chars = Array(0x00...0x20) - [?n, ?t]
		evil_chars.each { |i| HTML_ESCAPE[i.chr] = ' ' }
		ansi_chars = Array(0x7f..0xff)
		ansi_chars.each { |i| HTML_ESCAPE[i.chr] = '%d;' % i }
		# \x9 (\t) and \xA (\n) not included
		HTML_ESCAPE_PATTERN = /[&"><\0-\x8\xB-\x1f\x7f-\xff]/
		def setup options
			super
			return if options == @last_options
			@last_options = options
			@HTML_ESCAPE = HTML_ESCAPE.dup
			@HTML_ESCAPE["\t"] = ' ' * options[:tab_width]
			
			@opened = [nil]
			@css = CSS.new
			hint = options[:hint]
			raise ArgumentError if hint and not [:debug, :info].include? hint
			case options[:css]
			
			when :class
				@css_style = Hash.new do |h, k|
					if k.is_a? Array
						type = k.first
					else
						type = k
					end
					c = ClassOfKind[type]
					if c == :NO_HIGHLIGHT and not hint
						h[k] = false
					else
						title = if hint
							if hint == :debug
								' title="%p"' % [ k ]
							elsif hint == :info and k.size == 1
								" title=\"#{type.to_s.gsub(/_/, " ").capitalize}\""
							end
						else
							''
						end
						h[k] = '' % [title, c]
					end
				end
				
			when :style
				@css_style = Hash.new do |h, k|
					if k.is_a? Array
						styles = k.dup
					else
						styles = [k]
					end
					type = styles.first
					styles.map! { |c| ClassOfKind[c] }
					if styles.first == :NO_HIGHLIGHT and not hint
						h[k] = false
					else
						title = if hint
							if hint == :debug
								' title="%p"' % [ styles ]
							elsif hint == :info and styles.size == 1
								" title=\"#{type.to_s.gsub(/_/, " ").capitalize}\""
							end
						else
							''
						end
						style = @css[*styles]
						h[k] =
							if style
								'' % [title, style]
							else
								false
							end
					end
				end
				
			else
				raise "Unknown value %p for :css." % options[:css]
				
			end
		end
		def finish options
			not_needed = @opened.shift
			@out << '' * @opened.size
			@out.extend Output
			@out.numerize! options[:line_numbers], options # if options[:line_numbers]
			@out.wrap! options[:wrap] # if options[:wrap]
			#require 'pp'
			#pp @css_style, @css_style.size
			super
		end
		def token text, type
			if text.is_a? String
				if text =~ /#{HTML_ESCAPE_PATTERN}/o
					text = text.gsub(/#{HTML_ESCAPE_PATTERN}/o) { |m| @HTML_ESCAPE[m] }
				end
				@opened[0] = type
				style = @css_style[@opened]
				if style
					@out << style << text << ''
				else
					@out << text
				end
			else
				case text
				when :open
					@opened[0] = type
					@out << @css_style[@opened]
					@opened << type
				when :close
					unless @opened.empty?
						raise 'Not Token to be closed.' unless @opened.size > 1
						@out << ''
						@opened.pop
					end
				when nil
					raise 'Token with nil as text was given: %p' % [[text, type]]
				else
					raise 'unknown token kind: %p' % text
				end
			end
		end
		
	end
end
end