# The base class for all API objects that can be converted to XML.
# The class provides basic functionality to convert objects to and from xml based
# on the default Caren API format.
class Caren::Base

  attr_accessor :attributes, :original_xml

  # Basic initializer, handles quick setting of passed attributes.
  def initialize args={}, xml=""
    self.original_xml = xml
    self.attributes = {}
    self.class.keys.each do |key|
      if args.has_key?(key)
        self.attributes[key] = args[key]
      elsif args.has_key?(key.to_s)
        self.attributes[key] = args[key.to_s]
      else
        self.attributes[key] = nil
      end
    end
    # We want to use #id and #type (in Ruby 1.8.x we need to undef it)
    self.instance_eval('undef id') if self.respond_to?(:id)
    self.instance_eval('undef type') if self.respond_to?(:type)
  end

  # List of available attributes for this object
  def self.keys
    [ :updated_at, :created_at, :action ]
  end

  # Root name of the XML array of objects
  def self.array_root
    :objects
  end

  # Name of each XML if converted to XML
  def self.node_root
    :object
  end
  
  # Override this for instance specific node roots
  def node_root
    nil
  end

  # The relative location of this resource.
  def self.resource_location
    raise "No resource location found"
  end

  # Convert an array of these objects to XML
  def self.to_xml array
    array.to_xml( :root => self.array_root )
  end

  # this method is called for each parsed object. So subclasses can add extra parsing later on.
  def self.init_dependent_objects object
    object
  end

  # Convert a XML string to a single object or an array of objects
  def self.from_xml xml
    return nil if xml == ''
    hash = xml.is_a?(Hash) ? xml : Hash.from_xml(xml)
    raise Caren::Exceptions::InvalidXmlResponse unless hash
    if hash.has_key?(self.array_root.to_s)
      return hash[self.array_root.to_s].map{ |h| init_dependent_objects(self.from_xml(h)) }
    elsif hash.has_key?(self.node_root.to_s)
      return init_dependent_objects(self.new( hash[self.node_root.to_s], xml ))
    else
      return init_dependent_objects(self.new( hash, xml ))
    end
  end

  # Convert this object to XML
  def to_xml options={}
    self.as_xml.to_xml(options.merge( :root => (self.node_root || self.class.node_root) ))
  end

  # Overridable hash of attributes that is used for converting to XML.
  def as_xml
    attributes.reject{ |k,v| k == :action }
  end

  # The absolute (constructed url) to the resource.
  def self.resource_url id=nil
    [self.resource_location,id].compact.join("/")
  end

  def self.search_url key, value
    "#{self.resource_url}?key=#{key.to_s.dasherize}&value=#{value}"
  end

  # The absolute (constructed url) to the resource.
  def resource_url id=nil
    self.class.resource_url(id)
  end

  def self.hash_from_image hash_or_path
    return hash_or_path if hash_or_path.is_a?(Hash)
    { :name => File.basename(hash_or_path),
      :content => Base64.encode64(File.open(hash_or_path).read),
      :content_type => `file -b --mime-type #{hash_or_path}`.gsub(/\n/,"").split(";")[0] }
  end

  private

  # Method missing calls to enable getters/setters
  def method_missing args, value=nil
    return self.attributes[args] if self.class.keys.include?(args)
    return self.attributes[args.to_s.gsub('=','').to_sym] = value if self.class.keys.include?(args.to_s.gsub('=','').to_sym)
    super
  end

end