module Comm
  module Module
    class CsvReader
      require 'fastercsv'
      require 'nkf'
      attr_accessor :file_io
      attr_accessor :regex_date_str
      attr_accessor :regex_datetime_str
      attr_accessor :regex_float_str
      #== コンストラクタ
      def initialize(opts={})
        if opts[:attachment]
          attachment =  opts[:attachment]
          if !attachment.class == StringIO or !attachment.class == Tempfile
            raise 'unexpected type data : '+attachment.class.to_s
          end
          @file_io = attachment
        end
        #csvから入力を許容するフォーマット
        @regex_date_str = Regexp.new('^[12]\d{3}-[01]\d-[0-3]\d$')
        @regex_datetime_str = Regexp.new('^[12]\d{3}-[01]\d-[0-3]\d [0-2]\d:[0-6]\d$')
        @regex_float_str = Regexp.new('^(0|-?[1-9]\d*|-?(0|[1-9]\d*)\.\d+)$')
      end
      SJIS_TO_UTF8=' -Sw'
      TO_LF=' -Lu'
      #== 一行づつ読み込みながら処理もするメソッド
      def read_lines(file_io=@file_io, opts={})
        parse_opts = {:converters=>[lambda{|f, info| f ? NKF::nkf(SJIS_TO_UTF8, f) : f }],
                      :quote_char=>'"',
                      :encoding=>"S"}.update(opts)
        contents=[]
        FasterCSV.parse(file_io, parse_opts) do |row|
          contents << read_line(row)
        end
        return contents
      end
      def read_line(row)
        raise 'This Must be Overrided.'
      end
      #== 一度に全体を読み込んでしまうメソッド
      def read_file(file_io=@file_io, _parse_opts={})
        parse_opts = {:quote_char=>'"',}.update(_parse_opts)
        parsed = FasterCSV.parse( NKF::nkf(SJIS_TO_UTF8+TO_LF, file_io).read, parse_opts)
        return parsed
      end
      
      #== 値valのチェック&型変換
      # * opts[:type]  ...型を指定 :string/:integer/:date
      # * opts[:digit] ...型が:string,:integerの場合に桁数を指定
      # * opts[:allow_blank] ..空白を許可
      # * opts[:index] ...項目番号（エラー表示のみ。省略可。）
      def normarilze_csv_value(val, opts)
        if val.blank?
          if opts[:allow_blank]
            return val
          else
            raise '必須項目が未設定です:'+get_index_message(opts)
          end
        end
        val = val.to_s.strip
        case opts[:type]
        when :string
          check_digits(val, opts)
        when :integer
          check_digits(val, opts)
          val = val.to_i
        when :date
          unless val =~ /#{@regex_date_str}/
            raise '決められた日付フォーマットではありません:'+val.to_s+get_index_message(opts)
          end
          val = Date.parse(val)
        when :datetime
          unless val =~ /#{@regex_datetime_str}/
            raise '決められた日付フォーマットではありません:'+val.to_s+get_index_message(opts)
          end
          #Date型で返すので注意
          val = Date.parse(val)
        when :float
          unless val =~ /#{@regex_float_str}/
            raise '小数（または整数）ではありません:'+val.to_s+get_index_message(opts)
          end
          f_parts = val.split(',')
          f_parts.each_with_index do |part, index|
            check_digits_real(part, opts[:digit][index])
          end
          val = val.to_f
        else
          raise 'unexpected data type :'+opts[:type].to_s+get_index_message(opts)
        end
        return val
      end
      
    private
      def get_index_message(opts)
        return opts[:index] ? ' 項番:'+opts[:index].inspect : ''
      end
      def check_digits(val, opts)
        check_digits_real(val, opts[:digit])
      end
      def check_digits_real(val, num)
        if val.length > num
          raise '桁数が制限以上です:'+val.to_s
        end
      end
    end
  end
end