#--
# = NMatrix
#
# A linear algebra library for scientific computation in Ruby.
# NMatrix is part of SciRuby.
#
# NMatrix was originally inspired by and derived from NArray, by
# Masahiro Tanaka: http://narray.rubyforge.org
#
# == Copyright Information
#
# SciRuby is Copyright (c) 2010 - 2014, Ruby Science Foundation
# NMatrix is Copyright (c) 2012 - 2014, John Woods and the Ruby Science Foundation
#
# Please see LICENSE.txt for additional copyright notices.
#
# == Contributing
#
# By contributing source code to SciRuby, you agree to be bound by
# our Contributor Agreement:
#
# * https://github.com/SciRuby/sciruby/wiki/Contributor-Agreement
#
# == io/matlab/fortran_format.rb
#
# A parser for making sense of FORTRAN formats.
# => Only handles R (real), F (float) and E (exponential) format codes. 
#++

class NMatrix
  module IO
    module FortranFormat

      # Class for reading strings in FORTRAN format for specifying attributes
      # of numerical data in a file. Supports F (float), E (exponential) and 
      # R (real).
      # 
      # == Usage
      # 
      #   p = NMatrix::IO::FortranFormat::Reader.new("(16I5)")
      #   v = p.parse
      #   puts v #=> { :format_code => "INT_ID", 
      #          #=>   :repeat      =>       16,
      #          #=>   :field_width =>        5 }
      class Reader

        # Accepts a string in FORTRAN format and initializes the 
        # NMatrix::IO::FortranFormat::Reader object for further parsing of the 
        # data.
        # 
        # == Arguments
        # 
        # * +string+ - FORTRAN format string to be parsed.
        def initialize string
          @string = string
        end

        # Parses the FORTRAN format string passed in initialize and returns
        # a hash of the results.
        # 
        # == Result Hash Format
        # 
        # Take note that some of the below parameters may be absent in the hash
        # depending on the type of string being parsed.
        # 
        # * +:format_code+ - A string containing the format code of the read data. 
        #                    Can be "INT_ID", "FP_ID" or "EXP_ID" 
        # * +:repeat+      - Number of times this format will repeat in a line.
        # * +:field_width+ - Width of the numerical part of the number.
        # * +:post_decimal_width+ - Width of the numerals after the decimal point.
        # * +:exponent_width+ - Width of exponent part of the number.
        def parse
          raise(IOError, "Left or right parentheses missing") if parentheses_missing? # change tests to handle 'raise' not return

          @result = {}
          @string = @string[1..-2]

          if valid_fortran_format?
            load_result
          else
            raise(IOError, "Invalid FORTRAN format specified. Only Integer, Float or Exponential acceptable.")
          end

          @result
        end

       private
        def parentheses_missing?
          true if @string[0] != '(' or @string[-1] != ')'
        end

        # Changing any of the following regular expressions can lead to disaster
        def valid_fortran_format?
          @mdata = @string.match(/\A(\d*)(I)(\d+)\z/) # check for integer format
          @mdata = @string.match(/\A(\d*)(F)(\d+)\.(\d+)\z/) if @mdata.nil? # check for floating point if not integer
          @mdata =  @string.match(/\A(\d*)(E)(\d+)\.(\d+)(E)?(\d*)\z/) if @mdata.nil? # check for exponential format if not floating point

          @mdata
        end

        def load_result
          if @mdata.to_a.include? "I"
            create_integer_hash
          elsif @mdata.to_a.include? "F"
            create_float_hash
          else
            create_exp_hash
          end
        end

        def create_integer_hash
          @result[:format_code] = "INT_ID"
          @result[:repeat]      = @mdata[1].to_i if !@mdata[1].empty?
          @result[:field_width] = @mdata[3].to_i
        end

        def create_float_hash
          @result[:format_code]        = "FP_ID"
          @result[:repeat]             = @mdata[1].to_i if !@mdata[1].empty?
          @result[:field_width]        = @mdata[3].to_i
          @result[:post_decimal_width] = @mdata[4].to_i
        end

        def create_exp_hash
          @result[:format_code]        = "EXP_ID"
          @result[:repeat]             = @mdata[1].to_i if !@mdata[1].empty?
          @result[:field_width]        = @mdata[3].to_i
          @result[:post_decimal_width] = @mdata[4].to_i
          @result[:exponent_width]     = @mdata[6].to_i if !@mdata[6].empty?
        end
      end
      
    end
  end
end