# This class can be used to make SQL-dumps of databases, tables or however you want it.
class Baza::Dump
  # Constructor.
  #===Examples
  # dump = Baza::Dump.new(:db => db)
  def initialize(args)
    @db = args.fetch(:db)
    @debug = args[:debug]
    @tables = args[:tables]
  end

  # Method used to update the status.
  def update_status
    return nil unless @on_status
    rows_count = Knj::Locales.number_out(@rows_count, 0)
    rows_count_total = Knj::Locales.number_out(@rows_count_total, 0)
    percent = (@rows_count.to_f / @rows_count_total.to_f) * 100
    percent_text = Knj::Locales.number_out(percent, 1)
    @on_status.call(text: "Dumping table: '#{@table_obj.name}' (#{rows_count}/#{rows_count_total} - #{percent_text}%).")
  end

  # Dumps all tables into the given IO.
  def dump(io)
    debug "Going through tables."
    @rows_count = 0

    if @tables
      tables = @tables
    else
      tables = @db.tables.list
    end

    if @on_status
      @on_status.call(text: "Preparing.")

      @rows_count_total = 0
      tables.each do |table_obj|
        @rows_count_total += table_obj.rows_count
      end
    end

    tables.each do |table_obj|
      table_obj = @db.tables[table_obj] if table_obj.is_a?(String) || table_obj.is_a?(Symbol)
      next if table_obj.native?

      # Figure out keys.
      @keys = []
      table_obj.columns do |col|
        @keys << col.name
      end

      @table_obj = table_obj
      update_status
      debug "Dumping table: '#{table_obj.name}'."
      dump_table(io, table_obj)
    end
  end

  # A block can be executed when a new status occurs.
  def on_status(&block)
    @on_status = block
  end

  def debug(message)
    puts message if @debug
  end

  # Dumps the given table into the given IO.
  def dump_table(io, table_obj)
    create_data = table_obj.data
    create_data.delete(:name)

    # Get SQL for creating table and add it to IO.
    sqls = @db.tables.create(table_obj.name, create_data, return_sql: true)
    sqls.each do |sql|
      io.write("#{sql};\n")
    end


    # Try to find a primary column in the table.
    prim_col = nil
    table_obj.columns do |col|
      if col.primarykey?
        prim_col = col
        break
      end
    end


    debug "Dumping data for table: #{table_obj.name}"

    # Set up rows and way to fill rows.
    rows = []


    @db.select(table_obj.name, nil, unbuffered: true) do |row|
      rows << row.values
      @rows_count += 1

      if rows.length >= 1000
        update_status
        dump_insert_multi(io, table_obj, rows)
      end
    end


    # Dump the last rows if any.
    dump_insert_multi(io, table_obj, rows) unless rows.empty?
  end

  # Dumps the given rows from the given table into the given IO.
  def dump_insert_multi(io, table_obj, rows)
    debug "Inserting #{rows.length} into #{table_obj.name}."
    sqls = @db.insert_multi(table_obj.name, rows, return_sql: true, keys: @keys)
    sqls.each do |sql|
      io.write("#{sql};\n")
    end

    rows.clear

    # Ensure garbage collection or we might start using A LOT of memory.
    GC.start
  end
end