class Ridgepole::ForeignKey
  class << self
    def init
      require 'foreigner'

      ActiveSupport.on_load :active_record do
        Foreigner.load
      end

      Ridgepole::DSLParser::Context.include_module(Ridgepole::ForeignKey::DSL)
    end

    def check_orphan_foreign_key(definition)
      definition.each do |table_name, attrs|
        if attrs[:foreign_keys] and not attrs[:definition]
          raise "Table `#{table_name}` to create the foreign key is not defined: #{attrs[:foreign_keys].keys.join(',')}"
        end
      end
    end

    def scan_foreign_keys_change(from, to, table_delta, options)
      from = (from || {}).dup
      to = (to || {}).dup
      foreign_keys_delta = {}

      to.each do |foreign_key_name, to_attrs|
        from_attrs = from.delete(foreign_key_name)

        if from_attrs
          if from_attrs != to_attrs
            foreign_keys_delta[:add] ||= {}
            foreign_keys_delta[:add][foreign_key_name] = to_attrs

            unless options[:merge]
              foreign_keys_delta[:delete] ||= {}
              foreign_keys_delta[:delete][foreign_key_name] = from_attrs
            end
          end
        else
          foreign_keys_delta[:add] ||= {}
          foreign_keys_delta[:add][foreign_key_name] = to_attrs
        end
      end

      unless options[:merge]
        from.each do |foreign_key_name, from_attrs|
          foreign_keys_delta[:delete] ||= {}
          foreign_keys_delta[:delete][foreign_key_name] = from_attrs
        end
      end

      unless foreign_keys_delta.empty?
        table_delta[:foreign_keys] = foreign_keys_delta
      end
    end

    def append_change_foreign_keys(table_name, delta, buf, options)
      (delta[:delete] || {}).each do |foreign_key_name, attrs|
        append_remove_foreign_key(table_name, foreign_key_name, attrs, buf, options)
      end

      (delta[:add] || {}).each do |foreign_key_name, attrs|
        append_add_foreign_key(table_name, foreign_key_name, attrs, buf, options)
      end
    end

    def append_add_foreign_key(table_name, foreign_key_name, attrs, buf, options)
      to_table = attrs.fetch(:to_table)
      attrs_options = attrs[:options] || {}

      if options[:bulk_change]
        buf.puts(<<-EOS)
  t.foreign_key(#{to_table.inspect}, #{attrs_options.inspect})
        EOS
      else
        buf.puts(<<-EOS)
add_foreign_key(#{table_name.inspect}, #{to_table.inspect}, #{attrs_options.inspect})
        EOS
      end
    end

    def append_remove_foreign_key(table_name, foreign_key_name, attrs, buf, options)
      attrs_options = attrs[:options] || {}
      target = {:name => attrs_options.fetch(:name)}

      if options[:bulk_change]
        buf.puts(<<-EOS)
  t.remove_foreign_key(#{target.inspect})
        EOS
      else
        buf.puts(<<-EOS)
remove_foreign_key(#{table_name.inspect}, #{target.inspect})
        EOS
      end
    end
  end # of class methods

  module DSL
    def add_foreign_key(from_table, to_table, options = {})
      unless options[:name]
        raise "Foreign key name in `#{from_table}` is undefined"
      end

      from_table = from_table.to_s
      to_table = to_table.to_s
      options[:name] = options[:name].to_s
      @__definition[from_table] ||= {}
      @__definition[from_table][:foreign_keys] ||= {}
      idx = options[:name]

      if @__definition[from_table][:foreign_keys][idx]
        raise "Foreign Key `#{from_table}(#{idx})` already defined"
      end

      @__definition[from_table][:foreign_keys][idx] = {
        :to_table => to_table,
        :options => options,
      }
    end
  end
end