/*
 * This file is part of muCommander, http://www.mucommander.com
 *
 * muCommander is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * muCommander is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.mucommander.ui.dialog.debug;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;

import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;

import com.mucommander.commons.util.ui.dialog.FocusDialog;
import com.mucommander.text.Translator;
import com.mucommander.ui.action.ActionProperties;
import com.mucommander.ui.action.impl.RefreshAction;
import com.mucommander.ui.action.impl.ShowDebugConsoleAction;
import com.mucommander.ui.main.MainFrame;
import com.mucommander.utils.MuLogging;
import com.mucommander.utils.MuLogging.LogLevel;

/**
 * This dialog shows the last log messages collected by {@link DebugConsoleAppender} and allows them to be copied
 * to the clipboard. It also makes it possible to change the log level, the level combo box being preset to the
 * level returned by {@link MuLogging#getLogLevel()}.
 *
 * @see ShowDebugConsoleAction
 * @see DebugConsoleAppender
 * @see MuLogging#setLogLevel(LogLevel)
 * @author Maxence Bernard
 */
public class DebugConsoleDialog extends FocusDialog implements ActionListener, ItemListener {

    /** Displays log events, and allows to copy their values to the clipboard */
    private JList loggingEventsList;

    /** Allows the log level to be changed */
    private JComboBox<LogLevel> levelComboBox;

    /** Closes the debug console when pressed */
    private JButton closeButton;

    /** Refreshes the list with the latest log records when pressed */
    private JButton refreshButton;

    // Dialog size constraints
    private final static Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(600,400);
    // Dialog width should not exceed 360, height is not an issue (always the same)
    private final static Dimension MAXIMUM_DIALOG_DIMENSION = new Dimension(700,500);

