'''The tests for all CSC 3340 project problems, as well as the solution to the problem.
   Author: Kyle Burke <kwburke@plymouth.edu>'''

import math
import sys
import pymongo
import datetime
import copy

project_number = 6


def test_part(func, context, message_list):
    '''Runs f, a function with the elements of context (followed by message_list) as it's parameters that tests the part.  If f completes, then the test succeeds, and this returns True.  If f throws an exception, then it prints the string at message_list[0] and returns False.  f should be written so that it stores the relevant error message in message_list[0] before any statement that could cause an exception.'''
    #print("Called test_part...")
    
    try:
        func(*context, message_list)
        #print("Finished calling func!")
    except Exception as e:
        print(e)
        return False
    return True

def assert_true(test):
    if not test:
        raise Exception()
    
def assert_false(test):
    if test:
        raise Exception()




   
def sub_record(key, result):
    for entry in key:
        if not entry in result:
            return False
    return True


############### Tests!!!!!!! ###########################

### Table Setup tests ###

print("Starting the grading!")

print("IMPORTANT: This does not include all of my tests!  Your score might be lower than what you get here if you don't test all other things carefully.  For most parts, my tests are more comprehensive.  The best thing you can do is to add more tests.  (If you do that, do not share with other groups if it includes solutions.)  You can still upload your code and ask me to review it.")

students = []

students.append(input("Please enter your teamname: "))

feedbacks = []

