require 'htree'
require 'web/escape'

module Web
  class Agent
    class Forms
      include Enumerable
      def initialize(html)
        @forms = []
        parse(html)
      end
      attr_reader :forms

      def each
        @forms.each {|form|
          yield form
        }
      end

      def parse(html)
        form = nil
        doc = HTree.parse(html)
        doc.traverse_with_path {|elem,path|
          next unless path=~/\/form\z/
          @forms << Form.new(elem)
        }
        
      end

      class Form
        def initialize(elem)
          @method  = (elem.get_attr('method')  || 'GET').upcase
          @action  = elem.get_attr('action')
          @enctype = elem.get_attr('enctype') || 'application/x-www-form-urlencoded'
          @fields = []
          parse(elem)
        end
        attr_accessor :method, :action, :enctype, :fields
        
        def parse(elem)
          elem.traverse_element {|e|
            case e.qualified_name
            when "input"
              field = input(e)
              @fields << field if field
            when "textarea"
              @fields << textarea(e)
            when "select"
              @fields << select(e)
            end
          }
        end
        
        def input(elem)
          type = elem.get_attr("type")
          name = elem.get_attr('name')
          value = elem.get_attr('value')
          case type
          when 'hidden'
            Hidden.new(name, value)
          when 'password'
            Password.new(name, value)
          when 'checkbox'
            checked = elem.get_attr('value')=~/checked/i
            Checkbox.new(name, value, checked)
          when 'radio'
            checked = elem.get_attr('value')=~/checked/i
            Radio.new(name, value, checked)
          when 'submit'
            Submit.new(name, value)
          when 'file'
            File.new(name)
          when 'hidden'
            Hidden.new(name, value)
          when 'image'
            Image.new(name, value)
          when 'reset'
            nil
          else
            Text.new(name, value)
          end
        end
        
        def textarea(elem)
          name = elem.get_attr('name')
          text = ''
          elem.each_child {|c|
            text << Web::unescapeHTML(c.raw_string)
          }
          Textarea.new(name, text)
        end
        
        def select(elem)
          name     = elem.get_attr('name')
          multiple = elem.get_attr('multiple')=~/multiple/i ? true : false
          options  = []
          field = Select.new(name, multiple, [])
          elem.each_child {|c|
            if c.is_a?(HTree::Elem) && c.qualified_name=='option'
              value    = c.get_attr('value')
              selected = c.get_attr('selected') =~ /selected/i ? true : false
              field.options << Select::Option.new(value, selected)
            end
          }
          field
        end
        
        def submit(agent)
          uri = agent.uri + @action
          agent.req.header['content-type'] = @enctype
          agent.req.form.clear
          if @method=='POST'
            @fields.each {|field|
              case field
              when Text
                agent.req.form.add field.name, field.value
              when Password
                agent.req.form.add field.name, field.value
              when Checkbox
                agent.req.form.add field.name, field.value if field.checked
              when Radio
                agent.req.form.add field.name, field.value if field.checked
              when Submit
                agent.req.form.add field.name, field.value
              when File
                filedata = Web::Common::FileData.new(field.name,'application/octet-stream')
                filedata.path = field.path
                agent.req.form.add field.name, filedata
              when Hidden
                agent.req.form.add field.name, field.value
              when Image
                agent.req.form.add field.name, field.value
              when Textarea
                agent.req.form.add field.name, field.text
              when Select
                field.options.each {|o|
                  if o.checked
                    agent.req.form.add field.name, o.value
                  end
                }
              end
            }
            agent.post(uri)
          else
            agent.set_uri(uri)
            @fields.each {|field|
              case field
              when Text
                agent.req.query.add field.name, field.value
              when Password
                agent.req.query.add field.name, field.value
              when Checkbox
                agent.req.query.add field.name, field.value if field.checked
              when Radio
                agent.req.query.add field.name, field.value if field.checked
              when Submit
                agent.req.query.add field.name, field.value
              when File
                filedata = Web::Common::FileData.new(field.name,'application/octet-stream')
                filedata.path = field.path
                agent.req.query.add field.name, filedata
              when Hidden
                agent.req.query.add field.name, field.value
              when Image
                agent.req.query.add field.name, field.value
              when Textarea
                agent.req.query.add field.name, field.text
              when Select
                field.options.each {|o|
                  if o.checked
                    agent.req.query.add field.name, o.value
                  end
                }
              end
            }
            agent.get(agent.uri.to_s)
          end
        end

        class Text
          def initialize(name,value)
            @name = name
            @value = value
          end
          attr_accessor :name, :value
        end

        class Password
          def initialize(name,value)
            @name = name
            @value = value
          end
          attr_accessor :name, :value
        end

        class Checkbox
          def initialize(name, value, checked)
            @name = name
            @value = value
            @checked = checked
          end
          attr_accessor :name, :value, :checked
        end

        class Radio
          def initialize(name, value, checked)
            @name = name
            @value = value
            @checked = checked
          end
          attr_accessor :name, :value
        end

        class Submit
          def initialize(name,value)
            @name = name
            @value = value
          end
          attr_accessor :name, :value
        end

        class File
          def initialize(name,path=nil)
            @name = name
            @path = nil
          end
          attr_accessor :name, :path
        end

        class Hidden
          def initialize(name,value)
            @name = name
            @value = value
          end
          attr_accessor :name, :value
        end

        class Image
          def initialize(name,value)
            @name = name
            @value = value
          end
          attr_accessor :name, :value
        end

        class Textarea
          def initialize(name, text='')
            @name = name
            @text = text
          end
          attr_accessor :name,:text
        end

        class Select
          def initialize(name, multiple=false, options=[])
            @name     = name
            @multiple = multiple
            @options  = options
          end
          attr_accessor :name, :multiple, :options
          class Option
            def initialize(value, selected)
              @value    = value
              @selected = selected
            end
          end
        end
      end # Form
    end # Forms
  end # Agent
end # Web
