module Groonga
  module Command
    class Grndb
      def initialize(argv)
        @command, *@arguments = argv
        @succeeded = true
        @executed = false
        @database_path = nil
      end

      def run
        slop = create_slop
        rest = nil
        begin
          rest = slop.parse(@arguments)
        rescue Slop::Error
          $stderr.puts($!.message)
          return false
        end

        if slop.help?
          $stdout.puts(slop.help)
          return true
        end

        unless @executed
          if rest.empty?
            $stderr.puts("No command is specified.")
          else
            $stderr.puts("Unknown command: <#{rest.first}>")
          end
          return false
        end

        @succeeded
      end

      private
      def create_slop
        slop = Slop.new
        command_name = File.basename(@command)
        slop.banner = "Usage: #{command_name} COMMAND [OPTIONS] DB_PATH"
        slop_enable_help(slop)

        slop.command "check" do |command|
          command.description "Check database"
          slop_enable_help(command)

          command.run do |options, arguments|
            run_command(options, arguments) do |database, new_arguments|
              check(database, options, new_arguments)
            end
          end
        end

        slop.command "recover" do |command|
          command.description "Recover database"
          slop_enable_help(command)

          command.run do |options, arguments|
            run_command(options, arguments) do |database, new_arguments|
              recover(database, options, new_arguments)
            end
          end
        end

        slop
      end

      def slop_enable_help(slop)
        slop.on("-h", "--help", "Display this help message.", :tail => true)
      end

      def open_database(arguments)
        if arguments.empty?
          $stderr.puts("Database path is missing")
          @succeesed = false
          return
        end

        database = nil
        @database_path, *rest_arguments = arguments
        begin
          database = Database.open(@database_path)
        rescue Error => error
          $stderr.puts("Failed to open database: <#{@database_path}>")
          $stderr.puts(error.message)
          @succeeded = false
          return
        end

        begin
          yield(database, rest_arguments)
        ensure
          database.close
        end
      end

      def run_command(options, arguments)
        @executed = true

        if options.help?
          $stdout.puts(options.help)
          return
        end

        open_database(arguments) do |database|
          yield(database)
        end
      end

      def recover(database, options, arguments)
        begin
          database.recover
        rescue Error => error
          $stderr.puts("Failed to recover database: <#{@database_path}>")
          $stderr.puts(error.message)
          @succeeded = false
        end
      end

      def check(database, options, arguments)
        if database.locked?
          message =
            "Database is locked. " +
            "It may be broken. " +
            "Re-create the database."
          $stdout.puts(message)
          @succeeded = false
        end

        database.each do |object|
          case object
          when IndexColumn
            next unless object.locked?
            message =
              "[#{object.name}] Index column is locked. " +
              "It may be broken. " +
              "Re-create index by '#{@command} recover #{@database_path}'."
            $stdout.puts(message)
            @succeeded = false
          when Column
            next unless object.locked?
            name = object.name
            message =
              "[#{name}] Data column is locked. " +
              "It may be broken. " +
              "(1) Truncate the column (truncate #{name}) or " +
              "clear lock of the column (lock_clear #{name}) " +
              "and (2) load data again."
            $stdout.puts(message)
            @succeeded = false
          when Table
            next unless object.locked?
            name = object.name
            message =
              "[#{name}] Table is locked. " +
              "It may be broken. " +
              "(1) Truncate the table (truncate #{name}) or " +
              "clear lock of the table (lock_clear #{name}) " +
              "and (2) load data again."
            $stdout.puts(message)
            @succeeded = false
          end
        end
      end
    end
  end
end