for student in students:
    feedback_text = ""
    score = 0
    
    connection = pymongo.MongoClient()
    

    #Import the student file and call their main function if they have one
    stu_module = __import__(student + "_project" + str(project_number))
    if hasattr(stu_module, "main"):
        stu_module.main()
    
    stu_db = connection[student]
    
    
    parts = []
    
    #part 0: all royals
    def part_0_test(stu_db, error_list):
        
        #check whether the collection exists
        error_list[0] = "No collection named 'british-royals'."
        stu_collection = stu_db['british-royals']
        
        expected_names = ["Elizabeth", "James Charles Stuart", ("Charles", 2)]
        
        for name in expected_names:
            expected_num_docs = 1
            if isinstance(name[1], int):
                expected_num_docs = name[1]
                name = name[0]
                
            
            cursor = stu_collection.find({"full name" : name})
            results = list(cursor)
            num_results = len(results)
            error_list[0] = "Didn't find any documents for the full name " + name
            assert_true(num_results > 0)
            error_list[0] = "Didn't find the correct number of documents for the name " + name
            assert_true(num_results == expected_num_docs)
            
            
        
    
        parentages = [("George Frederick Ernest Albert", "Edward Albert Christian George Andrew Patrick David"),
                  ("Albert Edward", "George Frederick Ernest Albert")]
        
        for (parent_name, child_name) in parentages:
            
            error_list[0] = "Couldn't get one of the royals from the collection: " + child_name
            child = stu_collection.find_one({"full name" : child_name})
            assert_true(child != None)
            
            error_list[0] = "Couldn't get one of the royals from the collection: " + parent_name
            parent = stu_collection.find_one({"full name" : parent_name})
            assert_true(parent != None)
            
            error_list[0] = child_name + " doesn't have a parents field"
            parent_id = parent["_id"]
            parent_ids = child['parents']
            
            error_list[0] = child_name + " is missing at least one parent (or has them incorrectly recorded)."
            assert_true(parent_id in parent_ids)
            
    parts.append((part_0_test, [stu_db], 25))
    
    
    def part_1_test(stu_db, error_list):
        
        #check whether the collection exists
        error_list[0] = "No collection named 'british-royals'."
        stu_collection = stu_db['british-royals']
        
        regnal_names = ["Elizabeth", "James"]
        
        for regnal_name in regnal_names:
            
            error_list[0] = "Got an exception while looking for the regnal name " + regnal_name 
            results = list(stu_collection.find({"name" : regnal_name}))
            
            error_list[0] = "Failed to find a document for the regnal name " + regnal_name
            assert_true(len(results) > 0)
            
            error_list[0] = "Got multiple documents matching the regnal name " + regnal_name
            assert_true(len(results) == 1)
        
        
    parts.append((part_1_test, [stu_db], 20))
    
    
    
    #tests get_regnal_name
    def part_2_test(stu_db, stu_module, error_list):
        
        #check whether the collection exists
        error_list[0] = "No collection named 'british-royals'."
        stu_collection = stu_db['british-royals']
        
        regnal_name_pairs = [
            ["Elizabeth", [
                "Elizabeth",
                "Elizabeth Alexandra Mary"]],
            ["James", [
                "James Charles Stuart",
                "James"]]]
        
        for (regnal_name, full_names) in regnal_name_pairs:
            for full_name in full_names:
                
                
                error_list[0] = "Threw an exception while calling the function on " + full_name
                result = stu_module.get_regnal_name(stu_collection, full_name)
                
                error_list[0] = "Incorrect regnal name for " + full_name
                assert_true(result == regnal_name)
                
    parts.append((part_2_test, [stu_db, stu_module], 10))
    
    
        
    #tests get_monarchs_by_name
    def part_3_test(stu_db, stu_module, error_list):
        
        #check whether the collection exists
        error_list[0] = "No collection named 'british-royals'."
        stu_collection = stu_db['british-royals']
        
        regnal_name_pairs = [
            ["Elizabeth", [
                "Elizabeth",
                "Elizabeth Alexandra Mary"]],
            ["James", [
                "James Charles Stuart",
                "James"]]]
        
        for (regnal_name, full_names) in regnal_name_pairs:
        
            error_list[0] = "Threw an exception while calling the function on " + regnal_name
            result = stu_module.get_monarchs_by_name(stu_collection, regnal_name)
            
            error_list[0] = "Didn't return a list for " + regnal_name
            assert_true(isinstance(result, list))
            
            error_list[0] = "Incorrect monarchs for " + regnal_name
            assert_true(set(result) == set(full_names))
                
    parts.append((part_3_test, [stu_db, stu_module], 10))
    
    
        
    #tests get_reign_range
    def part_4_test(stu_db, stu_module, error_list):
        
        #check whether the collection exists
        error_list[0] = "No collection named 'british-royals'."
        stu_collection = stu_db['british-royals']
        
        reign_range_pairs = [
            ("Queen Elizabeth I", (1558, 1603)),
            ("King James I", (1603, 1625)),
            ("King Marshmallow I", None)] 
        
        for (royal_name, reign_range) in reign_range_pairs:
        
            error_list[0] = "Threw an exception while calling the function on " + royal_name
            result = stu_module.get_reign_range(stu_collection, royal_name)
            
            if reign_range == None:
                error_list[0] = "Didn't return None for a royal name that didn't reign: " + royal_name
                assert_true(result == None)
                
            else:
                error_list[0] = "Didn't return a 2-tuple for " + royal_name
                assert_true(isinstance(result, tuple))
                assert_true(len(result) == 2)
                
                error_list[0] = "Didn't return a tuple of ints for " + royal_name
                assert_true(isinstance(result[0], int))
                assert_true(isinstance(result[1], int))
                
                error_list[0] = "Incorrect years for " + royal_name
                assert_true(result == reign_range)
                
    parts.append((part_4_test, [stu_db, stu_module], 10))
    
    
        
    #tests get_reign_lengths_by_length
    def part_5_test(stu_db, stu_module, error_list):
        
        #check whether the collection exists
        error_list[0] = "No collection named 'british-royals'."
        stu_collection = stu_db['british-royals']
        
        
    
        current_year = datetime.datetime.now().year
        actual_lengths = [0, 3, 5]
        charles_3_reign = current_year - 2022
        actual_lengths.append(charles_3_reign)
        actual_lengths.sort()
        
        error_list[0] = "Threw an exception while calling the function."
        stu_lengths = stu_module.get_reign_lengths_by_length(stu_collection)
        
        error_list[0] = "Doesn't return a list."
        assert_true(isinstance(stu_lengths, list))
        
        error_list[0] = "Returns the wrong list."
        assert_true(stu_lengths[ : len(actual_lengths)] == actual_lengths)
                
    parts.append((part_5_test, [stu_db, stu_module], 10))
    
    
        
    #tests get_reign_lengths_by_length
    def part_6_test(stu_db, stu_module, error_list):
        
        #check whether the collection exists
        error_list[0] = "No collection named 'british-royals'."
        stu_collection = stu_db['british-royals']
        
        actual_name_possibilities = [
            ['Queen Elizabeth II', 'Queen Victoria', 'King George III']]
        
        error_list[0] = "Threw an exception while calling the function."
        result = stu_module.get_royal_names_by_reign_lengths(stu_collection)
        
        error_list[0] = "Doesn't return a list."
        assert_true(isinstance(result, list))
        
        error_list[0] = "Returns the wrong list."
        assert_true(result[ : len(actual_name_possibilities[0])] in actual_name_possibilities)
                
    parts.append((part_6_test, [stu_db, stu_module], 10))
    
    
        
    #tests get_regnal_name_length
    def part_7_test(stu_db, stu_module, error_list):
        
        current_year = datetime.datetime.now().year
        charles_3_reign = current_year - 2022
        
        #check whether the collection exists
        error_list[0] = "No collection named 'british-royals'."
        stu_collection = stu_db['british-royals']
        
        reign_length_pairs = [
            ("Elizabeth", 115),
            ("Charles", 49 + charles_3_reign),
            ("Monkey", 0)] 
        
        for (regnal_name, reign_length) in reign_length_pairs:
        
            error_list[0] = "Threw an exception while calling the function on " + regnal_name
            result = stu_module.get_regnal_name_length(stu_collection, regnal_name)
            
            error_list[0] = "Doesn't return an int for " + regnal_name
            assert_true(isinstance(result, int))
            
            error_list[0] = "Doesn't return the right result for " + regnal_name
                
    parts.append((part_7_test, [stu_db, stu_module], 5))
    
    
    
    
    #TODO: this should probably be encapsulated into a function.
    for i in range(len(parts)):
        part_tuple = parts[i]
        part_test = part_tuple[0]
        context = part_tuple[1]
        max_points = part_tuple[2]
        
        error_list = ["Blank"]
        #print("Calling test_part...")
        correct = test_part(part_test, context, error_list)
    
        if correct:
            part_points = max_points
            error_message = ""
        else:
            part_points = 0
            error_message = "\n  " + error_list[0]
    
        feedback_text += "Part " + str(i) + ": " + str(part_points) + "/" + str(max_points) + error_message + "\n"
        
        feedback_text += "\n"
        score += part_points
    
    
    

    print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
    print()
    print("Team " + student + ": " + str(score) + "/100\n\n" + feedback_text)
    print()
    
    
    
    
    stu_collection = stu_db['british-royals']