import tkinter as tk
from tkinter import messagebox

class NoteTakingApp:
    def __init__(self, master):
        self.master = master
        self.master.title("Note Taking App")
        
        self.notes = []
        
        self.listbox = tk.Listbox(self.master, width=50, height=20)
        self.listbox.pack(pady=10)
        self.listbox.bind("<<ListboxSelect>>", self.display_note)
        
        self.load_notes()
        
        self.search_entry = tk.Entry(self.master)
        self.search_entry.pack(pady=5)
        
        self.search_button = tk.Button(self.master, text="Search", command=self.search_notes)
        self.search_button.pack()
        
        self.add_button = tk.Button(self.master, text="Add Note", command=self.add_note_window)
        self.add_button.pack()
        
        self.edit_button = tk.Button(self.master, text="Edit Note", command=self.edit_note)
        self.edit_button.pack()
        
        self.delete_button = tk.Button(self.master, text="Delete Note", command=self.delete_note)
        self.delete_button.pack()
        
        self.note_content = tk.Text(self.master, width=50, height=10)
        self.note_content.pack(pady=10)
        
    def search_notes(self):
        query = self.search_entry.get().strip().lower()
        if not query:
            messagebox.showinfo("Info", "Please enter a search query.")
            return
        
        matching_notes = [(title, note) for title, note in self.notes if query in title.lower() or query in note.lower()]
        if matching_notes:
            self.listbox.delete(0, "end")
            for title, _ in matching_notes:
                self.listbox.insert("end", title)
        else:
            messagebox.showinfo("Info", "No matching notes found.")
        
    def add_note_window(self):
        add_note_window = tk.Toplevel(self.master)
        add_note_window.title("Add Note")
        
        title_label = tk.Label(add_note_window, text="Title:")
        title_label.grid(row=0, column=0, padx=10, pady=5)
        
        self.title_entry = tk.Entry(add_note_window)
        self.title_entry.grid(row=0, column=1, padx=10, pady=5)
        
        note_label = tk.Label(add_note_window, text="Note:")
        note_label.grid(row=1, column=0, padx=10, pady=5)
        
        self.note_entry = tk.Text(add_note_window, width=40, height=5)
        self.note_entry.grid(row=1, column=1, padx=10, pady=5)
        
        bold_button = tk.Button(add_note_window, text="Bold", command=lambda: self.apply_formatting("bold"))
        bold_button.grid(row=2, column=0, padx=10, pady=5)
        
        italic_button = tk.Button(add_note_window, text="Italic", command=lambda: self.apply_formatting("italic"))
        italic_button.grid(row=2, column=1, padx=10, pady=5)
        
        bullet_button = tk.Button(add_note_window, text="Bullet", command=lambda: self.apply_formatting("bullet"))
        bullet_button.grid(row=2, column=2, padx=10, pady=5)
        
        save_button = tk.Button(add_note_window, text="Save", command=self.save_note)
        save_button.grid(row=3, columnspan=3, padx=10, pady=5)
        
    def edit_note(self):
        selected_index = self.listbox.curselection()
        if not selected_index:
            messagebox.showerror("Error", "Please select a note to edit.")
            return
        
        selected_note_index = selected_index[0]
        title, note = self.notes[selected_note_index]
        
        edit_note_window = tk.Toplevel(self.master)
        edit_note_window.title("Edit Note")
        
        title_label = tk.Label(edit_note_window, text="Title:")
        title_label.grid(row=0, column=0, padx=10, pady=5)
        
        self.title_entry = tk.Entry(edit_note_window)
        self.title_entry.insert(0, title)
        self.title_entry.grid(row=0, column=1, padx=10, pady=5)
        
        note_label = tk.Label(edit_note_window, text="Note:")
        note_label.grid(row=1, column=0, padx=10, pady=5)
        
        self.note_entry = tk.Text(edit_note_window, width=40, height=5)
        self.note_entry.insert("1.0", note)
        self.note_entry.grid(row=1, column=1, padx=10, pady=5)
        
        bold_button = tk.Button(edit_note_window, text="Bold", command=lambda: self.apply_formatting("bold"))
        bold_button.grid(row=2, column=0, padx=10, pady=5)
        
        italic_button = tk.Button(edit_note_window, text="Italic", command=lambda: self.apply_formatting("italic"))
        italic_button.grid(row=2, column=1, padx=10, pady=5)
        
        bullet_button = tk.Button(edit_note_window, text="Bullet", command=lambda: self.apply_formatting("bullet"))
        bullet_button.grid(row=2, column=2, padx=10, pady=5)
        
        save_button = tk.Button(edit_note_window, text="Save", command=lambda: self.save_edit(selected_note_index))
        save_button.grid(row=3, columnspan=3, padx=10, pady=5)
        
    def display_note(self, event):
        selected_index = self.listbox.curselection()
        if selected_index:
            selected_note_index = selected_index[0]
            title, note = self.notes[selected_note_index]
            self.note_content.delete("1.0", "end")
            self.note_content.insert("1.0", note)
        
    def apply_formatting(self, format_type):
        if format_type == "bold":
            current_tags = self.note_entry.tag_names("sel.first")
            if "bold" in current_tags:
                self.note_entry.tag_remove("bold", "sel.first", "sel.last")
            else:
                self.note_entry.tag_add("bold", "sel.first", "sel.last")
                self.note_entry.tag_configure("bold", font=("TkDefaultFont", 10, "bold"))
        elif format_type == "italic":
            current_tags = self.note_entry.tag_names("sel.first")
            if "italic" in current_tags:
                self.note_entry.tag_remove("italic", "sel.first", "sel.last")
            else:
                self.note_entry.tag_add("italic", "sel.first", "sel.last")
                self.note_entry.tag_configure("italic", font=("TkDefaultFont", 10, "italic"))
        elif format_type == "bullet":
            self.note_entry.insert("insert", "\u2022 ", "bullet")
            self.note_entry.tag_configure("bullet", lmargin1=20)
    
    def save_edit(self, index):
        title = self.title_entry.get().strip()
        note = self.note_entry.get("1.0", "end-1c").strip()
        
        if title == "":
            messagebox.showerror("Error", "Title cannot be empty!")
            return
        
        if note == "":
            messagebox.showerror("Error", "Note cannot be empty!")
            return
        
        self.notes[index] = (title, note)
        self.update_listbox()
        
    def delete_note(self):
        selected_index = self.listbox.curselection()
        if not selected_index:
            messagebox.showerror("Error", "Please select a note to delete.")
            return
        
        selected_note_index = selected_index[0]
        self.notes.pop(selected_note_index)
        self.update_listbox()
        
    def save_note(self):
        title = self.title_entry.get().strip()
        note = self.note_entry.get("1.0", "end-1c").strip()
        
        if title == "":
            messagebox.showerror("Error", "Title cannot be empty!")
            return
        
        if note == "":
            messagebox.showerror("Error", "Note cannot be empty!")
            return
        
        self.notes.append((title, note))
        self.update_listbox()
        self.title_entry.delete(0, "end")
        self.note_entry.delete("1.0", "end")
        
    def update_listbox(self):
        self.listbox.delete(0, "end")
        for title, _ in self.notes:
            self.listbox.insert("end", title)
            
    def load_notes(self):
        # This method can be used to load notes from a file/database at the beginning
        pass

def main():
    root = tk.Tk()
    app = NoteTakingApp(root)
    root.mainloop()

if __name__ == "__main__":
    main()
