module ActiveRecord
  module SchemaDumperSupport
    def trailer(stream)
      triggers = mount_trigger_schema.join("")

      if triggers.present?
        if(@dump.respond_to?(:final))
          @dump.final << triggers
        else
          stream.puts triggers
        end
      end

      super(stream)
    end

    private

    def mount_trigger_schema
      triggers = []

      discovery_triggers.each do |trigger|
        table_name = trigger["event_object_table"]
        trigger_name = trigger["trigger_name"].gsub("_#{table_name}_trigger", '')

        event_manipulation = discovery_event(trigger['trigger_name']).to_json
        event_manipulation = "#{trigger['action_timing'].downcase}: #{event_manipulation}"

        procedure = discovery_trigger_method(table_name, trigger_name)

        declare = parse_trigger_declarations(procedure["definition"])
        declare = ", declare: #{declare}" if declare

        triggers << "\n"
        triggers << "\tcreate_trigger \"#{table_name}\", \"#{trigger_name}\", #{event_manipulation} #{declare} do\n"
        triggers << "<<-TRIGGERBODY\n"
        triggers << parse_trigger_body(procedure["definition"])
        triggers << "\nTRIGGERBODY\n"
        triggers << "\tend # create_trigger"
        triggers << "\n"
      end

      triggers
    end

    def discovery_triggers
      sql = <<-DETECTTRIGGER
        SELECT DISTINCT trigger_name, event_object_table, action_timing
        FROM information_schema.triggers
        WHERE trigger_schema = current_schema();
      DETECTTRIGGER

      @connection.execute(sql)
    end

    def discovery_event(trigger_name)
      sql = <<-DETECTTRIGGER
        SELECT DISTINCT event_manipulation
        FROM information_schema.triggers
        WHERE trigger_schema = current_schema()
          AND trigger_name = '#{trigger_name}'
      DETECTTRIGGER

      @connection.execute(sql).pluck("event_manipulation")
    end

    def parse_trigger_body(definition)
      procedure_body = definition[definition.index("BEGIN")+5..definition.size]
      procedure_body[0..procedure_body.rindex("END") -1].strip
    end

    def parse_trigger_declarations(definition)
      declare = definition[definition.index("DECLARE")+7..definition.size]
      declare = declare[0, declare.index("BEGIN")].strip.split(";")
      declare = declare.map{ |variable|
        var = variable.strip
        var = var.split(" ")
        { var.shift => var.join(" ") }
      }
    end

    def discovery_trigger_method(table_name, trigger_name)
      sql = <<-DISCOVERYTRIGGERMETHOD
            SELECT
                n.nspname AS schema,
                proname AS fname,
                proargnames AS args,
                t.typname AS return_type,
                d.description,
                pg_get_functiondef(p.oid) as definition
            FROM pg_proc p
            JOIN pg_type t
              ON p.prorettype = t.oid
            LEFT OUTER
            JOIN pg_description d
              ON p.oid = d.objoid
            LEFT OUTER
            JOIN pg_namespace n
              ON n.oid = p.pronamespace
           WHERE n.nspname = current_schema()
             AND proname = '#{Pgtrigger::Utils.build_trigger_name(table_name, trigger_name)}_func'
      DISCOVERYTRIGGERMETHOD
      @connection.execute(sql).first
    end
  end

  class SchemaDumper
    prepend SchemaDumperSupport
  end
end