require "time"

require "rss/utils"
require "rss/converter"

module RSS

	VERSION = "0.0.8"

	DEBUG = false

	class Error < StandardError; end

	class OverlappedPrefixError < Error
		attr_reader :prefix
		def initialize(prefix)
			@prefix = prefix
		end
	end

	class InvalidRSSError < Error; end

	class MissingTagError < InvalidRSSError
		attr_reader :tag, :parent
		def initialize(tag, parent)
			@tag, @parent = tag, parent
			super("tag <#{tag}> is missing in tag <#{parent}>")
		end
	end

	class TooMuchTagError < InvalidRSSError
		attr_reader :tag, :parent
		def initialize(tag, parent)
			@tag, @parent = tag, parent
			super("tag <#{tag}> is too much in tag <#{parent}>")
		end
	end

	class MissingAttributeError < InvalidRSSError
		attr_reader :tag, :attribute
		def initialize(tag, attribute)
			@tag, @attribute = tag, attribute
			super("attribute <#{attribute}> is missing in tag <#{tag}>")
		end
	end

	class UnknownTagError < InvalidRSSError
		attr_reader :tag, :uri
		def initialize(tag, uri)
			@tag, @uri = tag, uri
			super("tag <#{tag}> is unknown in namespace specified by uri <#{uri}>")
		end
	end

	class NotExceptedTagError < InvalidRSSError
		attr_reader :tag, :parent
		def initialize(tag, parent)
			@tag, @parent = tag, parent
			super("tag <#{tag}> is not expected in tag <#{parent}>")
		end
	end

	class NotAvailableValueError < InvalidRSSError
		attr_reader :tag, :value
		def initialize(tag, value)
			@tag, @value = tag, value
			super("value <#{value}> of tag <#{tag}> is not available.")
		end
	end

	class UnknownConversionMethodError < Error
		attr_reader :to, :from
		def initialize(to, from)
			@to = from
			@from = from
			super("can't convert to #{to} from #{from}.")
		end
	end
	# for backward compatibility
	UnknownConvertMethod = UnknownConversionMethodError

	class ConversionError < Error
		attr_reader :string, :to, :from
		def initialize(string, to, from)
			@string = string
			@to = from
			@from = from
			super("can't convert #{@string} to #{to} from #{from}.")
		end
	end

	module BaseModel

		include Utils

		def install_have_child_element(name)
			add_need_initialize_variable(name)

			attr_accessor name
			install_element(name) do |n, elem_name|
				<<-EOC
				if @#{n}
					"\#{indent}\#{@#{n}.to_s(convert)}"
				else
					''
				end
EOC
			end
		end
		alias_method(:install_have_attribute_element, :install_have_child_element)

		def install_have_children_element(name, postfix="s")
			add_have_children_element(name)

			def_children_accessor(name, postfix)
			install_element(name, postfix) do |n, elem_name|
				<<-EOC
				rv = ''
				@#{n}.each do |x|
					rv << "\#{indent}\#{x.to_s(convert)}"
				end
				rv
