import os
import traceback

# --- ImageJ/Java Libraries ---
from ij import IJ
from ij.plugin.frame import RoiManager

from javax.swing import (JDialog, JPanel, JList, JScrollPane, JTextField,
                         JLabel, JCheckBox, JButton, DefaultListModel,
                         ListSelectionModel, BorderFactory, JOptionPane)

from java.awt import BorderLayout, GridLayout
from java.awt.event import WindowAdapter

class ROIEditor(WindowAdapter):
    """ 
    Creates a JFrame with tools for creating, modifying, and managing ROIs 
    for a single image using a "commit on action" model.
    """
    def __init__(self, parent_gui, project, project_image):
        self.parent_gui = parent_gui
        self.project = project
        self.image_obj = project_image
        self.win = None
        self.unsaved_changes = False
        self.updating_fields = False  # Flag to prevent event cascades
        self.last_selected_index = -1 # Track the last selected ROI index

        # Open Image and create canvas and imagewindow to hold it
        self.imp = IJ.openImage(self.image_obj.full_path)
        if not self.imp:
            IJ.error("Failed to open image:", self.image_obj.full_path)
            return
        self.imp.show()
        self.win = self.imp.getWindow()

        # Open a local ROI manager instance
        self.rm = RoiManager(True) 
        self.rm.reset()

        if self.image_obj.has_roi():
            self.rm.runCommand("Open", self.image_obj.roi_path)
            self.rm.runCommand("Show All")

        # Build GUI
        self.base_title = "ROI Editor: " + self.image_obj.filename
        self.frame = JDialog(self.win, self.base_title, False)
        self.frame.setSize(350, 650)
        self.frame.addWindowListener(self)
        self.frame.setLayout(BorderLayout(5, 5))

        # --- GUI Components ---

        # ROI list
        self.roi_list_model = DefaultListModel()
        self.roi_list = JList(self.roi_list_model)
        self.update_roi_list_from_manager()
        self.roi_list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
        self.roi_list.addListSelectionListener(self._on_roi_select)
        list_pane = JScrollPane(self.roi_list)
        list_pane.setBorder(BorderFactory.createTitledBorder("ROIs"))

        # Edit Panel for selected ROI
        edit_panel = JPanel(GridLayout(0, 2, 5, 5))
        edit_panel.setBorder(BorderFactory.createTitledBorder("Edit Selected ROI"))
        self.roi_name_field = JTextField()
        self.bregma_field = JTextField()
        edit_panel.add(JLabel("ROI Name:"))
        edit_panel.add(self.roi_name_field)
        edit_panel.add(JLabel("Bregma Value:"))
        edit_panel.add(self.bregma_field)
        
        self.show_all_checkbox = JCheckBox("Show All ROIs", True)
        self.show_all_checkbox.addActionListener(self._toggle_show_all)
        edit_panel.add(self.show_all_checkbox)
        
        # Button panel for actions
        button_panel = JPanel(GridLayout(0, 1, 10, 10))
        button_panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10))
        create_button = JButton("Create New From Selection", actionPerformed=self._create_new_roi)
        update_button = JButton("Update Selected ROI", actionPerformed=self._update_selected_roi)
        delete_button = JButton("Delete Selected ROI", actionPerformed=self._delete_selected_roi)
        self.ready_checkbox = JCheckBox("Mark as 'ROIs completed'", True)
        is_ready = (self.image_obj.status == "ROIs completed")
        self.ready_checkbox.setSelected(is_ready)
        self.ready_checkbox.addActionListener(self._toggle_ready_status)
        save_button = JButton("Save All ROIs & Close", actionPerformed=self._save_and_close)
        button_panel.add(create_button)
        button_panel.add(update_button)
        button_panel.add(delete_button)
        button_panel.add(self.ready_checkbox)
        button_panel.add(save_button)

        # Main layout
        south_panel = JPanel(BorderLayout())
        south_panel.add(edit_panel, BorderLayout.NORTH)
        south_panel.add(button_panel, BorderLayout.CENTER)
        self.frame.add(list_pane, BorderLayout.CENTER)
        self.frame.add(south_panel, BorderLayout.SOUTH)

    def show(self):
        if not self.frame or not self.win:
            return
        img_win_x = self.win.getX()
        img_win_width = self.win.getWidth()
        img_win_y = self.win.getY()
        self.frame.setLocation(img_win_x + img_win_width, img_win_y)
        self.frame.setVisible(True)
    
    # --------------------------------------------------------------------------
    # Core Logic and Event Handling
    # --------------------------------------------------------------------------

    def _commit_changes_for_index(self, index, commit_geometry=True):
        """
        A single, unified method to save changes for the ROI at a given index.
        The commit_geometry flag prevents overwriting geometry during new ROI creation.
        Returns True if the ROI name was changed.
        """
        if index < 0 or index >= self.rm.getCount():
            return False

        name_was_changed = False
        try:
            manager_roi = self.rm.getRoi(index)
            if not manager_roi: 
                return False

            # Part 1: Commit metadata (text fields)
            new_name = self.roi_name_field.getText().strip()
            new_bregma = self.bregma_field.getText().strip()
            current_bregma = str(manager_roi.getProperty("comment") or "")

            if manager_roi.getName() != new_name and new_name:
                self.rm.rename(index, new_name)
                self._set_unsaved_changes(True)
                name_was_changed = True
            
            if new_bregma != current_bregma:
                if new_bregma:
                    try:
                        float(new_bregma)
                        manager_roi.setProperty("comment", new_bregma)
                    except ValueError:
                        JOptionPane.showMessageDialog(self.frame, "Bregma must be a number.", "Invalid Input", JOptionPane.WARNING_MESSAGE)
                        self.bregma_field.setText(current_bregma)
                else:
                    manager_roi.setProperty("comment", None)
                self._set_unsaved_changes(True)

            # Part 2: Commit geometry (shape on canvas)
            if commit_geometry:
                image_roi = self.imp.getRoi()
                if image_roi:
                    updated_roi = image_roi.clone()
                    # Preserve the name and properties from the manager ROI
                    current_manager_roi = self.rm.getRoi(index)
                    updated_roi.setName(current_manager_roi.getName()) 
                    updated_roi.setProperty("comment", current_manager_roi.getProperty("comment"))
                    self.rm.setRoi(updated_roi, index)
                    self._set_unsaved_changes(True)
                
        except Exception as e:
            IJ.log("Error during commit for index {}: {}".format(index, str(e)))
            traceback.print_exc()
        
        return name_was_changed

    def _on_roi_select(self, event):
        """ Handles a user manually selecting an ROI in the list. """
        if event.getValueIsAdjusting() or self.updating_fields:
            return

        # Don't commit changes when switching ROIs - we only want to commit
        # when explicitly saving or when the user is editing an existing ROI
        # (not when creating new ones)

        selected_index = self.roi_list.getSelectedIndex()
        if selected_index != -1:
            self.updating_fields = True
            try:
                self._refresh_roi_display(selected_index)
                selected_roi = self.rm.getRoi(selected_index)
                if selected_roi:
                    self.roi_name_field.setText(selected_roi.getName() or "")
                    bregma_prop = selected_roi.getProperty("comment")
                    if bregma_prop is not None:
                        self.bregma_field.setText(str(bregma_prop))
                    else:
                        self.bregma_field.setText("")
            finally:
                self.updating_fields = False
        else:
            self.roi_name_field.setText("")
            self.bregma_field.setText("")
        
        self.last_selected_index = selected_index
        
    # --------------------------------------------------------------------------
    # GUI Actions (Buttons & Checkboxes)
    # --------------------------------------------------------------------------

    def _create_new_roi(self, event):
        """
        Handles the entire ROI creation process as a self-contained transaction,
        preventing event listener conflicts.
        """
        # Step 1: Validate that a new ROI can be created.
        current_roi = self.imp.getRoi()
        if not current_roi:
            JOptionPane.showMessageDialog(self.frame, "Please draw a selection on the image first.", "No Selection", JOptionPane.WARNING_MESSAGE)
            return
        
        new_name = self.roi_name_field.getText().strip()
        if not new_name:
            JOptionPane.showMessageDialog(self.frame, "Please enter a name in the 'ROI Name' field.", "No Name Provided", JOptionPane.WARNING_MESSAGE)
            return

        # Step 2: Save the current selection and create the new ROI object
        roi_clone = current_roi.clone()
        roi_clone.setName(new_name)
        
        bregma_value = self.bregma_field.getText().strip()
        if bregma_value:
            try:
                float(bregma_value)
                roi_clone.setProperty("comment", bregma_value)
            except ValueError: 
                pass

        # Step 3: DON'T commit changes to the previous ROI
        # The text fields now contain data for the NEW roi, not the old one
        # So committing would overwrite the old ROI's name/bregma with the new ROI's data
        
        # Step 4: Add the new ROI to the manager
        self.rm.addRoi(roi_clone)
        new_index = self.rm.getCount() - 1
        
        # Step 5: Update the UI to reflect the new ROI
        try:
            self.updating_fields = True
            
            # Update the list display
            self.update_roi_list_from_manager()
            
            # Select the newly created ROI
            self.roi_list.setSelectedIndex(new_index)
            
            # Clear the text fields for the next ROI entry
            self.roi_name_field.setText("")
            self.bregma_field.setText("")
            
            # Update the tracking index
            self.last_selected_index = new_index
            
            # Refresh the display to show the new ROI
            self._refresh_roi_display(new_index)
            
        finally:
            self.updating_fields = False

        # Step 6: Finalize the creation process
        self._set_unsaved_changes(True)
        

    def _update_selected_roi(self, event):
        """Updates the currently selected ROI with values from the text fields."""
        selected_index = self.roi_list.getSelectedIndex()
        if selected_index == -1:
            JOptionPane.showMessageDialog(self.frame, "Please select an ROI from the list to update.", "No ROI Selected", JOptionPane.WARNING_MESSAGE)
            return
        
        # Commit both metadata and geometry changes
        if self._commit_changes_for_index(selected_index, commit_geometry=True):
            self.update_roi_list_from_manager()
        
        # Refresh to show the updated ROI
        self._refresh_roi_display(selected_index)
        


    def _delete_selected_roi(self, event):
        """Deletes the selected ROI from the manager."""
        selected_index = self.roi_list.getSelectedIndex()
        if selected_index == -1:
            JOptionPane.showMessageDialog(self.frame, "Please select an ROI from the list to delete.", "No ROI Selected", JOptionPane.WARNING_MESSAGE)
            return

        roi_name = self.rm.getRoi(selected_index).getName() or "Untitled"
        result = JOptionPane.showConfirmDialog(self.frame, "Delete ROI '{}'?".format(roi_name), "Confirm Deletion", JOptionPane.YES_NO_OPTION)
        if result != JOptionPane.YES_OPTION:
            return

        self.rm.select(selected_index)
        self.rm.runCommand("Delete")
        
        self.last_selected_index = -1
        self.update_roi_list_from_manager()
        self.roi_name_field.setText("")
        self.bregma_field.setText("")
        self._set_unsaved_changes(True)
        self.imp.deleteRoi()

    def _toggle_ready_status(self, event):
        """Updates the image's status in the project object."""
        self.image_obj.status = "ROIs completed" if self.ready_checkbox.isSelected() else "In Progress"
        self._set_unsaved_changes(True)
        
    def _save_and_close(self, event=None):
        """Saves all changes, updates project, and closes the editor."""
        # No need to commit individual ROI changes - all ROIs are already in the manager
        # and will be saved to the file
        
        if not self._save_all_rois_to_file():
            return

        if not self.project.sync_project_db():
            JOptionPane.showMessageDialog(self.frame, "Could not save project databases.", "Database Sync Failed", JOptionPane.ERROR_MESSAGE)
            return

        self.parent_gui.update_view_for_image(self.image_obj)
        self.parent_gui.set_unsaved_changes(True)
        self._set_unsaved_changes(False)
        self.cleanup()
        
    # --------------------------------------------------------------------------
    # Helper & Utility Methods
    # --------------------------------------------------------------------------
    
    def update_roi_list_from_manager(self):
        """Syncs the JList with the IJ ROI manager without triggering selection events."""
        listeners = self.roi_list.getListSelectionListeners()
        for l in listeners: 
            self.roi_list.removeListSelectionListener(l)
        try:
            current_selection = self.roi_list.getSelectedIndex()
            self.roi_list_model.clear()
            for i, roi in enumerate(self.rm.getRoisAsArray()):
                self.roi_list_model.addElement("{}. {}".format(i + 1, roi.getName() or "Untitled"))
            if -1 < current_selection < self.roi_list_model.getSize():
                self.roi_list.setSelectedIndex(current_selection)
        finally:
            for l in listeners: 
                self.roi_list.addListSelectionListener(l)

    def _refresh_roi_display(self, selected_index):
        """Loads and displays the selected ROI using the Manager's native select method."""
        # Enforce "Show All" state
        if self.show_all_checkbox.isSelected():
            self.rm.runCommand("Show All")
        else:
            self.rm.runCommand("Show None")

        # Then select the specific ROI to make it active and editable
        if -1 < selected_index < self.rm.getCount():
            self.rm.select(selected_index)
        else:
            self.imp.deleteRoi()

    def _save_all_rois_to_file(self):
        """Validates and saves ROI data to a .zip file."""
        rois = self.rm.getRoisAsArray()
        for i, roi in enumerate(rois):
            if not roi.getName() or not roi.getName().strip():
                JOptionPane.showMessageDialog(self.frame, "ROI #{} has no name. Please name all ROIs.".format(i+1), "Validation Error", JOptionPane.WARNING_MESSAGE)
                return False
        
        self.image_obj.rois = [{'roi_name': r.getName(), 'bregma': r.getProperty("comment") or 'N/A'} for r in rois]
        roi_dir = os.path.dirname(self.image_obj.roi_path)
        if not os.path.exists(roi_dir):
            os.makedirs(roi_dir)
            
        self.rm.runCommand("Save", self.image_obj.roi_path)
        return True

    def _toggle_show_all(self, event):
        """Toggles visibility of all ROIs."""
        self._refresh_roi_display(self.roi_list.getSelectedIndex())

    def _set_unsaved_changes(self, state):
        """Updates the UI to show if there are unsaved changes."""
        self.unsaved_changes = state
        self.frame.setTitle(self.base_title + (" *" if state else ""))

    def cleanup(self):
        """Closes all associated windows and resources."""
        if self.imp: 
            self.imp.close()
        if self.rm: 
            self.rm.close()
        if self.frame: 
            self.frame.dispose()

    def windowClosing(self, event):
        """Handles the window 'X' button with a save confirmation."""
        if self.unsaved_changes:
            result = JOptionPane.showConfirmDialog(self.frame, "You have unsaved changes. Save before closing?", "Unsaved Changes", JOptionPane.YES_NO_CANCEL_OPTION)
            if result == JOptionPane.YES_OPTION:
                self._save_and_close()
            elif result == JOptionPane.NO_OPTION:
                self.cleanup()
        else:
            self.cleanup()