PROGRAM Academic_Standing
!-----------------------------------------------------------------------
! Program to determine academic standing of engineering students
! according to two criteria:  cumulative hours and cumulative GPA.
! It also counts the total # of students checked and the # found to
! be in good standing, and calculates the average current GPA for all
! students.  Variables used are:
!   NumStudents           : total number of students
!   Num_in_Good_Standing  : number in good standing
!   Response              : user response to more-data inquiry
!   Sum_of_GPAs           : sum of all current GPAs 
!
! Input:  Response; also several items of student information in 
!         subroutine CalculateStats 
! Output: Student report by subroutine Report and summary statistics 
!         by subroutine WrapUp  
!-----------------------------------------------------------------------

  IMPLICIT NONE
  INTEGER :: NumStudents = 0, Num_in_Good_Standing = 0
  REAL :: Sum_of_GPAs = 0.0
  CHARACTER(1) :: Response

  CALL Inform

  ! Repeat the following until no more data
  DO
     CALL Process(NumStudents, Num_in_Good_Standing, Sum_of_GPAs)
     WRITE (*, '(// 1X, A)', ADVANCE = "NO") "More (Y or N)? "
     READ *, Response
     IF (Response /= "Y") EXIT
  END DO

  CALL WrapUp(NumStudents, Num_in_Good_Standing, Sum_of_GPAs)

