TextAnalysisPanel.java

package com.kapil.verbametrics.ui.panels;

import com.kapil.verbametrics.domain.ReadabilityMetrics;
import com.kapil.verbametrics.domain.SentimentScore;
import com.kapil.verbametrics.dto.TextAnalysisResponse;
import com.kapil.verbametrics.services.BasicTextAnalysisService;
import com.kapil.verbametrics.services.ReadabilityAnalysisService;
import com.kapil.verbametrics.services.SentimentAnalysisService;
import com.kapil.verbametrics.ui.controller.TextAnalysisController;
import com.kapil.verbametrics.ui.util.FileOperationsUtil;
import com.kapil.verbametrics.ui.util.UIStateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ConfigurableApplicationContext;

import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.IOException;

import static com.kapil.verbametrics.ui.util.GuiConstants.*;

/**
 * Panel for text analysis functionality.
 *
 * @author Kapil Garg
 */
public class TextAnalysisPanel extends JPanel {

    private static final Logger LOGGER = LoggerFactory.getLogger(TextAnalysisPanel.class);

    private final TextAnalysisController controller;

    private final JTextArea inputArea = new JTextArea(TEXTAREA_ROWS, TEXTAREA_COLS);
    private final JTextArea outputArea = new JTextArea(TEXTAREA_ROWS, TEXTAREA_COLS);

    private final JButton analyzeBtn = new JButton(ANALYZE);
    private final JButton clearBtn = new JButton(CLEAR_TEXT);
    private final JButton loadBtn = new JButton(LOAD_FROM_FILE);
    private final JButton sampleBtn = new JButton(LOAD_SAMPLE_TEXT);
    private final JButton exportBtn = new JButton(EXPORT_RESULTS);
    private final JLabel charCountLabel = new JLabel("Characters: 0");

    /**
     * Constructor to initialize the text analysis panel.
     *
     * @param ctx the Spring application context
     */
    public TextAnalysisPanel(ConfigurableApplicationContext ctx) {
        this.controller = new TextAnalysisController(
                ctx.getBean(BasicTextAnalysisService.class),
                ctx.getBean(SentimentAnalysisService.class),
                ctx.getBean(ReadabilityAnalysisService.class)
        );
        buildUi();
    }

