// Arup Guha
// 5/20/2020
// Solution to COP 3502 Program #1: Course Summary

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "leak_detector_c.h"

#define MAXSIZE 20

#define PASSLINE 70.0

// Required structs.

typedef struct student{
    int id;
    char *lname; //stores last name of student
    float *scores; //stores scores of the student. Size is taken from num_scores array.
    float std_avg; //average score of the student (to be calculated)
} student;

typedef struct course {
    char *course_name; //stores course name
    int num_sections; //number of sections
    student **sections;//stores array of student arrays(2D array). Size is num_sections;
    int *num_students;//stores array of number of students in each section. Size is num_sections;
    int *num_scores; //stores array of number of assignments in each section. Size is num_sections;
} course;

// Functions to read in information.
course* read_courses(int* numCoursesPtr);
student **read_sections(int num_students[], int num_scores[], int num_sections);
void readStudent(student* ptrStudent, int num_scores);

// Functions to process information.
void process_courses(course *courses, int num_courses);
float calcSectionAvg(student* ptrStudent, int num_students);
student* getBestStudent(student* ptrStudent, int num_students);
int numPass(student* ptrStudent, int num_students);

// Functions to free dynamically allocated memory.
void release_courses( course *courses, int num_courses);
void release_section(student* ptrStudents, int num_students);

int main(void) {

    // For memory leak detection.
    atexit(report_mem_leak);

    // Read in # of cases.
    int numCases;
    scanf("%d", &numCases);

    // Process each test case.
    for (int loop=1; loop<=numCases; loop++) {

        course* ucfCourses = NULL;
        int numCourses;

        // Allocate array of courses.
        ucfCourses = read_courses(&numCourses);

        // Case header.
        printf("test case %d\n", loop);

        // Process this case.
        process_courses(ucfCourses, numCourses);

        // After each test case.
        printf("\n");

        // Free all of the associated memory.
        release_courses(ucfCourses, numCourses);
    }

    return 0;
}

// Reads in the number of courses into the variable pointed to by numCoursesPtr, then allocates the memory
// to store those courses, reads in all the information for those courses and returns a pointer to the
// allocated array.
course* read_courses(int* numCoursesPtr) {

    // Read in the number of courses.
    scanf("%d", numCoursesPtr);

    // Allocate the space for the course array.
    course* ucfCourses = calloc(*numCoursesPtr, sizeof(course));

    // Allocate each course individually.
    for (int i=0; i<*numCoursesPtr; i++) {

        // Copy in the course name, allocating the right amount of memory.
        char courseName[MAXSIZE+1];
        scanf("%s", courseName);
        ucfCourses[i].course_name = malloc((strlen(courseName)+1)*sizeof(char));
        strcpy(ucfCourses[i].course_name, courseName);

        // Read in the number of sections.
        scanf("%d", &(ucfCourses[i].num_sections));
        int arrSize = ucfCourses[i].num_sections;

        // We have to allocate these first, based on the design given.
        ucfCourses[i].num_students = malloc(arrSize*sizeof(int));
        ucfCourses[i].num_scores = malloc(arrSize*sizeof(int));

        // Read in all of the sections of this course.
        ucfCourses[i].sections = read_sections(ucfCourses[i].num_students, ucfCourses[i].num_scores, ucfCourses[i].num_sections);
    }

    // Ta da!
    return ucfCourses;
}

// Takes in an integer representing the number of sections in a course, as well as arrays that will store
// the number of students and number of scores for each of those sections, allocates memory to store all
// of the sections, reads in those sections, fills in the num_students and num_scores arrays with the
// appropriate information and returns a pointer to the two dimensional array allocated.
student** read_sections(int num_students[], int num_scores[], int num_sections) {

    // Allocate space for the array of pointers.
    student** retVal = malloc(num_sections*sizeof(student*));

    // Go through each section.
    for (int i=0; i<num_sections; i++) {

        // Read in the number of students and number of assignments for section i.
        scanf("%d%d", &num_students[i], &num_scores[i]);

        // Allocating space for a student.
        retVal[i] = malloc(num_students[i]*sizeof(student));

        // Now read in information for each student individually.
        for (int j=0; j<num_students[i]; j++)
            readStudent(&retVal[i][j], num_scores[i]);
    }

    // This is what we want to return.
    return retVal;
}

