
import java.util.Arrays;

/**
 * A class to hold some very basic information about a student.
 * Added an array of grades (assignment grades) to replace the one percentage
 * grade of earlier versions.
 *
 * @author Mark Young (A00000000)
 * @version 3.1 (2017-11-06)
 */
public class Student {

    /** The student's A-Number */
    public final String A_NUMBER;
    /** The student's name */
    private String name;
    /** The student's grade (as a percentage) */
    private int[] asgnGrades;

    // Class information -- common to all Students //

    /** The number for the next Student created */
    private static int nextANumber = 1;

    /** Maximum possible percentage grade */
    public static final int MAX_GRADE = 100;
    /** Minimum possible percentage grade */
    public static final int MIN_GRADE = 0;
    /** Number of assignments in course */
    public static final int NUM_ASGN = 8;
    /** Number of dropped assignment grades */
    public static final int NUM_DROPS = 2;
    /** Special value to represent a non-existent grade */
    public static final int INVALID_GRADE = -1;

    /**
     * Create a Student object.
     *
     * @param name      the requested name
     */
    public Student(String name) {
        // assign next A-Number to this Student
        this.A_NUMBER = String.format("A%08d", nextANumber);
        ++nextANumber;

        // set personal data
        this.name = name;
        this.asgnGrades = new int[NUM_ASGN];
        // array starts off all zeroes, which is what we want,
        // so no need to set each asgnGrades[i]
    }

    /**
     * Get the value of A_NUMBER
     *
     * @return the value of A_NUMBER
     */
    public String getANumber() {
        return A_NUMBER;
    }

    /**
     * Get the value of name
     *
     * @return the value of name
     */
    public String getName() {
        return name;
    }

    /**
     * Get student's grade on a particular assignment.
     *
     * @param a the assignment requested
     * @return the grade on the requested assignment
     *         OR INVALID_GRADE if there is no such assignment
     */
    public int getAsgnGrade(int a) {
        if (isValidAssignment(a)) {
            return asgnGrades[a - 1];
        } else {
            return INVALID_GRADE;
        }
    }

    /**
     * Get the student's percentage grade on assignments.
     * Current version is based on average assignment grade,
     * dropping the lowest grades.
     *
     * @return this Student's average assignment grade
     */
    public int getAsgnsGrade() {
        int[] drops = getSmallest(asgnGrades, NUM_DROPS);
        int sum = sumArray(asgnGrades) - sumArray(drops);
        return (int)Math.round((double)sum / (NUM_ASGN - NUM_DROPS));
    }

    public int[] getAsgnGrades() {
        return Arrays.copyOf(asgnGrades, NUM_ASGN);
    }

    /**
     * Get the course grade for this student.
     * Eventually this will be based on all grades,
     * but currently it is based only on the assignments grade.
     *
     * @return this Student's percentage grade for the course
     * @since 3.1
     */
    public int getGrade() {
        return getAsgnsGrade();
    }

    /**
     * get letter grade for this student
     *
     * @return  the letter grade equivalent to this student's percentage grade
     * @since 2.0
     */
    public String getLetterGrade() {
        return Student.letterGradeFor(this.getGrade());
    }

    /**
     * Set the value of name
     *
     * @param name new value of name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * Set the value of an assignment grade.
     *
     * @param a  number of assignment this grade is for
     * @param g  new assignment grade (must be 0..100)
     */
    public void setAsgnGrade(int a, int g) {
        if (isValidAssignment(a) && isValidGrade(g)) {
            asgnGrades[a - 1] = g;
        }  
        // otherwise fail without warning(!)
    }

    /**
     * Print a brief summary of the student's data.
     */
    public void printRecord() {
        System.out.println("\tNumber: " + A_NUMBER);
        System.out.println("\tName:   " + name);
        System.out.println("\tGrade:  " + getGrade());
        System.out.println("\tLetter: " + this.getLetterGrade());
    }

    /**
     * Create a String to represent this Student.
     * This version uses their name and A-number.
     *
     * @return  a String representing this Student
     */
    @Override
    public String toString() {
        return name + " (" + A_NUMBER + ")";
    }

    /**
     * Return whether a given grade is in the valid range.
     *
     * @param g     the grade to check for validity
     * @return  whether g is in the correct range.
     * @since 2.0
     */
    public static boolean isValidGrade(int g) {
        return (MIN_GRADE <= g && g <= MAX_GRADE);
    }

    /**
     * Return whether a given number is a valid assignment number.
     *
     * @param a     the number to test for validity
     * @return whether a is in the correct range
     * @since 3.0
     */
    public static boolean isValidAssignment(int a) {
        return 1 <= a && a <= NUM_ASGN;
    }


    /**
     * Return the letter grade corresponding to the given grade.
     *
     * @param g     the percentage grade to be converted to a letter grade.
     * @return  the letter grade corresponding to g.
     * @since 2.0
     */
    public static String letterGradeFor(int g) {
        if (g < 50) {
            return "F";
        } else if (g < 60) {
            return "D";
        } else if (g < 63) {
            return "C-";
        } else if (g < 67) {
            return "C";
        } else if (g < 70) {
            return "C+";
        } else if (g < 73) {
            return "B-";
        } else if (g < 77) {
            return "B";
        } else if (g < 80) {
            return "B+";
        } else if (g < 85) {
            return "A-";
        } else if (g < 90) {
            return "A";
        } else {
            return "A+";
        }
    }

    /**
     * Sum the elements of an array.
     *
     * @param arr   the array to sum the elements of
     * @return the sum of the elements of arr
     */
    private static int sumArray(int[] arr) {
        int sum = 0;
        for (int i = 0; i < arr.length; ++i) {
            sum += arr[i];
        }
        return sum;
    }

    /**
     * Return an array containing the lowest values from an array.
     * Requires that n %le; arr.length.
     *
     * @param arr the array to find the lowest values in
     * @param n the number of lowest values to find
     * @return an array containing the n smallest values of arr
     */
    private static int[] getSmallest(int[] arr, int n) {
        int[] copy = Arrays.copyOf(arr, arr.length);
        Arrays.sort(copy);
        return Arrays.copyOfRange(copy, 0, n);
    }

}
