module ActiveRecord::Turntable::Migration
  extend ActiveSupport::Concern

  # AR < 3.1
  def self.extended(base)
    class << base
      def announce_with_turntable(message)
        announce_without_turntable("#{message} - #{get_current_shard}")
      end

      alias_method_chain :migrate, :turntable
      alias_method_chain :announce, :turntable
      include ShardDefinition
    end
    base.class_eval do
      class_inheritable_accessor :target_shards
    end
    ::ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:include, SchemaStatementsExt)
  end

  # AR >= 3.1
  included do
    extend ShardDefinition
    class_attribute :target_shards
    def announce_with_turntable(message)
      announce_without_turntable("#{message} - #{get_current_shard}")
    end

    alias_method_chain :migrate, :turntable
    alias_method_chain :announce, :turntable
    ::ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:include, SchemaStatementsExt)
    ::ActiveRecord::Migration::CommandRecorder.send(:include, CommandRecorder)
  end

  # for all
  module ShardDefinition
    def clusters(*cluster_names)
      config = ActiveRecord::Base.turntable_config
      (self.target_shards ||= []) <<
        if cluster_names.first == :all
          config['clusters'].map do |name, cluster_conf|
            cluster_conf["shards"].map {|shard| shard["connection"]}
          end
        else
          cluster_names.map do |cluster_name|
            config['clusters'][cluster_name]["shards"].map do |shard|
              shard["connection"]
            end
          end
        end
    end

    def shards(*connection_names)
      (self.target_shards ||= []) << connection_names
    end
  end

  def get_current_shard
    "Shard: #{@@current_shard}" if @@current_shard
  end

  def migrate_with_turntable(direction)
    config = ActiveRecord::Base.configurations
    @@current_shard = nil
    shards = (self.class.target_shards||=[]).flatten.uniq.compact
    if self.class.target_shards.blank?
      return migrate_without_turntable(direction)
    end

    shards_conf = shards.map do |shard|
      config[Rails.env||"development"]["shards"][shard]
    end
    seqs = config[Rails.env||"development"]["seq"]
    shards_conf += seqs.values
    shards_conf << config[Rails.env||"development"]
    shards_conf.each_with_index do |conf, idx|
      @@current_shard = (shards[idx] || seqs.keys[idx - shards.size] || "master")
      ActiveRecord::Base.establish_connection(conf)
      if !ActiveRecord::Base.connection.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name())
        ActiveRecord::Base.connection.initialize_schema_migrations_table
      end
      migrate_without_turntable(direction)
    end
  end

  module SchemaStatementsExt
    def create_sequence_for(table_name, options = { })
      # TODO: pkname should be pulled from table definitions
      pkname = "id"
      sequence_table_name = ActiveRecord::Turntable::Sequencer.sequence_name(table_name, "id")
      create_table(sequence_table_name, options)
      execute "ALTER TABLE #{quote_table_name(sequence_table_name)} MODIFY id bigint(20) DEFAULT NULL auto_increment NOT NULL;"
      execute "INSERT INTO #{quote_table_name(sequence_table_name)} (`id`) VALUES (0)"
    end

    def drop_sequence_for(table_name, options = { })
      # TODO: pkname should be pulled from table definitions
      pkname = "id"
      sequence_table_name = ActiveRecord::Turntable::Sequencer.sequence_name(table_name, "id")
      drop_table(sequence_table_name)
    end

    def rename_sequence_for(table_name, new_name)
      # TODO: pkname should pulled from table definitions
      seq_table_name = ActiveRecord::Turntable::Sequencer.sequence_name(table_name, "id")
      new_seq_name = ActiveRecord::Turntable::Sequencer.sequence_name(new_name, "id")
      rename_table(seq_table_name, new_seq_name)
    end
  end

  module CommandRecorder
    def create_sequence_for(*args)
      record(:create_sequence_for, args)
    end

    def rename_sequence_for(*args)
      record(:rename_sequence_for, args)
    end

    private

    def invert_create_sequence_for(args)
      [:drop_sequence_for, args]
    end

    def invert_rename_sequence_for(args)
      [:rename_sequence_for, args.reverse]
    end
  end

end

