class Quickbooks::API
  include Quickbooks::Logger
  include Quickbooks::Config
  include Quickbooks::Support::Inflection

  attr_reader :dtd_parser, :qbxml_parser, :schema_type
  private_class_method :new
  @@instances = {}

  def initialize(schema_type = nil, opts = {})
    self.class.check_schema_type!(schema_type)
    @schema_type = schema_type

    @dtd_parser = Quickbooks::DtdParser.new(schema_type)
    @qbxml_parser = Quickbooks::QbxmlParser.new(schema_type)

    load_qb_classes
    @@instances[schema_type] = self
  end

  # simple singleton constructor without caching support
  #
  def self.[](schema_type)
    @@instances[schema_type] || new(schema_type)
  end

  # full singleton constructor
  #
  def self.instance(schema_type = nil, opts = {})
    @@instances[schema_type] || new(schema_type, opts)
  end

  # user friendly api decorators. Not used anywhere else.
  # 
  def container
    container_class
  end

  def qbxml_classes
    cached_classes
  end

  # api introspection
  #
  def find(class_name)
    cached_classes.find { |c| underscore(c) == class_name.to_s }
  end

  def grep(pattern)
    cached_classes.select { |c| underscore(c).match(/#{pattern}/) }
  end

  # QBXML 2 RUBY

  def qbxml_to_obj(qbxml)
    qbxml_parser.parse(qbxml)
  end

  def qbxml_to_hash(qbxml, include_container = false)
    if include_container
      qbxml_to_obj(qbxml).attributes
    else
      qbxml_to_obj(qbxml).inner_attributes
    end
  end

  # RUBY 2 QBXML

  def hash_to_obj(data)
    key, value = data.detect { |name, value| name != 'xml_attributes' && name != :xml_attributes }
    key_path = container_class.template(true).path_to_nested_key(key.to_s)
    raise(RuntimeError, "#{key} class not found in api template") unless key_path

    wrapped_data = Hash.nest(key_path, value)
    container_class.new(wrapped_data)
  end

  def hash_to_qbxml(data)
    hash_to_obj(data).to_qbxml
  end

private 

  def load_qb_classes
    rebuild_schema_cache(false)
    load_full_container_template
    container_class
  end

  # rebuilds schema cache in memory
  #
  def rebuild_schema_cache(force = false)
    dtd_parser.parse_file(dtd_file) if (cached_classes.empty? || force)
  end

  # load the recursive container class template into memory (significantly
  # speeds up wrapping of partial data hashes)
  # 
  def load_full_container_template(use_disk_cache = false)
      container_class.template(true)
  end

end