CONTAINS

  !-Inform--------------------------------------------------------------
  !  Subroutine to display instructions to the user.
  !  Output:  Several lines of instructions
  !---------------------------------------------------------------------

  SUBROUTINE Inform    
    PRINT *, "You will first be asked to enter the student's number,"
    PRINT *, "class, cumulative hours, and cumulative gpa.  Enter these"
    PRINT *, "with at least one space or comma separating them."
    PRINT *
    PRINT *, "You will then be asked to enter the number of hours and"
    PRINT *, "the numeric grade earned for each of the courses the"
    PRINT *, "student took during the current year.  Separate the"
    PRINT *, "number of hours from the grade by at least one space"
    PRINT *, "or by a comma.  Enter 0 for hours and 0 for grades when"
    PRINT *, "you are finished entering the information for each &
             &student."
    PRINT *
    PRINT *
    PRINT *
    
  END SUBROUTINE Inform

 
  !--Process------------------------------------------------------------
  ! Accepts student information, determines academic standing, and
  ! maintains counts of # processed and # in good standing, and a sum
  ! of current GPAs.  Variables used are:
  !   NumStudents          : total number of students
  !   Num_in_Good_Standing : number in good standing
  !   Sum_of_GPAs          : sum of all current GPAs
  !   StudentNumber        : student's number
  !   Class                : student's class
  !   CumulativeHours      : student's cumulative hours
  !   CumulativeGPA        : student's cumulative GPA
  !   CurrentGPA           : student's current GPA
  !   InGoodStanding       : indicates whether student is in good
  !                          standing
  !
  ! Accepts:  NumStudents, Num_in_Good_Standing, and Sum_of_GPAs
  ! Returns:  Updated values of NumStudents, Num_in_Good_Standing, and
  !           Sum_of_GPAs
  !---------------------------------------------------------------------

  SUBROUTINE Process(NumStudents, Num_in_Good_Standing, Sum_of_GPAs)

    INTEGER, INTENT(INOUT) :: NumStudents, Num_in_Good_Standing
    REAL, INTENT(INOUT) :: Sum_of_GPAs
    REAL :: CumulativeHours, CumulativeGPA, CurrentGPA
    INTEGER :: StudentNumber, Class
    LOGICAL :: InGoodStanding

    CALL CalculateStats(StudentNumber, Class, NumStudents, &
                        CumulativeHours, CumulativeGPA, CurrentGPA, &
                        Sum_of_GPAs)
    CALL CheckEligibility(Class, CumulativeHours, CumulativeGPA, &
                          InGoodStanding, Num_in_Good_Standing)
    CALL Report(StudentNumber, Class, CumulativeHours, CurrentGPA, &
                CumulativeGPA, InGoodStanding)

   END SUBROUTINE Process
 

  !-WrapUp--------------------------------------------------------------
  ! Subroutine to print some summary statistics.
  ! Accepts:  Number of students (NumStudents), number in good standing
  !           (Num_in_Good_Standing), and sum of all GPAs (Sum_of_GPAs)
  ! Output:   Report containing values of NumStudents, 
  !           Num_in_Good_Standing, and average GPA
  !---------------------------------------------------------------------
  
  SUBROUTINE WrapUp(NumStudents, Num_in_Good_Standing, Sum_of_GPAs)
  
    INTEGER, INTENT(IN) :: NumStudents, Num_in_Good_Standing
    REAL, INTENT(IN) :: Sum_of_GPAs

    PRINT *
    PRINT *
    PRINT *, "******************************************************"
    PRINT *, "*                SUMMARY STATISTICS                  *"
    PRINT *, "******************************************************"
    PRINT *
    PRINT '(1X, A, T34,  I5)', "Number of students processsed: ", &
          NumStudents
    IF (NumStudents /= 0) THEN
       PRINT '(1X, A, T34, F5.2)', "Average current gpa of students:", &
             Sum_of_GPAs / REAL(NumStudents)
       PRINT '(1X, A, T34, I5)', "Number in good standing:", &
             Num_in_Good_Standing
    END IF
    
  END SUBROUTINE WrapUp
  
  
  !-CalculateStats------------------------------------------------------
  ! Subroutine to read a student's number, class, cumulative hours,
  ! and cumulative GPA; then read Hours and Grade for courses taken 
  ! during the current year, and calculate current GPA, update 
  ! cumulative hours, cumulative GPA, and count (NumStudents) of
  ! students processed.  Hours = 0 and Grade = 0 are used to signal the 
  ! end of data for a student.  Other local variables used are:
  !   NewHours       : total hours earned during current year
  !   NewHonorPoints : honor points earned in current year
  !   OldHonorPoints : honor points earned in past years
  !
  ! Accepts:  NumStudents and Sum_of_GPAs
  ! Input:    StudentNumber, Class, CumulativeHours, CumulativeGPA;
  !           also Hours and Grade for each of several courses
  ! Returns:  StudentNumber, Class, CumulativeHours, CumulativeGPA,
  !           CurrentGPA and updated values of NumStudents and
  !           Sum_of_GPAs
  !---------------------------------------------------------------------
  
  SUBROUTINE CalculateStats(StudentNumber, Class, NumStudents,  &
                            CumulativeHours, CumulativeGPA, &
                            CurrentGPA, Sum_of_GPAs)
  
    INTEGER, INTENT(INOUT) ::  NumStudents
    INTEGER, INTENT(OUT) :: StudentNumber, Class
    REAL, INTENT(INOUT) :: Sum_of_GPAs
    REAL, INTENT(OUT) :: CumulativeHours, CumulativeGPA, CurrentGPA
    REAL :: Hours, Grade, NewHours, NewHonorPoints, OldHonorPoints
 
    WRITE (*, '(1X, A)', ADVANCE = "NO") &
          "Enter student number, class, cum. hours, cum. gpa: "
    READ *, StudentNumber, Class, CumulativeHours, CumulativeGPA
    OldHonorPoints = CumulativeHours * CumulativeGPA
    NewHours = 0.0
    NewHonorPoints = 0.0

    DO
       WRITE (*, '(1X, A)', ADVANCE = "NO") "Hours and grade? "
       READ *, Hours, Grade
       IF (Hours <= 0.0) EXIT
       ! Terminate repetition if end-of-data signaled
       ! Otherwise continue with the following

       NewHours = NewHours + Hours
       NewHonorPoints = NewHonorPoints + Hours * Grade
    END DO

    IF (NewHours == 0.0) THEN
       CurrentGPA = 0.0
    ELSE
       CurrentGPA = NewHonorPoints / NewHours
    END IF
    CumulativeHours = CumulativeHours + NewHours
    CumulativeGPA = (OldHonorPoints + NewHonorPoints) / CumulativeHours
    NumStudents = NumStudents + 1
    Sum_of_GPAs = Sum_of_GPAs + CurrentGPA
  
  END SUBROUTINE CalculateStats
  
  
  !-CheckEligibility----------------------------------------------------
  ! Subroutine to check academic standing.  Two criteria are used:
  ! cumulative hours and cumulative GPA.  Functions HoursCheck and 
  ! GPACheck are used to check these.  Class, CumulativeHours, and
  ! CumulativeGPA are the class, cumulative hours, and cumulative GPA 
  ! for the student being checked.  InGoodStanding is true or false
  ! according to whether or not the student is found to be in good
  ! standing, and Num_in_Good_Standing is the count of students who
  ! are in good standing.
  !
  ! Accepts:  Class, CumulativeHours, CumulativeGPA, and 
  !           Num_in_Good_Standing
  ! Returns:  InGoodStanding and updated value of Num_in_Good_Standing
  ! Output:   Message indicating an illegal class code
  !---------------------------------------------------------------------
  
  SUBROUTINE CheckEligibility(Class, CumulativeHours, CumulativeGPA, &
                              InGoodStanding, Num_in_Good_Standing)
  
    INTEGER, INTENT(IN) :: Class
    INTEGER, INTENT(INOUT) :: Num_in_Good_Standing
    REAL, INTENT(IN) :: CumulativeHours, CumulativeGPA
    LOGICAL, INTENT(OUT) :: InGoodStanding
  
    IF ((Class < 1) .OR. (Class > 3)) THEN
       PRINT *, "*** Illegal class code ***"
       InGoodStanding = .FALSE.
    ELSE
       InGoodStanding = HoursCheck(Class, CumulativeHours) .AND. &
                        GPACheck(Class, CumulativeGPA)
    END IF

    IF (InGoodStanding) Num_in_Good_Standing = Num_in_Good_Standing + 1
  
  END SUBROUTINE CheckEligibility
 

  !-Report--------------------------------------------------------------
  ! Subroutine to display the statistics for a given student.
  !
  ! Accepts:  Student's number, class, cumulative hours, current GPA
  !           cumulative GPA, and indicator whether student is in good
  !           standing
  ! Output:   StudentNumber, Class, CumulativeHours, CurrentGPA, and a
  !           message indicating  whether student is in good standing 
  !---------------------------------------------------------------------
  
  SUBROUTINE Report(StudentNumber, Class, CumulativeHours, CurrentGPA, &
             CumulativeGPA, InGoodStanding)
  
    INTEGER, INTENT(IN) :: StudentNumber, Class
    REAL, INTENT(IN) :: CumulativeHours, CurrentGPA, CumulativeGPA
    LOGICAL, INTENT(IN) :: InGoodStanding
  
    PRINT *
    PRINT '(1X, "***** report for student", I6, " *****")', &
          StudentNumber
    PRINT '(1X, A, I6)', "Class:           ", Class
    PRINT 10, "Cumulative Hours:", CumulativeHours
    PRINT 10, "Current GPA:     ", CurrentGPA
    PRINT 10, "Cumulative  GPA: ", CumulativeGPA
    10 FORMAT(1X, A, F6.2)
  
    IF (InGoodStanding) THEN
       PRINT 20, "In good standing"
    ELSE
       PRINT 20, "*** Not in good standing ***"
    END IF
    20 FORMAT(1X, A / 1X, 35("*"))
    
  END SUBROUTINE Report


  !-HoursCheck----------------------------------------------------------
  ! Check cumulative hours (CumulativeHours) of student in Class.  Local
  ! parameters Freshman_Hours, Sophomore_Hours, and Junior_Hours give 
  ! the minimum number of hours required of freshmen, sophomores, and 
  ! juniors, respectively.
  ! Accepts: Class and CumulativeHours
  ! Returns: True or false according to whether student has accumulated
  !          enough hours
  !---------------------------------------------------------------------
  
  FUNCTION HoursCheck(Class, CumulativeHours)
  
    LOGICAL :: HoursCheck
    INTEGER, INTENT(IN) :: Class
    REAL, INTENT(IN) ::  CumulativeHours
    REAL, PARAMETER :: Freshman_Hours = 25.0, Sophomore_Hours = 50.0, &
                       Junior_Hours = 85.0
  
    IF (Class == 1) THEN
       HoursCheck = (CumulativeHours >= Freshman_Hours)
    ELSE IF (Class == 2) THEN
       HoursCheck = (CumulativeHours >= Sophomore_Hours)
    ELSE
       HoursCheck = (CumulativeHours >= Junior_Hours)
    END IF
    
  END FUNCTION HoursCheck
  
  
  !-GPACheck------------------------------------------------------------
  ! Check cumulative GPA of student in Class. Local parameters
  ! Freshman_GPA, Sophomore_GPA, and Junior_GPA give give the minimum 
  ! GPA required of freshmen, sophomores, and juniors, respectively. 
  !
  ! Accepts: Class and CumulativeGPA
  ! Returns: True or false according to whether student's GPA is high
  !          enough
  !---------------------------------------------------------------------
  
  FUNCTION GPACheck(Class, CumulativeGPA)
  
    LOGICAL :: GPACheck
    INTEGER, INTENT(IN) :: Class
    REAL, INTENT(IN) :: CumulativeGPA
    REAL, PARAMETER :: Freshman_GPA = 1.7, Sophomore_GPA = 1.85, &
                       Junior_GPA = 1.95
  
    IF (Class == 1) THEN
       GPACheck = (CumulativeGPA >= Freshman_GPA)
    ELSE IF (Class == 2) THEN
       GPACheck = (CumulativeGPA >= Sophomore_GPA)
    ELSE
       GPACheck = (CumulativeGPA >= Junior_GPA)
    END IF
    
  END FUNCTION GPACheck
  
END PROGRAM Academic_Standing
