class MyClass(js.JsonSaveable):

    x = js.Int()
    y = js.Float()
    lst = js.List()

    def __init__(self, x, lst):
        self.x = x
        self.lst = lst
class Donor(js.JsonSaveable):
    _name = js.String()
    _donations = js.List()

    def __init__(self, name, donations=None):
        self.name = name
        if donations == None:
            self._donations = []
        else:
            self._donations = list(donations)
#        self.donations = donations

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        self._name = value

    @property
    def donations(self):
        return self._donations

    def add_donation(self, amount):
        try:
            self.donations.append(float(amount))
        # Handle error if user doesn't input a valid numerical donation
        except ValueError:
            print('Not a valid donation.')
            prompt_donation(self.name)

    # Create email to donor thanking them for their generous donation
    def create_email(self, amount):
        return '\nDear {},\n\nThank you so much for generous donation of ${}.\n\n\t\t\tSincerely,\n\t\t\tPython Donation Team'.format(
            self.name, amount)

    def sum_donations(self):
        return sum(self.donations)

    def number_donations(self):
        return len(self.donations)

    def avg_donations(self):
        return self.sum_donations() / self.number_donations()

    def multiply_donations(self, factor):
        self._donations = list(map(lambda x: x * factor, self.donations))
        return self._donations

    def filter_donations(self, min_donation=0, max_donation=99999999999999999):
        self._donations = list(
            filter(lambda x: int(min_donation) <= x <= int(max_donation),
                   self.donations))
        return self._donations
class DonorGroup(js.JsonSaveable):
    """Donor group initialized with first Donor class
    Donors are kept in a dictionary created in DonorGroup"""
    donor_list_js = js.List()
    donor_dict_js = js.Dict()

    def __init__(self, donor=None, file_path=None):
        self.donor_list_js = [donor.fullname_js]
        self.donor_dict_js = {donor.fullname_js: donor.donations_js}
        self.file_path = file_path
        
    def add_donor(self, new_donor):
        self.donor_list_js.append(new_donor)
        self.donor_dict_js[new_donor.fullname_js] = new_donor.donations_js
        return self.donor_list_js

    @property
    def create_report(self):
        rows = [(donor, sum(self.donor_dict_js[donor]),len(self.donor_dict_js[donor]),np.mean(self.donor_dict_js[donor])) for donor in self.donor_dict_js]

        #rows.sort(key = takeSecond, reverse = True)

        print('{:<20s} |{:>15s}|{:>12s} |{:>15s}'.format('Donor Name','Total Given','Num Gifts','Average Gift'))
        for i in ['{:<20s} ${:15,.2f} {:12d} ${:15,.2f}'.format(*row) for row in rows]:
            print(i)

        return "report created"

    @property
    def send_letters(self):
        return self.file_path

    @send_letters.setter
    def send_letters(self, letter_dir):

        self.file_path = letter_dir
        
        try:
            os.chdir(self.file_path)
        except FileNotFoundError:
            print("\nNOT A VALID FILE PATH")

        for donor in self.donor_dict_js:
            
            letter_text = f"""Dear {donor}, \n\nThank you for your generous donation of ${sum(self.donor_dict_js[donor]):,.2f}. Thanks to you we will finally be able to begin construction on the children's wing at Dark Place Hospital\n\nYours Truly,\nGarth Marenghi"""

            with open(donor.replace(" ","_").lower() + '_donations.txt', 'w') as donation_letter:
                donation_letter.write(letter_text)   

    def challenge(self, factor, min_donation=None, max_donation=None):
        get_donations = [[globals()[d.split(" ")[0].lower()].fullname, globals()[d.split(" ")[0].lower()].donation] for d in self.donor_list_js]
        
        if min_donation and max_donation:
            filtered_donations = [[d[0], list(filter(lambda x: x >= min_donation and x <= max_donation, d[1]))] for d in get_donations]
        elif min_donation:
            filtered_donations = [[d[0], list(filter(lambda x: x >= min_donation, d[1]))] for d in get_donations]
        elif max_donation:
            filtered_donations = [[d[0], list(filter(lambda x: x <= max_donation, d[1]))] for d in get_donations]
        else:
            filtered_donations = get_donations
        
        new_donors = [Donor(d[0], list(map(lambda x: x * factor, d[1]))) for d in filtered_donations]

        #new donor group created 
        dg_factored = DonorGroup(new_donors[0]) 

        d = 1

        while d + 1 <= len(new_donors):
            dg_factored.add_donor = new_donors[d]
            d += 1               
                
        return dg_factored

    def projection(self, factor, min_donation = None, max_donation = None):
        dg_factored = self.challenge(factor, min_donation, max_donation)

        contribution = sum([sum(dg_factored.donor_dict_js[d]) for d in dg_factored.donor_list_js])

        return contribution 
    
    def __repr__(self):
        return "Donor Group: {}".format(self.donor_list_js)

    #load donors from JSON file 
    def load_donors_json(self):
        with open('donor_db.json','r') as donor_file:
            donor_db = json.load(donor_file)
            print('donor db loaded')
        return donor_db

    #save donor dict to json file
    def save_donors_json(self):
        donors_json = json.dumps(self.donor_dict_js)
        with open('donor_db.json', 'w') as donor_file:
            donor_file.write(donors_json)
            print('JSON file created')    