EOC
			end
		end

		def install_text_element(name)
			self::ELEMENTS << name
			add_need_initialize_variable(name)

			attr_writer name
			convert_attr_reader name
			install_element(name) do |n, elem_name|
				<<-EOC
				if @#{n}
					rv = "\#{indent}<#{elem_name}>"
					value = html_escape(@#{n})
					if convert and @converter
						rv << @converter.convert(value)
					else
						rv << value
					end
	  	    rv << "</#{elem_name}>"
					rv
				else
					''
				end
EOC
			end
		end

		def install_date_element(name, type, disp_name=name)
			self::ELEMENTS << name
			add_need_initialize_variable(name)

			# accessor
			convert_attr_reader name
			module_eval(<<-EOC, *get_file_and_line_from_caller(2))
			def #{name}=(new_value)
				if new_value.kind_of?(Time)
					@#{name} = new_value
				else
					if @do_validate
						begin
							@#{name} = Time.send('#{type}', new_value)
						rescue ArgumentError
							raise NotAvailableValueError.new('#{disp_name}', new_value)
						end
					elsif /\\A\\s*\\z/ !~ new_value.to_s
						@#{name} = Time.parse(new_value)
					else
						@#{name} = nil
					end
				end

				# Is it need?
				if @#{name}
					class << @#{name}
						alias_method(:_to_s, :to_s) unless respond_to?(:_to_s)
						alias_method(:to_s, :#{type})
					end
				end

			end
EOC
			
			install_element(name) do |n, elem_name|
				<<-EOC
				if @#{n}
					rv = "\#{indent}<#{elem_name}>"
					value = html_escape(@#{n}.#{type})
					if convert and @converter
						rv << @converter.convert(value)
					else
						rv << value
					end
	  	    rv << "</#{elem_name}>"
					rv
				else
					''
				end
EOC
			end

		end

		private
		def install_element(name, postfix="")
			elem_name = name.sub('_', ':')
			module_eval(<<-EOC, *get_file_and_line_from_caller(2))
			def #{name}_element#{postfix}(convert=true, indent='')
				#{yield(name, elem_name)}
			end
			private :#{name}_element#{postfix}
EOC
		end

		def convert_attr_reader(*attrs)
			attrs.each do |attr|
				attr = attr.id2name if attr.kind_of?(Integer)
				module_eval(<<-EOC, *get_file_and_line_from_caller(2))
				def #{attr}
					if @converter
						@converter.convert(@#{attr})
					else
						@#{attr}
					end
				end
EOC
			end
		end

		def def_children_accessor(accessor_name, postfix="s")
			module_eval(<<-EOC, *get_file_and_line_from_caller(2))
			def #{accessor_name}#{postfix}
				@#{accessor_name}
			end

			def #{accessor_name}(*args)
				if args.empty?
					@#{accessor_name}.first
				else
					@#{accessor_name}.send("[]", *args)
				end
			end
				
			def #{accessor_name}=(*args)
				if args.size == 1
					@#{accessor_name}.push(args[0])
				else
					@#{accessor_name}.send("[]=", *args)
				end
			end
			alias_method(:set_#{accessor_name}, :#{accessor_name}=)
EOC
		end

	end

	URI = "http://purl.org/rss/1.0/"

	class Element

		extend BaseModel
		include Utils

		class << self

			def inherited(klass)
				klass.module_eval(<<-EOC)
				public
				
				TAG_NAME = name.split('::').last.downcase


				@@must_call_validators = {::RSS::URI => ''}

				def self.must_call_validators
					@@must_call_validators
				end

				def self.install_must_call_validator(prefix, uri)
					@@must_call_validators[uri] = prefix
				end
				
				@@model = []

				def self.model
					@@model
				end

				def self.install_model(tag, occurs=nil)
					if m = @@model.find {|t, o| t == tag}
						m[1] = occurs
					else
						@@model << [tag, occurs]
					end
				end

				@@get_attributes = []
				
				def self.get_attributes()
					@@get_attributes
				end

				def self.install_get_attribute(name, uri, required=true)
					attr_writer name
					convert_attr_reader name
					@@get_attributes << [name, uri, required]
				end

				@@have_content = false

				def self.content_setup
					attr_writer :content
					convert_attr_reader :content
					@@have_content = true
				end

				def self.have_content?
					@@have_content
				end

				@@have_children_elements = []

				def self.have_children_elements
					@@have_children_elements
				end

				def self.add_have_children_element(variable_name)
					@@have_children_elements << variable_name
				end
				
				@@need_initialize_variables = []
				
				def self.add_need_initialize_variable(variable_name)
					@@need_initialize_variables << variable_name
				end
				
				def self.need_initialize_variables
					@@need_initialize_variables
				end

				EOC
			end

			def required_prefix
				nil
			end

			def required_uri
				nil
			end
			
			def install_ns(prefix, uri)
				if self::NSPOOL.has_key?(prefix)
					raise OverlappedPrefixError.new(prefix)
				end
				self::NSPOOL[prefix] = uri
			end

		end

		attr_accessor :do_validate

		def initialize(do_validate=true)
			@converter = nil
			@output_encoding = nil
			@do_validate = do_validate
			initialize_variables
		end

		def tag_name
			self.class::TAG_NAME
		end

		def converter=(converter)
			@converter = converter
			children.each do |child|
				child.converter = converter unless child.nil?
			end
		end
		
		def validate
			validate_attribute
			__validate
		end
		
		def validate_for_stream(tags)
			__validate(tags, false)
		end

    private
		def initialize_variables
			self.class.need_initialize_variables.each do |variable_name|
				instance_eval("@#{variable_name} = nil")
			end
			initialize_have_children_elements
			@content = "" if self.class.have_content?
		end

		def initialize_have_children_elements
			self.class.have_children_elements.each do |variable_name|
				instance_eval("@#{variable_name} = []")
			end
		end

		# not String class children.
		def children
			[]
		end

		# default #validate() argument.
		def _tags
			[]
		end

		def _attrs
			[]
		end

		def __validate(tags=_tags, recursive=true)
			if recursive
				children.compact.each do |child|
					child.validate
				end
			end
			must_call_validators = self.class::must_call_validators
			tags = tag_filter(tags.dup)
			p tags if DEBUG
			self.class::NSPOOL.each do |prefix, uri|
				if tags.has_key?(uri) and !must_call_validators.has_key?(uri)
					meth = "#{prefix}_validate"
					send(meth, tags[uri]) if respond_to?(meth, true)
				end
			end
			must_call_validators.each do |uri, prefix|
				send("#{prefix}_validate", tags[uri])
			end
		end

		def validate_attribute
			_attrs.each do |a_name, required|
				if required and send(a_name).nil?
					raise MissingAttributeError.new(self.class::TAG_NAME, a_name)
				end
			end
		end

		def other_element(convert, indent='')
			rv = ''
			private_methods.each do |meth|
				if /\A([^_]+)_[^_]+_elements?\z/ =~ meth and
						self.class::NSPOOL.has_key?($1)
					res = send(meth, convert)
					rv << "#{indent}#{res}\n" if /\A\s*\z/ !~ res
				end
			end
			rv
		end

		def _validate(tags, model=self.class.model)
			count = 1
			do_redo = false
			not_shift = false
			tag = nil

			model.each_with_index do |elem, i|

				if DEBUG
					p "before" 
					p tags
					p elem
				end

				if not_shift
					not_shift = false
				else
					begin
						tag = tags.shift
					rescue NameError
					end
				end

				if DEBUG
					p "mid"
					p count
				end

				case elem[1]
				when '?'
					if count > 2
						raise TooMuchTagError.new(elem[0], tag_name)
					else
						if elem[0] == tag
							do_redo = true
						else
							not_shift = true
						end
					end
				when '*'
					if elem[0] == tag
						do_redo = true
					else
						not_shift = true
					end
				when '+'
					if elem[0] == tag
						do_redo = true
					else
						if count > 1
							not_shift = true
						else
							raise MissingTagError.new(elem[0], tag_name)
						end
					end
				else
					if elem[0] == tag
						begin
							if model[i+1][0] != elem[0] and tags.first == elem[0]
								raise TooMuchTagError.new(elem[0], tag_name)
							end
						rescue NameError # for model[i+1][0] and tags.first
						end
					else
						raise MissingTagError.new(elem[0], tag_name)
					end
				end

				if DEBUG
					p "after"
					p not_shift
					p do_redo
					p tag
				end

				if do_redo
					do_redo = false
					count += 1
					redo
				else
					count = 1
				end

			end

			if !tags.nil? and !tags.empty?
				raise NotExceptedTagError.new(tag, tag_name)
			end

		end

		def tag_filter(tags)
			rv = {}
			tags.each do |tag|
				rv[tag[0]] = [] unless rv.has_key?(tag[0])
				rv[tag[0]].push(tag[1])
			end
			rv
		end

	end

end
