# == epcb/csv/csv-parser
#
# Revision:: $Id$
#
require 'csv'
require 'epcb/csv/common'

#
# アップグレードするデータを格納したCSVを解析するパーサ。
#
class EPCB::CSV::CSVParser
  # コンストラクタ。
  # === ARGS
  # src_path:: 解析するCSVファイルのパス
  def initialize(src_path)
    @src_file = File.open(src_path, "r")
    #@src_file.binmode
    @line_offset = 0
  end

  # 解析するファイルにまだ読み込んでいない行が存在するかどうか判定する。
  # === RETURNS
  # 解析するファイルにまだ読み込んでいない行が存在する場合は true
  def has_next_line?
    return !@src_file.closed? && !@src_file.eof?
  end

  # コンストラクタで渡されたFileを解析し、1シート分の
  # MonthDataインスタンスを返す。
  #
  # === RETURNS
  # 解析結果である1シート分のMonthData
  def parse
    return parse_with_offset(@line_offset)
  end

  # コンストラクタで渡されたFileを +line_offset+ の位置から解析し、
  # 1シート分のMonthDataインスタンスを返す。
  # === ARGS
  # line_offset:: 解析を開始する行の位置
  # === RETURNS
  # 解析結果である1シート分のMonthData
  def parse_with_offset(offset)
    data_set = nil
    #puts "parse_with_offset: offset = #{offset}"
    while true
      tmp = @src_file.gets
      if tmp.nil?
        #puts "CALL destory()"
        # 解析するデータはもうないので、destroyを呼ぶ
        destroy
        break
      end

      #puts "tmp: #{EPCB::escape(tmp)}"
      line = tmp.chomp
      row = CSV.parse_line(line, ?,, EPCB::CSV_LINE_SEP)
      # 今回ファイルを読み込んでから、最初の月、
      # または新しい月に切り替わった場合
      if /^\$\$(.+)/ =~ row[0]
        name = $1
        # 新しい月に切り替わったら
        if data_set != nil
          # 先読みした分、ファイルポインタを元に戻しておく
          size = -(tmp.unpack("C*").size) - 1 # \rの分-1する
          @src_file.seek(size, IO::SEEK_CUR)
          # ループを抜けて、data_setを返す
          break
        end
        Log::voutln("Parsing sheet \"#{name}\".")

        # マスタシートの場合
        if EPCB::master_sheet?(name)
          data_set = EPCB::MasterData.new
        # プリファレンスシートの場合
        elsif EPCB::prefs_sheet?(name)
          data_set = EPCB::PreferencesData.new
        # データシートらしきものの場合
        else
          data_set = EPCB::MonthData.new(name)
        end
      # データ行の場合
      else
        # シート名が存在しないで、データ行にいきなり来た状態。例外を投げるべき?
        next if data_set.nil?
        Log::debugln("#{row.inspect}")
        parse_row(row, data_set)
      end
      # 行オフセットをインクリメント
      offset = offset + 1
      @line_offset = @line_offset + 1
    end
    #puts "offset = #{offset}, @line_offset = #{@line_offset}"
    return data_set
  end

  private
  # CSVのデータ行1行分を解析する。
  # === ARGS
  # line:: CSVの行文字列
  # data_set:: 解析結果を格納するためのMonthData
  def parse_row(row, data_set)
    if data_set.master?
      row.each do |col|
        data_set.use << EPCB::UseMasterData.new(col)
      end
    elsif data_set.prefs?
      raise(EPCB::InvalidCSVDataFormatError, "CSV's column size must be 2. (#{row.to_a.size})") if row.to_a.size != 2
      data_set.store(row[0], row[1])
    else
      raise(EPCB::InvalidCSVDataFormatError, "CSV's column size must be 4. (#{row.to_a.size})") if row.to_a.size != 4
      # CSVのデータ行は"日付,用途,金額,備考"というフォーマット
      data = EPCB::new_specific_instance("IncomeExpensesData", nil, 
                                         EPCB::TARGET_VERSION, *row)
      data_set.add(data)
    end
  end

  def destroy
    begin
      @src_file.close unless @src_file.nil?
    rescue IOError
      # 何もしない
    end
  end
end #CSVParser#