// Pre-condition: ptrStudent is pointing to an uninitialized student struct.
// Post-condition: Reads in (from standard input), information for one student, and fills the struct
//                 pointed to by ptrStudent with that information, including calculating their average score.
void readStudent(student* ptrStudent, int num_scores) {

    // Read in student id.
    scanf("%d", &(ptrStudent->id));

    // Read in the student's last name.
    char name[MAXSIZE+1];
    scanf("%s", name);

    // Allocate space for last name, and copy into the struct.
    ptrStudent->lname = malloc((strlen(name)+1)*sizeof(char));
    strcpy(ptrStudent->lname, name);

    // Allocate space for array of scores and read those in.
    ptrStudent->scores = malloc(num_scores*sizeof(float));
    ptrStudent->std_avg = 0;

    // Here we read in each of this student's scores, and also add their sum.
    for (int i=0; i<num_scores; i++) {
        scanf("%f", (ptrStudent->scores)+i);
        ptrStudent->std_avg += ptrStudent->scores[i];
    }

    // We had added, so divide to get the average.
    ptrStudent->std_avg /= num_scores;

}

// This function takes in the array of courses, as well as the number of courses stored in the array,
// and processes the information as requested by the assignment.
void process_courses(course *courses, int num_courses) {

    // Go through each course.
    for (int i=0; i<num_courses; i++) {

        // First just do name and # of sections.
        printf("%s", courses[i].course_name);

        // Add up all the passing students and print it out.
        int totalPass = 0;
        for (int j=0; j<courses[i].num_sections; j++)
            totalPass += numPass(courses[i].sections[j], courses[i].num_students[j]);
        printf(" %d", totalPass);

        // Now, print out each section average.
        for (int j=0; j<courses[i].num_sections; j++)
            printf(" %.2f", calcSectionAvg(courses[i].sections[j], courses[i].num_students[j]));

        // Will store a pointer to the best student here.
        student* bestStudent = NULL;

        // Go through each section in order.
        for (int j=0; j<courses[i].num_sections; j++) {

            // Just get the best student from this section.
            student* tmp = getBestStudent(courses[i].sections[j], courses[i].num_students[j]);

            // Update if this is better than anything else seen before.
            if (bestStudent == NULL || tmp->std_avg > bestStudent->std_avg)
                bestStudent = tmp;
        }

        // This finishes out printing a course.
        printf(" %d %s %.2f\n", bestStudent->id, bestStudent->lname, bestStudent->std_avg);
    }
}

// This function takes in an array of students representing a single section, and the size of the array, and
// returns the average score of the section (average grade of all the students).
float calcSectionAvg(student* ptrStudent, int num_students) {

    // Add up the sum of the averages of the students in this section.
    float sum = 0;

    for (int i=0; i<num_students; i++)
        sum += ptrStudent[i].std_avg;

    // This is the desired average.
    return sum/num_students;
}

// This function takes in an array of students representing a single section, and the size of the array, and
// returns a pointer to the best student in the class (one with the highest course average).
student* getBestStudent(student* ptrStudent, int num_students) {

    // At first we use the first student.
    student* res = &ptrStudent[0];

    // Now go through the rest. Update if an average is better.
    for (int i=1; i<num_students; i++)
        if (ptrStudent[i].std_avg > res->std_avg)
            res = &(ptrStudent[i]);

    return res;
}

// This function takes in an array of students representing a single section, and the size of the array, and
// returns the number of passing students (students whose average was 70% or higher).
int numPass(student* ptrStudent, int num_students) {

    // Stores # of students who passed the course.
    int res = 0;

    // Add 1 to our accumulator for each passing student.
    for (int i=0; i<num_students; i++)
        if (ptrStudent[i].std_avg >= PASSLINE)
            res++;

    // This is the number of students who passed.
    return res;
}

// Frees all memory directly or indirectly pointed to by the pointer courses.
void release_courses( course *courses, int num_courses) {

    // First free memory associated with each individual course.
    for (int i=0; i<num_courses; i++) {

        // For each section, free the scores array and the last name.
        for (int j=0; j<courses[i].num_sections; j++)
            release_section(courses[i].sections[j], courses[i].num_students[j]);

        // These three arrays associated with a course have to be freed.
        free(courses[i].num_students);
        free(courses[i].num_scores);
        free(courses[i].course_name);

        // And sections is a 2d array that was dynamically allocated.
        free(courses[i].sections);
    }

    // Now we can free the array.
    free(courses);
}

// Frees all the memory associated with the array ptrStudents, which is of length num_students.
void release_section(student* ptrStudents, int num_students) {

    // Go through each student. There are two items to be freed.
    for (int i=0; i<num_students; i++) {
        free(ptrStudents[i].lname);
        free(ptrStudents[i].scores);
    }

    // Now, this was also dynamically allocated, (the one D array), so free it.
    free(ptrStudents);
}
