#
# == epcb/test/cli/common
# インタフェーステスト向けの共通ファイル。
#
# Revision:: $Id$
#
require 'epcb/test/common'

#
# コマンドインタフェーステスト用の名前空間
#
module EPCB::Test::CLI
  #
  # インタフェーステストのコマンドラインヘルパークラス。
  #
  class Helper < ::EPCB::CLI::Helper
    def initialize(option_holder)
      super(option_holder)
    end

    def register_at_exit(ios)
      at_exit do
        ios.each do |io|
          begin
            io.close
          rescue IOError
            # Ignore
          ensure
            begin
              if File.exists?(io.path) && $options.remove_std
                FileUtils.rm(io.path)
              end
            rescue
              # Ignore
            end
          end
        end
      end
    end

    # --data-dirオプションを定義する。
    # === ARGS
    # opts:: オプション
    def define_data_dir(opts)
      # --datadir
      opts.on("-d", "--data-dir DIR", String,
              "specify test data directory. " \
              "(default: \"#{$options.data_dir}\")") do |arg|
        $options.data_dir = arg
      end
    end

    # --programオプションを定義する。
    # === ARGS
    # opts:: オプション
    def define_program(opts)
      opts.on("-p", "--program FILE", String,
              "sepcify a program of epcb-csv to run. " \
              "(default: \"#{$options.program}\")") do |arg|
        $options.program = arg
      end
    end

    # --removeオプションを定義する。
    # === ARGS
    # opts:: オプション
    def define_remove(opts)
      opts.on("-r", "--remove",
              "remove temporarily redirected \"stdout\" and \"stderr\".") do |arg|
        $options.remove_std = arg
      end
    end

    # --tmp-dirオプションを定義する。
    # === ARGS
    # opts:: オプション
    def define_tmp_dir(opts)
      opts.on("-t", "--tmp-dir DIR",
              "specify temporary directory. " \
              "(default: \"#{$options.tmp_dir}\"") do |arg|
        $options.tmp_dir = arg
      end
    end
  end #Helper#

  #
  # TestCase用ヘルパークラス。
  #
  class EPCB::Test::CLI::TestCaseHelper
    require 'test/unit/assertions'
    include ::Test::Unit::Assertions

    attr_reader :putil, :eutil

    # コンストラクタ
    # === ARGS
    # out:: 標準出力のリダイレクト先
    # err:: 標準エラー出力のリダイレクト先
    # remove_std:: リダイレクトした標準出力のファイルを削除するかどうかのフラグ
    def initialize(out, err, remove_std = false)
      @putil = EPCB::Test::ProcessUtil.new(remove_std)
      @putil.set_redirection(out, "stdout")
      @putil.set_redirection(err, "stderr")
      @eutil = EPCB::ExcelUtil.new
    end
    
    def destroy
      @eutil.destroy_excel
    end
    
    # サブコマンド+genall+を実行し、生成されたExcelPettyCashBookのデータを検証する。
    # === ARGS
    # name:: ファイル名(拡張子は含めない)
    # template:: 移行先バージョンのテンプレートファイル
    # outfile:: 出力先ファイルパス
    # data_array:: 検証で使用するデータの配列
    # validate_proc:: 検証する際に実行するブロック
    def genall_and_validate_excel(name, template, outfile,
                                  data_array, &validate_proc)
      run_command("genall", name, "-t #{$options.data_dir}/#{template} -o #{outfile}")
      assert(FileTest.exist?(outfile))

      dest_path = File.file_system.getAbsolutePathName(outfile)
      sheet_names = get_sheet_names(data_array)
      begin
        workbook = @eutil.open_workbook_and_activate(dest_path)
        start_index = 1
        for i in (6 .. workbook.worksheets.count) # 6: データシートの始まり
          sheet = workbook.worksheets(i)
          assert_equal(sheet_names[i-6], sheet.name)
          sheet.activate
          @eutil.initialize_on_active_sheet

          # 引数のdata_arrayから各シートごとのデータの2次元配列を作成する
          sheet_data = Array.new
          for j in (start_index .. data_array.size - 1)
            if data_array[j].size == 1
              start_index = j + 1
              break
            end
            sheet_data << data_array[j].dup
          end
          puts "sheet_data = #{sheet_data.inspect}"

          # 存在しないはずのデータ行がシートにないか確認
          7.times { |j| assert_nil(@eutil.get_offset(sheet_data.size, j).value) }

          # 各シートごとのデータを使用して、引数のブロックを実行する
          sheet_data.size.times do |line|
            7.times do |column|
              validate_proc.call(sheet_data, line, column)
            end
          end
        end
      ensure
        @eutil.destroy_excel
      end
    end #genall_and_validate_excel#

    # シート名を格納した配列を取得する。
    # === ARGS
    # array:: 検証で使用するデータ配列
    # === RETURNS
    # シート名を格納した配列
    def get_sheet_names(array)
      sheet_names = Array.new
      array.size.times do |i|
        if array[i].size == 1
          name = array[i][0]
          # 先頭の$$を削除しておく
          sheet_names << name.sub(/^\$\$/, "")
        end
      end
      return sheet_names
    end

    # サブコマンド+gencsv+を実行し、生成されたCSVファイルを検証する。
    # === ARGS
    # name:: CSVファイル名(拡張子は含めない)
    # data:: 検証用データ配列
    # opt:: コマンドに渡されるオプション
    def gencsv_and_validate_csv(name, data, opt = nil)
      out_file = "#{$options.tmp_dir}/#{name}.csv"
      opt = "" if opt.nil?
      opt += "#{opt} -o #{out_file}"
      run_command("gencsv", name, opt)
      validate_csv(out_file, data)
    end

    # 引数の+subcommand+を実行し、exit_statusが 0 であることを確認する。
    # === ARGS
    # subcommand:: サブコマンド(gencsv, genallのいずれか)
    # name:: 移行元のExcelPettyCashBook
    # opt:: コマンドに渡されるオプション
    def run_command(subcommand, name, opt = nil)
      cmd = "#{$options.program} #{subcommand} #{$options.data_dir}/#{name}.xls"
      cmd += " #{opt}" unless opt.nil?
      @putil.quiet_run(cmd)
      assert_equal(0, @putil.exit_status)
    end

    # CSVファイルを検証する
    # === ARGS
    # file_path:: CSVファイルパス
    # data:: CSVファイルのデータと比較するための検証データ配列
    def validate_csv(file_path, data)
      assert(File.exist?(file_path))
      File.open(file_path, "rb") do |file|
        i = 0
        file.each do |line|
          row = CSV.parse_line(line, ?,, EPCB::CSV_LINE_SEP)
          assert_equal(data[i].size, row.size,
                       "L#{i+1}: CSVのカラム数が不正")
          data[i].size.times do |j|
            assert_equal(data[i][j], row[j].to_s,
                         "L#{i+1} C#{j+1}: カラムのデータが不正")
          end
          i = i + 1
        end
      end
    end
  end #TestCaseHelper#

end
