require 'rexml/validation/validationexception'

module REXML
  module Validation
    module Validator
      NILEVENT = [ nil ]
      def reset
        @current = @root
        @attr_stack = []
        self
      end
      def validate( event ) 
        #puts "Current: #@current"
        #puts "Event: #{event.inspect}"
        #puts "#"*80
        @attr_stack = [] unless defined? @attr_stack
        match = @current[ event ]
        raise ValidationException.new( "Validation error.  Expected: "+
          @current.keys.collect{|e| e.inspect }.join(" or ")+
          " but got #{Event.new( event[0], event[1] ).inspect}" ) unless match
        @current = match

        # Check for attributes
        case event[0]
        when :start_element
          @attr_stack << event[2]
          begin
            sattr = [:start_attribute, nil]
            eattr = [:end_attribute]
            text = [:text, nil]
            k,v = event[2].find { |k,v| 
              sattr[1] = k
              m = @current[ sattr ] 
              if m 
                # If the state has text children...
                if m[ eattr ]
                  @current = m
                else
                  text[1] = v
                  m = m[ text ]
                  text[1] = nil
                  @current = m if m
                end
                m = @current[ eattr ]
                if m
                  @current = m
                  true
                else
                  false
                end
              else
                false
              end
            }
            event[2].delete(k) if k
          end while k
        when :end_element
          attrs = @attr_stack.pop
          raise ValidationException.new( "Validation error.  Illegal "+
            " attributes: #{attrs.inspect}") if attrs.length > 0
        end
      end
      def dump
        @root.dump
      end
    end

    class State
      include Enumerable

      attr_accessor :flag
      attr_reader :count

      def initialize( count = nil )
        @states = []
        if count
          @@count = count
        else
          if defined? @@count
            @@count += 1
          else
            @@count = 0
          end
        end
        @count = @@count
      end

      def []=( event, state )
        @states.delete_if { |e,s| e == event }
        @states << [event, state]
        return state
      end

      def []( event )
        rv = @states.find { |e,s| e.matches?( event ) }
        return rv[1] if rv
      end

      def keys
        @states.collect{ |e,s| e }
      end

      def each
        @states.each {|e,s| yield s}
      end
      
      def to_s
        inspect
      end

      def inspect
        "? #@count) #{@states.collect{|e,s| e.to_s+%Q/->/+
          (s.nil? ? s.inspect( visited ) : s.count.to_s)}.join(', ')} ?"
      end
      def dump
        ind = 0
        visited = []
        itterate_over = lambda {|x|
          puts(" "*ind + x.inspect)
          unless visited.include? x
            visited << x
            ind += 1
            x.each(&itterate_over)
            ind -= 1
          end
        }
        itterate_over.call( self )
      end
    end

    class Event
      def initialize(event_type, event_arg=nil )
        @event_type = event_type
        @event_arg = event_arg
      end

      attr_reader :done?
      attr_reader :event_type
      attr_accessor :event_arg

      def single?
        return (@event_type != :start_element and @event_type != :start_attribute)
      end

      def matches?( event )
        #puts "#@event_type =? #{event[0]} && #@event_arg =? #{event[1]} "
        return false unless event[0] == @event_type
        case event[0]
        when nil
          return true
        when :start_element
          return true if event[1] == @event_arg
        when :end_element
          return true
        when :start_attribute
          return true if event[1] == @event_arg
        when :end_attribute
          return true
        when :end_document
          return true
        when :text
          return (@event_arg.nil? or @event_arg == event[1])
=begin
        when :processing_instruction
          false
        when :xmldecl
          false
        when :start_doctype
          false
        when :end_doctype
          false
        when :externalentity
          false
        when :elementdecl
          false
        when :entity
          false
        when :attlistdecl
          false
        when :notationdecl
          false
        when :end_doctype
          false
=end
        else
          false
        end
      end

      def ==( other )
        @event_type == other.event_type and @event_arg == other.event_arg
      end

      def to_s
        inspect
      end

      def inspect
        "#{@event_type.inspect}( #@event_arg )"
      end
    end
  end
end
