#
# == epcb/cli
# ExcelPettyCashBookコマンドラインツール用のExitStatusを定義。
#
# Revision:: $Id$
#
require 'cli'
require 'log'
require 'epcb/errors'

module CLI
  class ExitStatus
    # CSVデータのフォーマットが不正
    INVALID_CSV_DATA_FORMAT = 130
    # 生成するExcelPettyCashBookのバージョンが不正
    INVALID_WORKBOOK_VERSION = 140
  end
end #CLI#

module EPCB
  module CLI
    #
    # コマンドラインのサブコマンドに相当するクラス。
    #
    class Subcommand
      attr_reader :command, :klass
      # コンストラクタ。
      # === ARGS
      # command:: サブコマンド文字列
      # klass:: 実際に処理を行うクラス名
      def initialize(command, klass)
        @command = command
        @klass = klass
      end
    end #Subcommand#

    #
    # ExcelPettyCashBookコマンドラインツールに特化したクラスメソッドを持つクラス。
    #
    class Helper
      def initialize(option_holder)
        @option_holder = option_holder
      end

      # 処理にかかった時間を表示する。
      # === ARGS
      # enabled:: 時間を表示するかどうかのフラグ
      # stopwatch:: Stopwatchインスタンス
      def report_elapsed_time(enabled, stopwatch)
        if enabled
          stopwatch.suspend
          puts "Elapsed #{format("%0.3f", stopwatch.time)} (sec)"
        end
      end

      # --verboseや--debugのオプションを参照し、
      # 適切なLoggerクラスのインスタンスを生成する。
      # === ARGS
      # io:: ログ出力先のIOオブジェクト
      # === RETURNS
      # Loggerインスタンス
      def create_logger(io)
        logger = nil
        if @option_holder.debug
          logger = Log::DebugLogger.new(io)
          logger.level = Logger::DEBUG
        else
          logger = Log::Logger.new(io)
          if @option_holder.verbose
            logger.level = Logger::INFO
          else
            logger.level = Logger::WARN
          end
        end
        return logger
      end

      # --benchオプションを定義する。
      # === ARGS
      # opts:: オプション
      def define_bench(opts)
        opts.on("-b", "--bench",
                "report an elapsed time.") do |arg|
          @option_holder.bench = arg
        end
      end

      # --masterオプションを定義する。
      # === ARGS
      # opts:: オプション
      def define_master(opts)
        opts.on("-m", "--master",
                "output master sheet data.") do |arg|
          @option_holder.master = arg
        end
      end

      # --outfileオプションを定義する。
      # === ARGS
      # opts:: オプション
      def define_outfile(opts)
        opts.on("-o", "--outfile FILE", String,
                "specify a file name to be output.") do |arg|
          @option_holder.outfile = arg
        end
      end

      # --prefsオプションを定義する。
      # === ARGS
      # opts:: オプション
      def define_prefs(opts)
        opts.on("-p", "--prefs",
                "output preferences.") do |arg|
          @option_holder.prefs = arg
        end
      end

      # --source-versionオプションを定義する。
      # === ARGS
      # opts:: オプション
      def define_source_version(opts)
        opts.on("-S", "--source-version VERSION", String,
                "specify a source file version.") do |arg|
          @option_holder.source_version = arg
        end
      end

      # --templateオプションを定義する。
      # === ARGS
      # opts:: オプション
      def define_template(opts)
        opts.on("-t", "--template FILE", String,
                "specify a template file.") do |arg|
          @option_holder.template = arg
        end
      end

      # --verboseオプションを定義する。
      # === ARGS
      # opts:: オプション
      def define_verbose(opts)
        opts.on_tail("-V", "--verbose",
                     "enable verbose output.") do |arg|
          @option_holder.verbose = arg
        end
      end

      # --deubgオプションを定義する。
      # === ARGS
      # opts:: オプション
      def define_debug(opts)
        opts.on_tail("-D", "--debug",
                     "enable debug output.") do |arg|
          @option_holder.debug = arg
        end
      end

      # --versionオプションを定義する。
      # === ARGS
      # opts:: オプション
      # progname:: オプション指定時に表示するプログラム名
      # ver:: オプション指定時に表示するバージョン情報
      def define_version(opts, progname, ver)
        opts.on_tail("-v", "--version",
                     "print version information and exit.") do
          puts("#{progname} #{ver}")
          exit(0)
        end
      end

      # --helpオプションを定義する。
      # === ARGS
      # opts:: オプション
      def define_help(opts)
        opts.on_tail("-h", "--help", "print this message and exit.") do
          puts(opts)
          exit(0)
        end
      end

      # Usageを表示してCLI::ExitStatus::ARGUMENT_ERRORでexitする
      def print_usage_and_exit(status)
        ARGV.options { |opts| $stderr.puts(opts) }
        exit(status)
      end

      # 正しいサブコマンドが指定されているかをチェックする。
      # エラーになった場合は適切な終了ステータスでexitする。
      # エラーにならなかった場合はサブコマンドに対応する処理を行うオブジェクトを返す。
      # === ARGS
      # argv:: コマンドライン引数
      # subcommands:: 正しいサブコマンド(Subcommand)の配列
      # === RETURNS
      # サブコマンドに対応する処理実行オブジェクト
      def check_subcommand_and_get_program(argv, subcommands)
        program = nil
        begin
          subcommands.each do |subc|
            if subc.command == argv[0]
              program = subc.klass.new
            end
          end
          # subcommandが指定されていない場合
          if program.nil?
            if argv[0] != nil
              Log::eoutln("Unknown subcommand \"#{argv[0]}\".\n")
            else
              Log::eoutln("Must specify a subcommand.\n")
            end
            print_usage_and_exit(::CLI::ExitStatus::ARGUMENT_ERROR)
          end
        end
        return program
      end

      # raiseされた例外を適切に処理するメソッド。
      # === ARGS
      # e:: 例外
      # === RETURNS
      # exitのためのエラーコード
      def handle_exception(e)
        status = ::CLI::ExitStatus::OK
        Log::eoutln(e.message) if !e.instance_of?(SystemExit)

        case e
        when ::CLI::CLIError
          status = e.code
        when IOError, Errno::EACCES
          ::CLI::print_backtrace(e)
          status = ::CLI::ExitStatus::IO_ERROR
        when Errno::ENOENT
          ::CLI::print_backtrace(e)
          status = ::CLI::ExitStatus::FILE_NOT_FOUND
        when EPCB::InvalidCSVDataFormatError
          status = ::CLI::ExitStatus::INVALID_CSV_DATA_FORMAT
        when EPCB::OptionUnsatisfiedError
          status = ::CLI::ExitStatus::OPTION_UNSATISFIED
        when EPCB::UnknownVersionError
          #::CLI::print_backtrace(e)
          status = ::CLI::ExitStatus::ARGUMENT_ERROR
        when EPCB::UnknownWorkbookVersionError
          status = ::CLI::ExitStatus::INVALID_WORKBOOK_VERSION
        when SystemExit
          exit($!.status)
        else
          $stderr.puts("Oops! Unexpected error. (#{e.message})")
          ::CLI::print_backtrace(e)
          status = ::CLI::ExitStatus::UNEXPECTED_ERROR
        end

        return status
      end
    end #Helper#

  end #CLI#

end #EPCB#