class Donor(js.JsonSaveable):
    """takes full name and creates donor record
    need a donor and a donation to create a donor record"""
    first_js = js.String()
    last_js = js.String()
    fullname_js = js.String()
    donations_js = js.List()

    def __init__(self, fullname, donations=None):
        if len(fullname.split(" ")) == 1:
            first = "Mr/Ms"
            last = fullname.split(" ")[0]
        elif len(fullname.split(" ")) == 2:
            first, last = fullname.split(" ")
        else:
            first = fullname.split(" ")[0]
            last = fullname.split(" ")[len(fullname.split(" "))-1]
        self.first_js = first
        self.last_js = last
        self.fullname_js = fullname
        
        if donations:
            self.donations_js = donations
        else:
            self.donations_js = []

    @classmethod
    def add_donation(cls, new_donation):
        cls.donations_js.append(new_donation)
        return cls.donations_js

    @property
    def sum_donations(self):
        return sum(self.donations_js)
    
    @property
    def count_donations(self):
        return len(self.donations_js)

    @property
    def avg_donations(self):
        return sum(self.donations_js) / len(self.donations_js)
      
    def thank_you(self):
        """sends thank you for latest donation"""
        newest_donation = self.donations_js[len(self.donations_js)-1]        
        thankyou =  f"""\nDear {self.first_js}, \n\nThank you for your generous donation of ${newest_donation:,.2f}. Thanks to you, we will finally be able to begin construction on the {self.last_js} Memorial Children's wing at The Dark Place Hospital\n\nYours Truly,\nGarth Marenghi\n"""
        print(thankyou)    
    
    def __lt__(self, other):
        return self.sum_donations < other.sum_donations

    def __gt__(self, other):
        return self.sum_donations > other.sum_donations

    def __eq__(self, other):
        return self.sum_donations == other.sum_donations

    def __ne__(self, other):
        return self.sum_donations != other.sum_donations

    def __repr__(self):
        return "Donor Record: {}, {}".format(self.last_js, self.first_js)
class DonorCollection(js.JsonSaveable):
    _donors = js.List()

    def __init__(self, donors=None):
        if donors == None:
            donors = []
        else:
            self._donors = donors

    @property
    def donors(self):
        return self._donors

    # Display list of donors by name
    def donor_list(self):
        list_donors = ''
        for donor in self.donors:
            list_donors += donor.name + '\n'
        return list_donors

    # Set donor
    def set_donor(self, full_name):
        exists = False
        # Check if existing donor
        for donor in self.donors:
            if donor.name == full_name:
                exists = True
                break
        # Not existing donor, so create new donor
        if not exists:
            donor = Donor(full_name)
            donors.add_donor(donor)
        return donor

    # Add new donor to donor collection
    def add_donor(self, donor):
        self.donors.append(donor)

    def sort_on_total_donation(self):
        return (sorted(self.donors, key=total_donation_key, reverse=True))

    # Create report
    def create_report(self):
        #Create list of summarized donations so that total can be sorted
        sorted_donors = donors.sort_on_total_donation()

        # Print summarized data
        report = '\nDonor Name                | Total Given | Num Gifts | Average Gift\n'
        report += '-' * 66 + '\n'
        for donor in sorted_donors:
            if donor.donations != []:
                report += f'{donor.name: <27}${donor.sum_donations(): >12.2f}{donor.number_donations(): >12}  ${round(donor.avg_donations(),2): >11.2f}\n'
        print(report)
        return report

    # Send letters to everyone
    def send_letters(self):
        now = datetime.datetime.now()
        now = str(now.year) + '-' + str(now.month) + "-" + str(now.day)
        path = os.getcwd() + '/letters'

        # Change directory to letters directory, if it doesn't exist, create it
        try:
            os.chdir(path)
        except FileNotFoundError:
            os.makedirs(path)
            os.chdir(path)

        # Loop through each donor and send thank you email
        try:
            for donor in self.donors:
                with open(donor.name + '_' + str(now) + '.txt',
                          'w') as outfile:
                    print(donor.name)
                    outfile.write(donor.create_email(donor.donations[-1]))

            print('\nThe thank you emails were sent!')
        except:
            print('\nThere was an error sending the thank you emails.')

    # Apply multiplication factor to each donor list
    def challenge(self, factor=3, min_donation=0, max_donation=99999999999999):
        donors_copy = deepcopy(self.donors)
        for donor in donors_copy:
            donor.filter_donations(min_donation, max_donation)
            donor.multiply_donations(factor)
        return donors_copy

    @classmethod
    # Load donors from json file
    def load_donors(cls):
        with open('donors.json', 'r') as file:
            donor_load = json.load(file)
            donors = cls.from_json_dict(donor_load)
            print('Donors JSON loaded')
        return donors

    @classmethod
    # Save donors to json file
    def save_donors(cls):
        donor_save = donors.to_json()
        with open('donors.json', 'w') as file:
            file.write(donor_save)
            print('Donors JSON saved')
        return donors