from dataclasses import dataclass

@dataclass
class Student:
    id: int
    name: str
    courses: list # of Course

@dataclass
class Course:
    course_code: str
    name: str
    subject: str
    offered: bool
    enrolled: list # of Student

# global variables for the central data
course_directory: dict = {}
student_directory: dict = {}

next_id: int = 1 # the next available id number

def create_student(name: str) -> Student:
    """Create new student and add to student database"""

    global next_id
    s = Student(next_id, name, [])
    student_directory[next_id] = s
    next_id = next_id + 1

    return s

def create_course(course_code: str, name: str, subject: str, offered: bool) -> Course:
    """Create new student and add to student database"""

    c = Course(course_code, name, subject, offered, [])
    if course_code in course_directory:
        raise Exception("Course codes must be unique")
    else:
        course_directory[course_code] = c

    return c

def register_student(id: int, course_code: str):
    """Register a student for a course"""

    if course_code not in course_directory:
        raise Exception(course_code + " is not in catalogue")
    elif not course_directory[course_code].offered:
        raise Exception(course_code + " is not being offered this semester")
    elif id not in student_directory:
        raise Exception("Student " + str(id) + " is not in student database")
    elif len(student_directory[id].courses) > 4:
        raise Exception("Student " + str(id) + " is already registered for 5 courses")
    elif course_directory[course_code] in student_directory[id].courses:
        raise Exception("Student " + str(id) + " is already enrolled in this course")
    else:
        student_directory[id].courses.append(course_directory[course_code])
        course_directory[course_code].enrolled.append(student_directory[id])

def check_enrollment(course_code: str):
    """Returns enrollment numbers for a given course"""
    if course_code not in course_directory:
        raise Exception(course_code + " is not in catalogue")

    return len(course_directory[course_code].enrolled)

def sort_by_enrollment():
    """Sorts courses by enrollment numbers"""
    sorted_items = sorted(course_directory.items(), key=lambda c: len(c[1].enrolled), reverse=True)
    return list(map(lambda s: s[0], sorted_items))

def underenrolled_students():
    """Generates a list of underenrolled students"""
    filtered_dict = dict(filter(lambda s: len(s[1].courses) < 3, student_directory.items()))
    return list(filtered_dict.keys())