# SOAP4R - WSDL literal mapping registry.
# Copyright (C) 2000-2007  NAKAMURA, Hiroshi <nahi@ruby-lang.org>.

# This program is copyrighted free software by NAKAMURA, Hiroshi.  You can
# redistribute it and/or modify it under the same terms of Ruby's license;
# either the dual license version in 2003, or any later version.


require 'soap/baseData'
require 'soap/mapping/mapping'
require 'soap/mapping/literalregistry'
require 'soap/mapping/typeMap'
require 'xsd/codegen/gensupport'
require 'xsd/namedelements'


module SOAP
module Mapping


class WSDLLiteralRegistry < LiteralRegistry
  attr_reader :definedelements
  attr_reader :definedtypes

  def initialize(definedtypes = XSD::NamedElements::Empty,
      definedelements = XSD::NamedElements::Empty)
    super()
    @definedtypes = definedtypes
    @definedelements = definedelements
  end

  def obj2soap(obj, qname, obj_class = nil)
    soap_obj = nil
    if obj.is_a?(SOAPElement)
      soap_obj = obj
    elsif eledef = @definedelements[qname]
      soap_obj = obj2elesoap(obj, eledef)
    elsif type = @definedtypes[qname]
      soap_obj = obj2typesoap(obj, type)
    else
      soap_obj = any2soap(obj, qname, obj_class)
    end
    return soap_obj if soap_obj
    if @excn_handler_obj2soap
      soap_obj = @excn_handler_obj2soap.call(obj) { |yield_obj|
        Mapping.obj2soap(yield_obj, nil, nil, MAPPING_OPT)
      }
      return soap_obj if soap_obj
    end
    raise MappingError.new("cannot map #{obj.class.name} as #{qname}")
  end

  # node should be a SOAPElement
  def soap2obj(node, obj_class = nil)
    cause = nil
    begin
      return any2obj(node, obj_class)
    rescue MappingError
      cause = $!
    end
    if @excn_handler_soap2obj
      begin
        return @excn_handler_soap2obj.call(node) { |yield_node|
	    Mapping.soap2obj(yield_node, nil, nil, MAPPING_OPT)
	  }
      rescue Exception
      end
    end
    if node.respond_to?(:type)
      raise MappingError.new("cannot map #{node.type.name} to Ruby object", cause)
    else
      raise MappingError.new("cannot map #{node.elename.name} to Ruby object", cause)
    end
  end

private

  def obj2elesoap(obj, eledef)
    ele = nil
    qualified = (eledef.elementform == 'qualified')
    if obj.is_a?(SOAPNil)
      ele = obj
    elsif eledef.type
      if type = @definedtypes[eledef.type]
        ele = obj2typesoap(obj, type)
      elsif type = TypeMap[eledef.type]
        ele = base2soap(obj, type)
      else
        raise MappingError.new("cannot find type #{eledef.type}")
      end
    elsif eledef.local_complextype
      ele = obj2typesoap(obj, eledef.local_complextype)
    elsif eledef.local_simpletype
      ele = obj2typesoap(obj, eledef.local_simpletype)
    else
      raise MappingError.new('illegal schema?')
    end
    ele.elename = eledef.name
    ele.qualified = qualified
    ele
  end

  def obj2typesoap(obj, type)
    ele = nil
    if type.is_a?(::WSDL::XMLSchema::SimpleType)
      ele = simpleobj2soap(obj, type)
    else # complexType
      if type.simplecontent
        ele = simpleobj2soap(obj, type.simplecontent)
      else
        ele = complexobj2soap(obj, type)
      end
      ele.type = type.name
      if type.base or Mapping.root_type_hint
        Mapping.reset_root_type_hint
        ele.force_typed = true
      end
      add_definedattributes2soap(obj, ele, type)
    end
    ele
  end

  def simpleobj2soap(obj, type)
    type.check_lexical_format(obj)
    return SOAPNil.new if obj.nil?
    if type.base
      ele = base2soap(obj, TypeMap[type.base])
    elsif type.list
      value = obj.is_a?(Array) ? obj.join(" ") : obj.to_s
      ele = base2soap(value, SOAP::SOAPString)
    else
      raise MappingError.new("unsupported simpleType: #{type}")
    end
    ele
  end

  def complexobj2soap(obj, type)
    ele = SOAPElement.new(type.name)
    complexobj2sequencesoap(obj, ele, type, type.choice?, type.choice?)
    ele
  end

  def complexobj2sequencesoap(obj, soap, type, nillable, is_choice)
    added = false
    type.elements.each do |child_ele|
      case child_ele
      when WSDL::XMLSchema::Any
        any = Mapping.get_attributes_for_any(obj)
        SOAPElement.from_objs(any).each do |child|
          soap.add(child)
        end
        ele_added = true
      when WSDL::XMLSchema::Element
        ele_added = complexobj2soapchildren(obj, soap, child_ele, nillable)
      when WSDL::XMLSchema::Sequence
        ele_added = complexobj2sequencesoap(obj, soap, child_ele, nillable, false)
      when WSDL::XMLSchema::Choice
        ele_added = complexobj2sequencesoap(obj, soap, child_ele, true, true)
      else
        raise MappingError.new("unknown type: #{child_ele}")
      end
      added = true if ele_added
      break if is_choice and ele_added
    end
    added
  end

  def complexobj2soapchildren(obj, soap, child_ele, nillable = false)
    if child_ele.map_as_array?
      complexobj2soapchildren_array(obj, soap, child_ele, nillable)
    else
      complexobj2soapchildren_single(obj, soap, child_ele, nillable)
    end
  end

  def complexobj2soapchildren_array(obj, soap, child_ele, nillable)
    child = Mapping.get_attribute(obj, child_ele.name.name)
    if child.nil? and obj.is_a?(::Array)
      child = obj
    end
    if child.nil?
      return false if nillable
      if child_soap = nil2soap(child_ele)
        soap.add(child_soap)
        return true
      else
        return false
      end
    end
    unless child.respond_to?(:each)
      return false
    end
    child.each do |item|
      if item.is_a?(SOAPElement)
        soap.add(item)
      else
        child_soap = obj2elesoap(item, child_ele)
        soap.add(child_soap)
      end
    end
    true
  end

  def complexobj2soapchildren_single(obj, soap, child_ele, nillable)
    child = Mapping.get_attribute(obj, child_ele.name.name)
    case child
    when NilClass
      return false if nillable
      if child_soap = nil2soap(child_ele)
        soap.add(child_soap)
        true
      else
        false
      end
    when SOAPElement
      soap.add(child)
      true
    else
      child_soap = obj2elesoap(child, child_ele)
      soap.add(child_soap)
      true
    end
  end

  def nil2soap(ele)
    if ele.nillable
      obj2elesoap(nil, ele)     # add an empty element
    elsif ele.minoccurs == 0
      nil       # intends no element
    else
      warn("nil not allowed: #{ele.name.name}")
      nil
    end
  end

  def add_definedattributes2soap(obj, ele, typedef)
    if typedef.attributes
      typedef.attributes.each do |at|
        value = get_xmlattr_value(obj, at.name)
        ele.extraattr[at.name] = value unless value.nil?
      end
    end
  end
end


end
end
