Generates the fortran code for the fpy_read interface module procedure
    of the specified type and dimensionality.
    
    :arg D: the integer dimensionality of the variable that is having its
      value set from the file.
    :arg dtype: the fortran data type that the subroutine will be reading data
      for. Can be ["real", "integer", "character", "logical", "complex"].
    :arg kind: the precision/kind information for the type. Can be one of
      ["dp", "sp"] for double/single precision real, integer or complex types.
    :arg suffix: whether to generate the module procedure for allocatable (None),
      pointer ("_p"), or fixed ("_f) variables.

def fpy_read(D, dtype, kind, suffix=None):
    """Generates the fortran code for the fpy_read interface module procedure
    of the specified type and dimensionality.
    
    :arg D: the integer dimensionality of the variable that is having its
      value set from the file.
    :arg dtype: the fortran data type that the subroutine will be reading data
      for. Can be ["real", "integer", "character", "logical", "complex"].
    :arg kind: the precision/kind information for the type. Can be one of
      ["dp", "sp"] for double/single precision real, integer or complex types.
    :arg suffix: whether to generate the module procedure for allocatable (None),
      pointer ("_p"), or fixed ("_f) variables.
    """
    modifiers = {
        None: ", allocatable",
        "_p": ", pointer",
        "_f": ""
    }
    common = {
        "dtype": dtype,
        "D": D,
        "modify": modifiers[suffix] if D > 0 else "",
        "Dx": "({})".format(','.join([":"]*D)) if D >0 else "",
        "maxlen": linemax,
        "suffix": xsuffix[suffix],
        "skind": kind if kind is not None else "",
        "fmtstr": "" if dtype != "complex" else "character(:), allocatable :: FMT"
    }
    if dtype == "complex":
        rfmt = "2E22.12" if kind == "dp" else "2E19.9"
    else:
        rfmt = ""
    if dtype == "complex":
        readfmt = prec[dtype][kind]
        
    if dtype in defaults:
        common["default"] = defaults[dtype]
    else:
        common["default"] = "0"
        
    if kind is not None and dtype in kmap:
        common["kind"] = "({})".format(kmap[dtype][kind])
    else:
        common["kind"] = ""
    
    #Add the specific sections for each of the dimensionalities that we read.
    if D==0:
        common["vars"] = "integer :: nlines, nvalues"
    elif D in [1, 2]:
        common["vars"] = "integer :: nlines, nvalues, i"
    else:
        fmtstr = "integer :: dims({0:d}), {1}, indices({0:d})"
        common["vars"] = fmtstr.format(D, ', '.join(["i{}".format(i+1) for i in range(D)]))

    if suffix == "_p":
        common["dealloc"] = "    if (associated(variable)) variable => null()\n"
    elif D > 0 and suffix is None:
        common["dealloc"] = "    if (allocated(variable)) deallocate(variable)\n"
    else:
        common["dealloc"] = ""
        
    if D in [0,1,2]:
        if dtype == "character":
            common["analyze"] = "    call fpy_linevalue_count(filename, commentchar, nlines, nvalues, .true.)\n"
        else:
            common["analyze"] = "    call fpy_linevalue_count(filename, commentchar, nlines, nvalues)\n"
    else:
        common["analyze"] = ""

    if D==0:
        if dtype == "complex":
            lines = ["if ((nvalues .gt. 2) .or. (nlines /= 1)) then"]
        else:
            lines = ["if ((nvalues .gt. 1) .or. (nlines /= 1)) then"]
        lines.extend(["  write(*,*) \"Cannot read a single value from \", filename",
                      "  write(*,*) \"Found \", nlines, \" lines and \", nvalues, \" values\"",
                      "  if (present(success_)) success_ = .false.",
                      "  if (strict) stop",
                      "end if\n"])
        common["warning"] = '    '+'\n    '.join(lines)
    elif D==1:
        if dtype == "complex":
            lines = ["if ((nlines .gt. 2 .and. nvalues .gt. 2) .or. (nlines .eq. 0 .or. nvalues .eq. 0)) then"]
        else:
            lines = ["if ((nlines .gt. 1 .and. nvalues .gt. 1) .or. (nlines .eq. 0 .or. nvalues .eq. 0)) then"]
        lines.extend(['  write(*,*) "Cannot read a vector value from ", filename',
                      '  write(*,*) "Found ", nlines, " lines and ", nvalues, " values"',
                      "  if (present(success_)) success_ = .false.",
                      "  if (strict) stop",
                      "end if\n"])
        if suffix == "_f":
            lines = [""]
            if dtype == "complex":
                lines.append("if ((nlines/2 .ne. size(variable, 1)) .and. (nvalues/2 .ne. size(variable, 1))) then")
            else:
                lines.append("if ((nlines .ne. size(variable, 1)) .and. (nvalues .ne. size(variable, 1))) then")
            lines.extend(["  write(*,*) \"File data dimensions don't match fixed variable shape \", shape(variable)",
                          "  write(*,*) \"Fortpy sees data dimensions in '\", filename, \"' as \", nlines, nvalues",
                          "  if (present(success_)) success_ = .false.",
                          "  if (strict) stop",
                          "end if\n"])
        common["warning"] = '    '+'\n    '.join(lines)
    elif D==2 and suffix == "_f":
        if dtype == "complex":
            lines = ["if ((nlines .ne. size(variable, 1)) .and. (nvalues .ne. size(variable, 2))) then",
                     "  write(*,*) \"File data dimensions don't match fixed variable shape \", shape(variable)",
                     "  write(*,*) \"Fortpy sees data dimensions in '\", filename, \"' as \", nlines, nvalues/2"]
        else:
            lines = ["if ((nlines .ne. size(variable, 1)) .and. (nvalues .ne. size(variable, 2))) then",
                     "  write(*,*) \"File data dimensions don't match fixed variable shape \", shape(variable)",
                     "  write(*,*) \"Fortpy sees data dimensions in '\", filename, \"' as \", nlines, nvalues"]
        lines.extend(["  if (present(success_)) success_ = .false.",
                      "  if (strict) stop",
                      "end if\n"])
        common["warning"] = '    '+'\n    '.join(lines)
    else:
        common["warning"] = ""

    if D==1 and suffix in [None, "_p"]:
        if dtype == "complex":
            lines = ["if (nlines .gt. 2) then",
                     "  FMT = '{}'".format(rfmt)]
        else:
            lines = ["if (nlines .gt. 1) then"]
        lines.extend(["  allocate(variable(nlines))",
                      "else"])
        if dtype == "complex":
            lines.extend(["  FMT = '{}'".format(rfmt),
                          "  do ifmt=2,nvalues/2; FMT = FMT//',1X,{}';  end do".format(rfmt),
                          "  allocate(variable(nvalues/2))"])
        else:
            lines.append("  allocate(variable(nvalues))")
        lines.extend(["end if",
                      "variable = {default}\n".format(**common)])
        common["allocate"] = '    '+'\n    '.join(lines)
    elif D==2 and suffix in [None, "_p"]:
        if dtype=="complex":
            common["allocate"] = ("    allocate(variable(nlines, nvalues/2))\n"
                                  "    FMT = '{0}'\n"
                                  "    do ifmt=2,nvalues/2; FMT = FMT//',1X,{0}';  end do\n"
                                  "    variable = {default}\n".format(rfmt, **common))
        else:
            common["allocate"] = ("    allocate(variable(nlines, nvalues))\n"
                                  "    variable = {default}\n".format(**common))
    elif D==0:
        if dtype == "complex":
            common["allocate"] = ("    write(FMT, *) 1\n"
                                  "    variable = {}\n".format(common["default"]))
        else:
            common["allocate"] = "    variable = {}\n".format(common["default"])
    else:
        common["allocate"] = ""

    if D==0:
        common["initial"] = ""    
    elif D in [1,2]:
        common["initial"] = "\n      i=1"
    else:
        lines = ["\n      do",
                 '  read(funit, "(A)", iostat=ioerr) line',
                 "  if (ioerr == 0) then",
                 "    cleaned = trim(adjustl(line))",
                 "    if (len(cleaned) .gt. 1) then",
                 "      if (cleaned(1:2) .eq. '##') then",
                 "        read(cleaned(3:) ,*) dims",
                 "        exit",
                 "      end if",
                 "    end if",
                 "  else",
                 "    write(*,*) \"No shape information found for {0}D data file '\", filename, \"'\"",
                 "    stop",
                 "  end if",
                 "end do",
                 ""]
        if suffix in [None, "_p"]:
            dims = ', '.join(["dims({})".format(i+1) for i in range(D)])
            lines.append("allocate(variable({}))".format(dims))
        if dtype == "complex":
            lines.append("FMT = '{0}'".format(rfmt))
            fmtloop = "do ifmt=2,dims({0}); FMT = FMT//',1X,{1}';  end do"
            lines.append(fmtloop.format(D, rfmt))
        lines.append("variable = {default}".format(**common))
        lines.append("")

        common["initial"] = '\n      '.join(lines).format(D)

    if D==0:
        lines = ["if (cleaned(1:1) /= commentchar) then"]
        if dtype == "complex":
            lines.append("  read(line, {}) variable".format(readfmt))
        else:
            lines.append("  read(line, *) variable")
        lines.append("end if")
        common["readvar"] = '\n            '.join(lines)
    elif D==1:
        lines = ["if (cleaned(1:1) /= commentchar) then"]                         
        if dtype == "complex":
            lines.append("  if (nvalues == 2) then")
            lines.append("    read(line, {}) variable(i)".format(readfmt))
        else:
            lines.append("  if (nlines .gt. 1) then")
            lines.append("    read(line, *) variable(i)")
        lines.extend(["    i = i+1",
                      "  else"])
        if dtype == "complex":
            lines.append("    read(line, {}) variable".format(readfmt))
        else:
            lines.append("    read(line, *) variable")
        lines.extend(["  end if",
                      "end if"])
        common["readvar"] = '\n            '.join(lines)
    elif D==2:
        lines = ["if (cleaned(1:1) /= commentchar) then"]
        if dtype == "complex":
            lines.append("  read(line, {}) variable(i,:)".format(readfmt))
        else:
            lines.append("  read(line, *) variable(i,:)")
        lines.extend(["  i = i+1",
                      "end if"])
        common["readvar"] = '\n            '.join(lines)
    else:
        lines = ["if (cleaned(1:2) .eq. '##') then",
                 "  read(cleaned(3:), *) indices"]
        ivars = ["i{}".format(i+1) for i in range(D-1)]
        for i in range(D-2):
            lines.append("  i{0} = indices({0})".format(i+1))
        lines.append("  i{} = 1".format(D-1))
        
        lines.append("elseif (cleaned(1:1) /= commentchar) then")
        if dtype == "complex":
            lines.append("  read(line, {}) variable({{0}},:)".format(readfmt))
        else:
            lines.append("  read(line, *) variable({0},:)")
        lines.extend(["  i{1} = i{1}+1",
                      "end if"])
        common["readvar"] = '\n            '.join(lines).format(','.join(ivars), D-1)

    if dtype=="character":
        common["xname"] = "fpy_read_{dtype}_{D}d{suffix}".format(**common)
    else:
        common["xname"] = "fpy_read_{dtype}{skind}_{D}d{suffix}".format(**common)

    if suffix is None:
        common["noexist"] = "return"
    elif suffix == "_p":
        #Set pointers to null if they weren't set on construction in their definition.
        common["noexist"] = "variable => null()\n      return"
    else:
        common["noexist"] = "variable = {}\n      return".format(common["default"])
        
    template = """  subroutine {xname}(filename, commentchar, variable, success_, strict_)
    character(len=*), intent(in) :: filename
    character(1), intent(in) :: commentchar
    logical, optional, intent(out) :: success_
    logical, optional, intent(in) :: strict_
    {dtype}{kind}{modify}, intent(inout) :: variable{Dx}

    character(len=:), allocatable :: cleaned
    integer :: ioerr, funit, ifmt
    logical :: exists, strict
    {vars}
    character({maxlen:d}) :: line
    {fmtstr}

    if (present(strict_)) then
      strict = strict_
    else
      strict = .false.
    end if

    inquire(file=filename, exist=exists)
    if (present(success_)) success_ = exists .or. .false.
    if (.not. exists) then
      if (fpy_verbose > 0) write (*,*) "Target file '", filename, "' does not exist."
      {noexist}
    end if

{dealloc}{analyze}{warning}{allocate}

    open(fpy_newunit(funit), file=filename, iostat=ioerr)
    if (ioerr == 0) then{initial}
      do
        read(funit, "(A)", iostat=ioerr) line
        if (ioerr == 0) then
          cleaned = trim(adjustl(line))
          if (len(cleaned) .gt. 0) then
            {readvar}
          end if
        else
          exit
        end if
      end do
    end if
    close(funit)    
    if (present(success_)) success_ = .true.
  end subroutine {xname}
    """
    return (common["xname"], template.format(**common))