require "pump/encoder"
require 'active_support/core_ext/string/inflections'

module Pump
  class Xml < Pump::Encoder

    private

    def compile_string
      <<-EOV
        def encode_single(object, options)
          "#{Tag.new(root_name, {}, sub_tags, tag_options)}"
        end

        def encode_array(objects, options)
          "#{TagArray.new(root_name, {}, sub_tags, tag_options)}"
        end

        def encode_partial_single(object, options)
          field_hash = options[:fields]
          "#{Tag.new(root_name, {}, sub_tags(true), tag_options)}"
        end

        def encode_partial_array(objects, options)
          field_hash = options[:fields]
          "#{TagArray.new(root_name, {}, sub_tags(true), tag_options)}"
        end
      EOV
    end

    def sub_tags(partial=false)
      encoder_config.map do |config|
        build_tag(config, partial)
      end
    end

    def build_tag(config, partial, path=[])
      tag_name, method_name = config.keys.first, config.values.first
      attrs = config[:attributes]
      options = config.merge({:xml_key_style => encoder_options[:xml_key_style], :partial => partial, :path => path})
      if method_name.is_a?(Array)
        Tag.new(tag_name, attrs, method_name.map{|conf| build_tag(conf, partial, path.dup << tag_name) }, options)
      elsif config[:array]
        options.merge!(:array_method => method_name, :array_root => tag_name)
        child_root = config[:child_root] || tag_name.to_s.singularize
        tags = config[:array].map{|conf| build_tag(conf, partial, path.dup << tag_name) }
        TagArray.new(child_root, attrs, tags, options)
      else
        Tag.new(tag_name, attrs, Value.new(method_name), options)
      end
    end

    def tag_options
      {
        :instruct => add_instruct?, :extra_indent => encoder_options[:extra_indent],
        :array_root => encoder_options[:array_root],
        :xml_key_style => encoder_options[:xml_key_style]
      }
    end

    def add_instruct?
      encoder_options.has_key?(:instruct) ? encoder_options[:instruct] : true
    end

    VALID_CHAR = [
      0x9, 0xA, 0xD,
      (0x20..0xD7FF),
      (0xE000..0xFFFD),
      (0x10000..0x10FFFF)
    ]

    VALID_XML_CHARS = Regexp.new('\A['+
      VALID_CHAR.map { |item|
        case item
        when 0.class
          [item].pack('U').force_encoding('utf-8')
        when Range
          [item.first, '-'.ord, item.last].pack('UUU').force_encoding('utf-8')
        end
      }.join +
    ']*\Z')

    def remove_ilegal_chars(string)
      return string if !string.is_a?(String) || string =~ VALID_XML_CHARS
      out = ""
      string.chars.each do |c|
        case c.ord
        when *VALID_CHAR
          out << c
        end
      end
      out
    end
  end
end

require "pump/xml/tag"
require "pump/xml/value"
require "pump/xml/tag_array"