    /**
     * Build the UI components and layout.
     */
    private void buildUi() {
        setLayout(new BorderLayout());
        JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, createInputSection(), createOutputPanel());
        splitPane.setResizeWeight(0.55);
        add(splitPane, BorderLayout.CENTER);
        registerActions();
    }

    /**
     * Create the input section with text area and buttons.
     *
     * @return the input section panel
     */
    private JPanel createInputSection() {
        JPanel inputSection = new JPanel(new BorderLayout());
        inputSection.add(createInputPanel(), BorderLayout.CENTER);
        inputSection.add(createButtonPanel(), BorderLayout.SOUTH);
        return inputSection;
    }

    /**
     * Create the input text panel.
     *
     * @return the input text panel
     */
    private JPanel createInputPanel() {
        JPanel inputPanel = new JPanel(new BorderLayout());
        inputPanel.setBorder(BorderFactory.createTitledBorder("Input Text"));
        inputArea.setLineWrap(true);
        inputArea.setWrapStyleWord(true);
        inputPanel.add(new JScrollPane(inputArea), BorderLayout.CENTER);
        return inputPanel;
    }

    /**
     * Create the button panel with action buttons and character count.
     *
     * @return the button panel
     */
    private JPanel createButtonPanel() {
        JPanel buttonPanel = new JPanel();
        buttonPanel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
        for (JButton btn : new JButton[]{analyzeBtn, clearBtn, sampleBtn, loadBtn, exportBtn}) {
            buttonPanel.add(btn);
            buttonPanel.add(Box.createHorizontalStrut(10));
        }
        buttonPanel.remove(buttonPanel.getComponentCount() - 1);
        buttonPanel.add(Box.createHorizontalStrut(20));
        buttonPanel.add(charCountLabel);
        return buttonPanel;
    }

    /**
     * Create the output panel to display results.
     *
     * @return the output panel
     */
    private JPanel createOutputPanel() {
        JPanel outputPanel = new JPanel(new BorderLayout());
        outputPanel.setBorder(BorderFactory.createTitledBorder("Results"));
        outputArea.setEditable(false);
        outputArea.setLineWrap(true);
        outputArea.setWrapStyleWord(true);
        outputPanel.add(new JScrollPane(outputArea), BorderLayout.CENTER);
        return outputPanel;
    }

    /**
     * Register action listeners for buttons and keyboard shortcuts.
     */
    private void registerActions() {
        setupButtonListeners();
        setupKeyboardShortcuts();
        setupDocumentListeners();
    }

    /**
     * Setup button action listeners.
     */
    private void setupButtonListeners() {
        analyzeBtn.addActionListener(e -> onAnalyze());
        clearBtn.addActionListener(e -> onClear());
        sampleBtn.addActionListener(e -> fillSample());
        loadBtn.addActionListener(e -> onLoadFromFile());
        exportBtn.addActionListener(e -> onExportResults());
    }

    /**
     * Setup keyboard shortcuts.
     */
    private void setupKeyboardShortcuts() {
        KeyStroke ctrlEnter = KeyStroke.getKeyStroke("control ENTER");
        inputArea.getInputMap(JComponent.WHEN_FOCUSED).put(ctrlEnter, "analyze");
        inputArea.getActionMap().put("analyze", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                onAnalyze();
            }
        });
    }

    /**
     * Setup document listeners for real-time updates (like character count).
     */
    private void setupDocumentListeners() {
        inputArea.getDocument().addDocumentListener(new DocumentListener() {
            @Override
            public void insertUpdate(DocumentEvent e) {
                updateCharCount();
            }
            @Override
            public void removeUpdate(DocumentEvent e) {
                updateCharCount();
            }
            @Override
            public void changedUpdate(DocumentEvent e) {
                updateCharCount();
            }
        });
    }

    /**
     * Handle the analyze button action.
     */
    private void onAnalyze() {
        if (!validateInput()) {
            return;
        }
        setRunningState(true);
        new AnalysisWorker(inputArea.getText()).execute();
    }

    /**
     * Handle the clear button action.
     */
    private void onClear() {
        UIStateUtil.clearTextArea(inputArea);
        UIStateUtil.clearTextArea(outputArea);
        UIStateUtil.requestFocus(inputArea);
    }

    /**
     * Validate the input text area.
     *
     * @return true if valid, false otherwise
     */
    private boolean validateInput() {
        String text = inputArea.getText();
        if (text == null || text.isBlank()) {
            UIStateUtil.showWarning(this, "Please enter text to analyze.", "Validation");
            return false;
        }
        if (text.length() > MAX_TEXT_LENGTH) {
            UIStateUtil.showWarning(this, "Text too long (max " + MAX_TEXT_LENGTH + " characters).", "Validation");
            return false;
        }
        return true;
    }

    /**
     * Set UI state for running or idle analysis.
     *
     * @param running true if analysis is running
     */
    private void setRunningState(boolean running) {
        UIStateUtil.setButtonEnabled(analyzeBtn, !running);
        if (running) {
            UIStateUtil.setTextAreaContent(outputArea, "Running analysis...", -1);
        }
    }

    /**
     * Format the analysis results into a readable string.
     *
     * @param basic       the basic text analysis response
     * @param sentiment   the sentiment score
     * @param readability the readability metrics
     * @return formatted analysis result string
     */
    private String formatAnalysisResult(TextAnalysisResponse basic, SentimentScore sentiment, ReadabilityMetrics readability) {
        return "Basic Stats:\n" +
                "- Words: " + basic.basicStats().wordCount() + "\n" +
                "- Sentences: " + basic.basicStats().sentenceCount() + "\n" +
                "- Characters: " + basic.basicStats().characterCount() + "\n\n" +
                "Sentiment:\n" +
                "- Label: " + sentiment.label() + "\n" +
                "- Confidence: " + String.format("%.3f", sentiment.confidence()) + "\n" +
                "- Score: " + String.format("%.3f", sentiment.score()) + "\n\n" +
                "Readability:\n" +
                "- Flesch-Kincaid Score: " + String.format("%.2f", readability.fleschKincaidScore()) + "\n" +
                "- Reading Ease: " + String.format("%.2f", readability.fleschReadingEase()) + "\n" +
                "- Reading Level: " + readability.readingLevel() + "\n" +
                "- Complexity: " + readability.complexity() + "\n";
    }

    /**
     * Fill the input area with a sample text for analysis.
     */
    private void fillSample() {
        UIStateUtil.setTextAreaContent(inputArea, SAMPLE_TEXT, SAMPLE_TEXT.length());
        UIStateUtil.requestFocus(inputArea);
    }

    /**
     * Handle the load from file button action.
     */
    private void onLoadFromFile() {
        File file = FileOperationsUtil.showOpenDialog(this, "txt", "md", "csv");
        if (file != null) {
            try {
                String content = FileOperationsUtil.loadTextFile(file.toPath());
                UIStateUtil.setTextAreaContent(inputArea, content, -1);
            } catch (Exception ex) {
                LOGGER.error("Failed to load file", ex);
                UIStateUtil.showError(this, "Failed to load file: " + ex.getMessage(), "Failed to load file");
            }
        }
    }

    /**
     * Update the character count display and color based on length.
     */
    private void updateCharCount() {
        int count = inputArea.getText().length();
        String text = "Characters: " + count;
        Color color;
        if (count > MAX_TEXT_LENGTH) {
            color = Color.RED;
        } else if (count > MAX_TEXT_LENGTH * 0.8) {
            color = Color.ORANGE;
        } else {
            color = Color.BLACK;
        }
        UIStateUtil.setLabelText(charCountLabel, text, color);
    }

    /**
     * Handle the export results button action.
     */
    private void onExportResults() {
        if (outputArea.getText().isEmpty()) {
            UIStateUtil.showWarning(this, "No results to export. Please analyze some text first.", "No Results");
            return;
        }
        File file = FileOperationsUtil.showSaveDialog(this, "analysis_results.txt", "txt");
        if (file != null) {
            try {
                FileOperationsUtil.saveTextFile(outputArea.getText(), file.toPath());
                UIStateUtil.showInfo(this, "Results exported successfully!", "Export Successful");
            } catch (IOException ex) {
                LOGGER.error("Failed to export results", ex);
                UIStateUtil.showError(this, "Failed to export results: " + ex.getMessage(), "Export Failed");
            }
        }
    }

    /**
     * SwingWorker for running analysis in the background.
     *
     * @see SwingWorker
     */
    private class AnalysisWorker extends SwingWorker<TextAnalysisController.AnalysisResult, Void> {
        private final String text;
        AnalysisWorker(String text) {
            this.text = text;
        }
        @Override
        protected TextAnalysisController.AnalysisResult doInBackground() {
            return controller.analyze(text);
        }
        @Override
        protected void done() {
            setRunningState(false);
            try {
                var result = get();
                String formattedResult = formatAnalysisResult(result.basic(), result.sentiment(), result.readability());
                UIStateUtil.setTextAreaContent(outputArea, formattedResult, -1);
            } catch (Exception ex) {
                UIStateUtil.clearTextArea(outputArea);
                LOGGER.error("Analysis failed", ex);
                UIStateUtil.showError(TextAnalysisPanel.this, "Analysis failed: " + ex.getMessage(), "Analysis Failed");
            }
        }
    }

}