class RecordInheritance

  def self.merge(json, opts = {})
    self.new.merge(json, opts)
  end


  def self.has_type?(type)
    self.new.has_type?(type)
  end


  # Add our inheritance-specific definitions to relevant JSONModel schemas
  def self.prepare_schemas
    get_config.each do |record_type, config|
      config[:inherited_fields].map {|fld| fld[:property]}.uniq.each do |property|
        schema_def = {
          'type' => 'object',
          'subtype' => 'ref',
          'properties' => {
            # Not a great type for a ref, but in this context we don't really
            # know for sure what type the ancestor might be.  Might need to
            # think harder about this if it causes problems.
            'ref' => {'type' => 'string'},
            'level' => {'type' => 'string'},
            'direct' => {'type' => 'boolean'},
          }
        }

        properties = JSONModel::JSONModel(record_type).schema['properties']
        if properties[property]['type'].include?('object')
          add_inline_inheritance_field(properties[property], schema_def)

        elsif properties[property]['type'] == 'array'
          extract_referenced_types(properties[property]['items']).each do |item_type|
            if item_type['type'].include?('object')
              add_inline_inheritance_field(item_type, schema_def)
            else
              $stderr.puts("Inheritence metadata for string arrays is not currently supported (record type: #{record_type}; property: #{property}).  Please file a bug if you need this!")
            end
          end
        else
          # We add a new property alongside
          properties["#{property}_inherited"] = schema_def
        end
      end
    end
  end


  # Extract a list elements like {'type' => 'mytype'} from the various forms
  # JSON schemas allow types to be in.  For example:
  #
  # {"type" => "JSONModel(:resource) uri"}
  #
  # {"type" => [{"type" => "JSONModel(:resource) uri"},
  #             {"type" => "JSONModel(:archival_object) uri"}]}
  #
  def self.extract_referenced_types(typedef)
    if typedef.is_a?(Array)
      typedef.map {|elt| extract_referenced_types(elt)}.flatten
    elsif typedef.is_a?(Hash)
      if typedef['type'].is_a?(String)
        [typedef]
      else
        extract_referenced_types(typedef['type'])
      end
    else
      $stderr.puts("Unrecognized type: #{typedef.inspect}")
      []
    end
  end

  def self.add_inline_inheritance_field(target, schema_def)
    schema = nil

    if target.has_key?('properties')
      schema = target['properties']
    elsif target['type'] =~ /JSONModel\(:(.*?)\) object/
      referenced_jsonmodel = $1.intern
      schema = JSONModel::JSONModel(referenced_jsonmodel).schema['properties']
    end

    if schema
      schema['_inherited'] = schema_def
    else
      $stderr.puts("Inheritence metadata for string arrays is not currently supported (property was: #{property}).  Please file a bug if you need this!")
    end
  end

  def self.get_config
    (AppConfig.has_key?(:record_inheritance) ? AppConfig[:record_inheritance] : {})
  end

  def initialize(config = nil)
    @config = config || self.class.get_config
  end


  def merge(jsons, opts = {})
    return merge_record(jsons, opts) unless jsons.is_a? Array

    jsons.map do |json|
      merge_record(json, opts)
    end
  end


  def has_type?(type)
    @config.has_key?(type.intern)
  end


  private


  def merge_record(json_in, opts)
    json = json_in.clone

    direct_only = opts.fetch(:direct_only) { false }
    remove_ancestors = opts.fetch(:remove_ancestors) { false }
    config = @config.fetch(json['jsonmodel_type'].intern) { false }

    if config
      config[:inherited_fields].each do |fld|
        next if direct_only && !fld[:inherit_directly]

        next if fld.has_key?(:skip_if) && fld[:skip_if].call(json)

        val = json[fld[:property]]

        if val.is_a?(Array)
          if val.empty? || fld.has_key?(:inherit_if) && fld[:inherit_if].call(val).empty?
            json['ancestors'].map {|ancestor|
              ancestor_val = fld.has_key?(:inherit_if) ? fld[:inherit_if].call(ancestor['_resolved'][fld[:property]])
                                                       : ancestor['_resolved'][fld[:property]]
              unless ancestor_val.empty?
                json[fld[:property]] = json[fld[:property]] + ancestor_val
                json[fld[:property]].flatten!
                json = apply_inheritance_properties(json, ancestor_val, ancestor, fld)
                break
              end
            }
          end
        else
          if !json[fld[:property]] || fld.has_key?(:inherit_if) && !fld[:inherit_if].call(json[fld[:property]])
            json['ancestors'].map {|ancestor|
              ancestor_val = ancestor['_resolved'][fld[:property]]
              if ancestor_val
                json[fld[:property]] = ancestor_val
                json = apply_inheritance_properties(json, ancestor_val, ancestor, fld)
                break
              end
            }
          end
        end
      end

      # composite identifer
      if config.has_key?(:composite_identifiers) && !direct_only
        ids = []
        json['ancestors'].reverse.each do |ancestor|
          if ancestor['_resolved']['component_id']
            id = ancestor['_resolved']['component_id']
            if config[:composite_identifiers][:include_level]
              id = [translate_level(ancestor['level']), id].join(' ')
            end
            ids << id
          elsif ancestor['_resolved']['id_0']
            ids << (0..3).map { |i| ancestor['_resolved']["id_#{i}"] }.compact.
              join(config[:composite_identifiers].fetch(:identifier_delimiter, ' '))
          end
        end

        # include our own id in the composite if it wasn't inherited
        if json['component_id'] && !json['component_id_inherited']
          id = json['component_id']
          if config[:composite_identifiers][:include_level]
            id = [translate_level(json['level']), id].join(' ')
          end
          ids << id
        end

        delimiter = config[:composite_identifiers].fetch(:identifier_delimiter, ' ')
        delimiter += ' ' if delimiter != ' ' && config[:composite_identifiers][:include_level]
        json['_composite_identifier'] = ids.join(delimiter)
      end
    end

    json['ancestors'] = [] if remove_ancestors
    json
  end


  def apply_inheritance_properties(json, vals, ancestor, field_config)
    props = {
      'ref' => ancestor['ref'],
      'level' => translate_level(ancestor['level']),
      'direct' => field_config[:inherit_directly]
    }

    ASUtils.wrap(vals).each do |val|
      if val.is_a?(Hash)
        val['_inherited'] = props
      else
        json["#{field_config[:property]}_inherited"] = props
      end
    end
    json
  end


  def translate_level(level)
    I18n.t("enumerations.archival_record_level.#{level}", :default => level)
  end

end
