module ACH
  # The +ACH::File::Reader+ class builds a corresponding +ACH::File+
  # object from a given set of data. The constructor takes an +enum+ object
  # representing a sequence of ACH lines (strings). This +enum+ object may be
  # a file handler, an array, or any other object that responds to the +#each+
  # method.
  class File::Reader
    include Constants

    def initialize(enum)
      @enum = enum
    end

    def to_ach
      header_line, batch_lines, control_line = ach_data

      File.new do
        build_header header_line

        batch_lines.each do |batch_data|
          batch do
            build_header batch_data[:header]

            batch_data[:entries].each do |entry_line|
              build_entry entry_line

              if batch_data[:addendas].key?(entry_line)
                batch_data[:addendas][entry_line].each do |addenda_line|
                  build_addenda addenda_line
                end
              end
            end

            build_control batch_data[:control]
          end
        end

        build_control control_line
      end
    end

    def ach_data
      process! unless processed?

      return @header, batches, @control
    end
    private :ach_data

    def process!
      each_line do |record_type, line|
        case record_type
        when FILE_HEADER_RECORD_TYPE
          @header = line
        when BATCH_HEADER_RECORD_TYPE
          initialize_batch!
          current_batch[:header] = line
        when BATCH_ENTRY_RECORD_TYPE
          current_batch[:entries] << line
        when BATCH_ADDENDA_RECORD_TYPE
          (current_batch[:addendas][current_entry] ||= []) << line
        when BATCH_CONTROL_RECORD_TYPE
          current_batch[:control] = line
        when FILE_CONTROL_RECORD_TYPE
          @control = line
        end
      end
      @processed = true
    end
    private :process!

    def processed?
      !!@processed
    end
    private :processed?

    def each_line
      @enum.each do |line|
        yield line[0..0].to_i, line.chomp
      end
    end
    private :each_line

    def batches
      @batches ||= []
    end
    private :batches

    def initialize_batch!
      batches << {:entries => [], :addendas => {}}
    end
    private :initialize_batch!

    def current_batch
      batches.last
    end
    private :current_batch

    def current_entry
      current_batch[:entries].last
    end
    private :current_entry
  end
end