    /**
     * Creates a new {@link DebugConsoleDialog} using the given {@link MainFrame} as a parent.
     *
     * @param mainFrame the {@link MainFrame} to use as a parent
     */
    public DebugConsoleDialog(MainFrame mainFrame) {
        super(mainFrame, ActionProperties.getActionLabel(ShowDebugConsoleAction.Descriptor.ACTION_ID), mainFrame);

        Container contentPane = getContentPane();

        loggingEventsList = new JList();
        // Autoscroll when dragged
        loggingEventsList.setAutoscrolls(true);
        loggingEventsList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
        loggingEventsList.setCellRenderer(new DebugListCellRenderer());
        refreshLogRecords();

        JScrollPane scrollPane = new JScrollPane(loggingEventsList, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        contentPane.add(scrollPane, BorderLayout.CENTER);

        JPanel southPanel = new JPanel(new BorderLayout());
        southPanel.add(createComboPanel(), BorderLayout.WEST);

        JPanel buttonPanel = new JPanel(new FlowLayout());

        refreshButton = new JButton(Translator.get(new RefreshAction.Descriptor().getLabel()));
        refreshButton.addActionListener(this);
        buttonPanel.add(refreshButton);

        closeButton = new JButton(Translator.get("close"));
        closeButton.addActionListener(this);
        buttonPanel.add(closeButton);

        southPanel.add(buttonPanel, BorderLayout.EAST);
        contentPane.add(southPanel, BorderLayout.SOUTH);

        setInitialFocusComponent(closeButton);
        setMinimumSize(MINIMUM_DIALOG_DIMENSION);
        setMaximumSize(MAXIMUM_DIALOG_DIMENSION);

        setInitialFocusComponent(closeButton);
    }

    /**
     * Creates and returns a panel containing the level combo box and a leading localized label describing it.
     *
     * @return a panel containing the level combo box and a leading localized label describing it
     */
    private JPanel createComboPanel() {
        JPanel comboPanel = new JPanel(new FlowLayout());
        comboPanel.add(new JLabel(Translator.get("debug_console_dialog.level")+":"));
        LogLevel logLevel = MuLogging.getLogLevel();

        levelComboBox = new JComboBox<>();
        for(LogLevel level:LogLevel.values())
            levelComboBox.addItem(level);
        		
        levelComboBox.setSelectedItem(logLevel);
        		
        levelComboBox.addItemListener(this);

        comboPanel.add(levelComboBox);

        return comboPanel;
    }

    /**
     * Refreshes the JList with the log records contained by {@link DebugConsoleAppender}.
     */
    private void refreshLogRecords() {
    	DefaultListModel<LoggingEvent> listModel = new DefaultListModel<>();
        DebugConsoleAppender handler = MuLogging.getDebugConsoleAppender();

        final LoggingEvent[] records = handler.getLogRecords();
        final LogLevel currentLogLevel = MuLogging.getLogLevel();
        
        for (LoggingEvent record : records) {
        	if (record.isLevelEqualOrHigherThan(currentLogLevel))
        		listModel.addElement(record);
        }

        loggingEventsList.setModel(listModel);

        SwingUtilities.invokeLater(() -> loggingEventsList.ensureIndexIsVisible(records.length-1));
    }
    
    /**
     * Changes the log level to the selected combo box value.
     */
    private void updateLogLevel() {
        LogLevel newLevel = (LogLevel) levelComboBox.getSelectedItem();

        MuLogging.setLogLevel(newLevel);
    }


    ///////////////////////////////////
    // ActionListener implementation //
    ///////////////////////////////////

    public void actionPerformed(ActionEvent e) {
        Object source = e.getSource();

        if(source==refreshButton) {
            refreshLogRecords();
        }
        else if(source==closeButton) {
            dispose();
        }
    }


    /////////////////////////////////
    // ItemListener implementation //
    /////////////////////////////////

    public void itemStateChanged(ItemEvent e) {
        // Refresh the log records displayed in the JList whenever the selected level has been changed.
        int selectedIndex = levelComboBox.getSelectedIndex();
        if(selectedIndex!=-1) {
            updateLogLevel();
            refreshLogRecords();
        }
    }


    ///////////////////
    // Inner classes //
    ///////////////////

    /**
     * Custom {@link ListCellRenderer} that renders {@link LoggingEvent} instances.
     */
    private class DebugListCellRenderer extends DefaultListCellRenderer {

        @Override
        public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
            if(value==null)
                return null;

            // TODO: line-wrap log items when the text is too long to fit on a single line
            // A single-column JTable may be the easiest way to go, see:
            // http://javaspecialists.co.za/archive/newsletter.do?issue=106&locale=en_US
            // http://forums.sun.com/thread.jspa?threadID=702740&start=0&tstart=0

            // Using a JTextArea with line-wrapping enabled does not work as a JList has by design a fixed height
            // for cells

            JLabel label = (JLabel)super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
            
            // Change the label's foreground color to match the level of the log record
            if(!isSelected) {
                LogLevel level = ((LoggingEvent)value).getLevel();
                Color color;

                if(level.equals(LogLevel.SEVERE))
                    color = Color.RED;
                else if(level.equals(LogLevel.WARNING))
                    color = new Color(255, 100, 0);     // Dark orange
                else if(level.equals(LogLevel.CONFIG))
                    color = Color.BLUE;
                else if(level.equals(LogLevel.INFO))
                    color = Color.BLACK;
                else if(level.equals(LogLevel.FINE))
                    color = Color.DARK_GRAY;
                else
                    color = new Color(110, 110, 110);    // Between Color.GRAY and Color.DARK_GRAY

                label.setForeground(color);
            }

            // TODO: remove this when line-wrapping has been implemented
            // If component's preferred width is larger than the list's width then the component is not entirely
            // visible. In that case, we set a tooltip text that will display the whole text when mouse is over the
            // component
            if (loggingEventsList.getVisibleRect().getWidth() < label.getPreferredSize().getWidth())
                label.setToolTipText(label.getText());
            // Have to set it to null because of the rubber-stamp rendering scheme (last value is kept)
            else
                label.setToolTipText(null);
            
            return label;
        }
    }
}
