import tkinter as tk
from tkinter import scrolledtext, filedialog, messagebox
import sys
import io
import math
import random

# --- Core Execution Logic for 'Prebuilt' Modules ---

def create_execution_environment():
    """Creates a global namespace for the user's code, pre-loading modules."""
    
    # 1. Standard library imports (guaranteed to work)
    env = {
        'math': math,
        'random': random,
        'sqrt': math.sqrt,
        'randint': random.randint,
        'print': print, 
        '__name__': '__main__', 
    }

    # 2. Mock or attempted imports for complex libraries (PyQt5, PyQt6)
    try:
        from PyQt5.QtWidgets import QApplication, QWidget
        env['PyQt5'] = sys.modules['PyQt5']
    except ImportError:
        env['PyQt5'] = 'PyQt5_MOCK: Module not installed or accessible in this environment.'

    try:
        from PyQt6.QtWidgets import QApplication as QApp6, QWidget as QWidget6
        env['PyQt6'] = sys.modules['PyQt6']
    except ImportError:
        env['PyQt6'] = 'PyQt6_MOCK: Module not installed or accessible in this environment.'

    return env

# --- Main IDE Application Class ---

class SimpleIDE(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Simple Python IDE (Tkinter)")
        self.geometry("1000x700")
        self.configure(bg="#2c3e50") # Dark background

        self.execution_env = create_execution_environment()
        
        # Line Limit State (Default to 100)
        self.max_lines_var = tk.StringVar(self)
        self.max_lines_var.set("100") 
        self.max_lines = 100 
        
        self.setup_ui()
        self.bind_events()
        # Initial line number generation is safe now, as recursion is fixed.
        self.update_line_numbers() 

    def setup_ui(self):
        """Sets up the main layout and widgets, including the new line limit selector."""
        
        # --- Configure Grid Layout ---
        self.grid_rowconfigure(0, weight=1) 
        self.grid_rowconfigure(1, weight=0) 
        self.grid_rowconfigure(2, weight=0) 
        self.grid_rowconfigure(3, weight=1) 
        self.grid_columnconfigure(0, weight=0) 
        self.grid_columnconfigure(1, weight=1) 

        # --- 1. Code Editor and Line Numbers Frame ---
        self.editor_frame = tk.Frame(self, bg="#34495e")
        self.editor_frame.grid(row=0, column=0, columnspan=2, sticky='nsew', padx=10, pady=(10, 5))
        self.editor_frame.grid_rowconfigure(0, weight=1)
        self.editor_frame.grid_columnconfigure(1, weight=1)

        # 1a. Line Numbers
        self.line_numbers = tk.Text(self.editor_frame, width=4, padx=4, pady=3,
                                    font=("Monospace", 12),
                                    bg="#34495e", fg="#ecf0f1", state='disabled',
                                    cursor="arrow", bd=0)
        self.line_numbers.grid(row=0, column=0, sticky='ns')

        # 1b. Code Editor
        self.code_editor = scrolledtext.ScrolledText(self.editor_frame, wrap='none',
                                                    font=("Monospace", 12),
                                                    bg="#2c3e50", fg="#ecf0f1",
                                                    insertbackground="#ecf0f1",
                                                    undo=True, bd=0)
        self.code_editor.grid(row=0, column=1, sticky='nsew')
        # Initial placeholder code
        self.code_editor.insert(tk.END, "# Welcome to the Simple Python IDE!\n"
                                        "# The 'math' and 'random' modules are prebuilt (no import needed).\n"
                                        "# Current Line Limit is set to 100 lines by default.\n"
                                        "result_sqrt = sqrt(81)\n"
                                        "print(f\"Square Root: {result_sqrt}\")\n")

        # --- 2. Buttons Frame and Line Limit Selector ---
        self.button_frame = tk.Frame(self, bg="#2c3e50")
        self.button_frame.grid(row=1, column=0, columnspan=2, sticky='ew', padx=10, pady=5)
        
        # Run Button
        tk.Button(self.button_frame, text="Run Code", command=self.run_code,
                  bg="#2ecc71", fg="white", font=("Arial", 11, "bold"), relief='flat', padx=15, pady=5).pack(side='left', padx=10)
        
        # Clear Output Button
        tk.Button(self.button_frame, text="Clear Output", command=lambda: self.output_text.delete(1.0, tk.END),
                  bg="#f39c12", fg="white", font=("Arial", 11, "bold"), relief='flat', padx=15, pady=5).pack(side='left', padx=10)

        # Line Limit Feature
        limit_options = ["12", "40", "100", "Unlimited"]
        
        tk.Label(self.button_frame, text="Max Lines:", bg="#2c3e50", fg="#ecf0f1", font=("Arial", 11)).pack(side='left', padx=(30, 5))
        
        limit_menu = tk.OptionMenu(self.button_frame, self.max_lines_var, *limit_options, command=self.set_line_limit)
        limit_menu.config(bg="#34495e", fg="#ecf0f1", activebackground="#4e6e8e", activeforeground="white", relief='flat')
        limit_menu["menu"].config(bg="#34495e", fg="#ecf0f1")
        limit_menu.pack(side='left', padx=5)

        # --- 3. Output Console ---
        tk.Label(self, text="Output Console:", bg="#2c3e50", fg="#ecf0f1", font=("Arial", 10, "bold")).grid(row=2, column=0, columnspan=2, sticky='nw', padx=10)
        self.output_text = scrolledtext.ScrolledText(self, wrap='word', height=10,
                                                     font=("Monospace", 10),
                                                     bg="#34495e", fg="#ecf0f1",
                                                     insertbackground="#ecf0f1",
                                                     state='disabled', bd=0)
        self.output_text.grid(row=3, column=0, columnspan=2, sticky='nsew', padx=10, pady=(0, 10))


    def set_line_limit(self, value):
        """Updates the internal line limit and enforces it on the current content."""
        if value == "Unlimited":
            self.max_lines = float('inf')
        else:
            self.max_lines = int(value)
            
        # Immediately check and potentially trim existing content
        self.update_line_numbers() 

    def bind_events(self):
        """Binds events for synchronization and functionality."""
        # Sync line numbers on scroll
        self.code_editor.bind('<MouseWheel>', self.sync_scroll)
        self.code_editor.bind('<Button-4>', self.sync_scroll) 
        self.code_editor.bind('<Button-5>', self.sync_scroll) 
        
        # Sync line numbers and enforce limit on content change (typing, paste)
        self.code_editor.bind('<KeyRelease>', self.update_line_numbers)
        self.code_editor.bind('<FocusIn>', self.update_line_numbers)
        self.code_editor.bind('<<Undo>>', self.update_line_numbers)
        self.code_editor.bind('<<Redo>>', self.update_line_numbers)
        
        # Prevent new line creation if limit reached (for 'Return' key)
        self.code_editor.bind('<KeyPress>', self.check_key_limit)

        # Bind the editor's scrollbar command to also call sync_scroll
        self.code_editor.vbar.config(command=self.sync_editor_vscroll)

    def check_key_limit(self, event):
        """Prevents adding new lines using the Enter key if the limit is reached."""
        if self.max_lines == float('inf'):
            return None 
        
        if event.keysym == "Return":
            # Check the current number of lines BEFORE the new line is added
            current_line_count = int(self.code_editor.index(tk.END).split('.')[0]) - 1
            
            if current_line_count >= self.max_lines:
                messagebox.showwarning("Line Limit Reached", 
                                       f"Cannot add a new line. The editor is limited to {self.max_lines} lines.", 
                                       parent=self)
                return "break" # Suppress the 'Return' event

        return None # Allow the event to proceed

    def sync_scroll(self, event=None):
        """Synchronizes the scrolling of the line numbers with the editor."""
        code_scroll_position = self.code_editor.yview()
        self.line_numbers.yview_moveto(code_scroll_position[0])
        
        if event and event.type != '4': 
            return 'break' 

    def sync_editor_vscroll(self, *args):
        """Helper function to synchronize scroll when using the editor's scrollbar."""
        self.code_editor.yview(*args)
        self.sync_scroll()

    def update_line_numbers(self, event=None):
        """Generates and updates the line number display, and enforces the line limit (especially for paste events)."""
        
        # Guard against unwanted updates from navigation keys which don't change content
        if event and event.keysym in ('Up', 'Down', 'Left', 'Right', 'Shift_L', 'Shift_R', 'Control_L', 'Control_R', 'Alt_L', 'Alt_R'):
            return

        # 1. Get the current number of lines in the content
        final_line_index = self.code_editor.index(tk.END)
        current_line_count = int(final_line_index.split('.')[0]) - 1

        # --- Line Limit Enforcement (Trimming for Paste) ---
        if self.max_lines != float('inf') and current_line_count > self.max_lines:
            # Calculate the index of the line right after the max limit
            trim_index = f"{self.max_lines + 1}.0"
            
            # Trim the content
            self.code_editor.delete(trim_index, tk.END)
            
            # Recalculate line count after trimming
            final_line_index = self.code_editor.index(tk.END)
            current_line_count = int(final_line_index.split('.')[0]) - 1
            
            # Show a warning only if the limit was breached
            messagebox.showwarning("Content Truncated", 
                                   f"The code was automatically trimmed to meet the current limit of {self.max_lines} lines.", 
                                   parent=self)
            
        # 2. Generate the line number text
        line_num_content = '\n'.join(str(i) for i in range(1, current_line_count + 1))
        
        # 3. Update the line numbers widget
        self.line_numbers.config(state='normal')
        self.line_numbers.delete(1.0, tk.END)
        self.line_numbers.insert(1.0, line_num_content)
        self.line_numbers.config(state='disabled')
        
        # 4. Sync the scroll position after update
        self.sync_scroll()
        
    def write_output(self, text):
        """Writes text to the output console and scrolls to the end."""
        self.output_text.config(state='normal')
        self.output_text.insert(tk.END, text)
        self.output_text.see(tk.END)
        self.output_text.config(state='disabled')

    def run_code(self):
        """Executes the code from the editor in the custom environment."""
        
        code_to_run = self.code_editor.get(1.0, tk.END)
        
        self.output_text.config(state='normal')
        self.output_text.delete(1.0, tk.END)
        self.output_text.config(state='disabled')
        self.write_output("--- Running Code ---\n")

        old_stdout = sys.stdout
        redirected_output = io.StringIO()
        sys.stdout = redirected_output

        try:
            exec(code_to_run, self.execution_env)
            
            output = redirected_output.getvalue()
            self.write_output(output)
            self.write_output("\n--- Execution Finished Successfully ---\n")
            
        except Exception as e:
            self.write_output(f"!!! Error during execution:\n{type(e).__name__}: {e}\n")
            
        finally:
            sys.stdout = old_stdout

if __name__ == '__main__':
    app = SimpleIDE()
    app.mainloop()

