package org.limewire.ui.swing.mainframe;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Panel;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import net.miginfocom.swing.MigLayout;

import org.jdesktop.application.Resource;
import org.jdesktop.swingx.JXPanel;
import org.limewire.friend.api.FriendConnection;
import org.limewire.friend.api.FriendConnectionEvent;
import org.limewire.listener.EventBean;
import org.limewire.listener.EventListener;
import org.limewire.listener.EventListenerList;
import org.limewire.listener.EventUtils;
import org.limewire.listener.SwingEDTEvent;
import org.limewire.ui.swing.components.MessageComponent;
import org.limewire.ui.swing.components.PanelResizer;
import org.limewire.ui.swing.components.Resizable;
import org.limewire.ui.swing.components.RolloverCursorListener;
import org.limewire.ui.swing.components.decorators.MessageDecorator;
import org.limewire.ui.swing.library.LibraryMediator;
import org.limewire.ui.swing.library.LibraryTableRect;
import org.limewire.ui.swing.library.navigator.LibraryNavItem;
import org.limewire.ui.swing.library.navigator.LibraryNavigatorPanel;
import org.limewire.ui.swing.library.navigator.LibraryNavItem.NavType;
import org.limewire.ui.swing.library.sharing.LibrarySharingEvent;
import org.limewire.ui.swing.nav.NavCategory;
import org.limewire.ui.swing.nav.NavItem;
import org.limewire.ui.swing.nav.NavMediator;
import org.limewire.ui.swing.nav.NavSelectable;
import org.limewire.ui.swing.nav.NavigationListener;
import org.limewire.ui.swing.nav.Navigator;
import org.limewire.ui.swing.settings.SwingUiSettings;
import org.limewire.ui.swing.util.GuiUtils;
import org.limewire.ui.swing.util.I18n;

import com.google.inject.Inject;
import com.google.inject.Provider;

/**
 * Container for overlay messages that are displayed when the user signs on 
 * for private sharing.
 */
class SignOnMessageLayer {
    /** Message type definitions. */
    private enum Type {
        NONE, LIBRARY, SHARING;
    }

    @Resource private Color messageForeground;
    @Resource private Font libraryFont;
    @Resource private Font sharingFont;
    @Resource private Icon addFileIcon;
    @Resource private Icon checkedIcon;
    @Resource private Icon closeIcon;
    @Resource private Icon closeHoverIcon;
    @Resource private Icon upArrowIcon;
    @Resource private Icon leftArrowIcon;
    
    private final JLayeredPane layeredPane;
    private final TopPanel topPanel;
    private final Navigator navigator;
    private final Provider<LibraryNavigatorPanel> libraryNavProvider;
    private final LibraryMediator libraryMediator;
    private final EventListenerList<LibrarySharingEvent> libraryListenerList;
    private final Provider<Rectangle> libraryTableRect;
    private final EventBean<FriendConnectionEvent> connectionEventBean;
    private final Provider<MessageDecorator> messageDecoratorProvider;
    
    /** Current message component. */
    private Component messageComponent;
    
    /** Type of current message. */
    private Type messageType = Type.NONE;
    
    /** Listener to handle layered pane resizing. */
    private PanelResizer panelResizer;
    
    /** Listener to handle library sharing events. */
    private EventListener<LibrarySharingEvent> libraryListener;
    
    /**
     * Constructs a SignOnMessagePanel with the specified services.
     */
    @Inject
    public SignOnMessageLayer(
            @GlobalLayeredPane JLayeredPane limeWireLayeredPane,
            TopPanel topPanel,
            Navigator navigator,
            Provider<LibraryNavigatorPanel> libraryNavProvider,
            LibraryMediator libraryMediator,
            EventListenerList<LibrarySharingEvent> libraryListenerList,
            @LibraryTableRect Provider<Rectangle> libraryTableRect,
            EventBean<FriendConnectionEvent> connectionEventBean,
            Provider<MessageDecorator> messageDecoratorProvider) {
        
        this.layeredPane = limeWireLayeredPane;
        this.topPanel = topPanel;
        this.navigator = navigator;
        this.libraryMediator = libraryMediator;
        this.libraryNavProvider = libraryNavProvider;
        this.libraryListenerList = libraryListenerList;
        this.libraryTableRect = libraryTableRect;
        this.connectionEventBean = connectionEventBean;
        this.messageDecoratorProvider = messageDecoratorProvider;
        
        GuiUtils.assignResources(this);
    }
    
    /**
     * Registers listeners for service events.  This method should be called
     * post-construction.
     */
    @Inject
    void register() {
        // Add listener to handle Navigator selection events.
        navigator.addNavigationListener(new NavigationListener() {
            @Override
            public void categoryAdded(NavCategory category) {
            }

            @Override
            public void categoryRemoved(NavCategory category, boolean wasSelected) {
            }

            @Override
            public void itemAdded(NavCategory category, NavItem navItem) {
            }

            @Override
            public void itemRemoved(NavCategory category, NavItem navItem, boolean wasSelected) {
            }

            @Override
            public void itemSelected(NavCategory category, NavItem navItem,
                    NavSelectable selectable, NavMediator navMediator) {
                //TODO: all this logic is extremely brittle
                switch (messageType) {
                case LIBRARY:
                    // If library message showing and Library selected, close message.
                    if (category == NavCategory.LIBRARY) {
                        hideMessage();
                    }
                    break;
                case SHARING:
                    // If sharing message showing and Library de-selected, close message.
                    if (category != NavCategory.LIBRARY) {
                        hideMessage();
                    }
                    break;
                }
                // If Private Shared selected and signed on, show message.
                NavType selectedType = getSelectedNavType();
                if ((category == NavCategory.LIBRARY) && 
                        (selectedType == NavType.LIST) && isSignedOn()) {
                    showMessage();
                }
            }
        });
        
        // Add listener to handle LibraryNavigator selection events.
        libraryNavProvider.get().addTableSelectionListener(new ListSelectionListener() {
            @Override
            public void valueChanged(ListSelectionEvent e) {
                NavType navType = getSelectedNavType();
                //TODO: all this logic is extremely brittle
                switch (messageType) {
                case LIBRARY:
                    // If library message showing and Private Shared selected, close message.
                    if (navType != NavType.LIBRARY) {
                        hideMessage();
                    }
                    // If Private Shared selected and signed on, show message.
                    if ((navType == NavType.LIST) && isSignedOn()) {
                        showMessage();
                    }
                    break;
                    
                case SHARING:
                    // If sharing message showing and Private Shared not selected, close message.
                    if (navType != NavType.LIST) {
                        hideMessage();
                    }
                    break;
                    
                case NONE:
                    // If Private Shared selected and signed on, show message.
                    if ((navType == NavType.LIST) && isSignedOn()) {
                        showMessage();
                    }
                    break;
                }
            }
        });
    }
    
    /**
     * Returns true if there are any pending sign-on messages.
     */
    public static boolean isSignOnMessageEnabled() {
        return SwingUiSettings.SHOW_LIBRARY_OVERLAY_MESSAGE.getValue() ||
                SwingUiSettings.SHOW_SHARING_OVERLAY_MESSAGE.getValue();
    }
    
    /**
     * Returns true if the user is signed on to any service.
     */
    private boolean isSignedOn() {
        FriendConnection connection = EventUtils.getSource(connectionEventBean);
        return ((connection != null) && connection.isLoggedIn());
    }
    
    /**
     * Displays a sign-on message using the specified layered pane.
     */
    public void showMessage() {
        // Retrieve message settings.
        boolean showLibraryMsg = SwingUiSettings.SHOW_LIBRARY_OVERLAY_MESSAGE.getValue();
        boolean showSharingMsg = SwingUiSettings.SHOW_SHARING_OVERLAY_MESSAGE.getValue();
        
        // Get Library item from navigator.
        NavItem libraryItem = navigator.getNavItem(NavCategory.LIBRARY, LibraryMediator.NAME);
        
        // Get selected collection in library.
        NavType selectedType = getSelectedNavType();
        
        // Remove old message components.
        if (panelResizer != null) {
            layeredPane.removeComponentListener(panelResizer);
            panelResizer = null;
        }
        if (messageComponent != null) {
            layeredPane.remove(messageComponent);
            messageComponent = null;
        }
        
        if ((libraryItem == null) || !libraryItem.isSelected()) {
            // Library not selected so show message pointing to Library.
            if (showLibraryMsg) {
                Point location = new Point(16, topPanel.getSize().height - 4);
                messageComponent = new LibraryMessagePanel(location, false);
                messageType = Type.LIBRARY;
            }
            
        } else if ((selectedType == NavType.LIBRARY) || (selectedType == NavType.PUBLIC_SHARED)) {
            // Library selected so show message pointing to Private Shared.
            if (showLibraryMsg) {
                LibraryNavigatorPanel libraryNavPanel = libraryNavProvider.get();
                Point location = new Point(libraryNavPanel.getSize().width - 6, 75);
                messageComponent = new LibraryMessagePanel(location, true);
                messageType = Type.LIBRARY;
            }
            
        } else {
            // Disable library overlay message.
            SwingUiSettings.SHOW_LIBRARY_OVERLAY_MESSAGE.setValue(false);
            
            // Private Shared selected so show message about sharing files.
            if (showSharingMsg) {
                messageComponent = new SharingMessagePanel();
                messageType = Type.SHARING;
                // Add listener for Library events to dismiss message.
                libraryListener = new EventListener<LibrarySharingEvent>() {
                    @Override
                    @SwingEDTEvent
                    public void handleEvent(LibrarySharingEvent event) {
                        hideMessage();
                    }
                };
                libraryListenerList.addListener(libraryListener);
            }
        }
        
        // Exit if no message to display.
        if (messageComponent == null) {
            messageType = Type.NONE;
            return;
        }
        
        // Add new message to layered pane.
        panelResizer = new PanelResizer((Resizable) messageComponent);
        layeredPane.add(messageComponent, JLayeredPane.PALETTE_LAYER);
        layeredPane.addComponentListener(panelResizer);
        layeredPane.validate();
        
        // Show message.
        messageComponent.setVisible(true);
        ((Resizable) messageComponent).resize();
    }
    
    private NavType getSelectedNavType() {
        // Get selected collection in library.
        LibraryNavItem selectedItem = libraryMediator.getSelectedNavItem();
        
        NavType selectedType;
        if(selectedItem == null)
            selectedType = NavType.LIBRARY;
        else
            selectedType = selectedItem.getType();
        return selectedType;
    }
    
    /**
     * Hides the current sign-on message, and disables the message so it will
     * not be displayed again.
     */
    public void hideMessage() {
        // Hide message and validate layered pane.
        if(panelResizer != null) {
            layeredPane.removeComponentListener(panelResizer);
            panelResizer = null;
        }
        if(messageComponent != null) {
            messageComponent.setVisible(false);
            layeredPane.remove(messageComponent);
            messageComponent = null;
        }
        layeredPane.validate();

        // Remove library listener.
        if (libraryListener != null) {
            libraryListenerList.removeListener(libraryListener);
            libraryListener = null;
        }
        
        // Disable overlay messages.
        switch (messageType) {
        case LIBRARY:
            SwingUiSettings.SHOW_LIBRARY_OVERLAY_MESSAGE.setValue(false);
            break;
        case SHARING:
            SwingUiSettings.SHOW_LIBRARY_OVERLAY_MESSAGE.setValue(false);
            SwingUiSettings.SHOW_SHARING_OVERLAY_MESSAGE.setValue(false);
            break;
        }
        messageType = Type.NONE;
    }
    
    /**
     * Creates a close button.
     */
    private JButton createCloseButton() {
        JButton closeButton = new JButton();
        
        closeButton.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
        closeButton.setContentAreaFilled(false);
        closeButton.setFocusPainted(false);
        closeButton.setIcon(closeIcon);
        closeButton.setRolloverIcon(closeHoverIcon);
        closeButton.setRolloverEnabled(true);
        closeButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                hideMessage();
            }
        });
        
        // Install listener to display rollover cursor.
        new RolloverCursorListener().install(closeButton);
        
        return closeButton;
    }
    
    /**
     * Container for Library overlay message that may be displayed when the 
     * user signs on for private sharing.  LibraryMessagePanel extends 
     * java.awt.Panel because the message may be displayed on top of the 
     * browser component.
     */
    private class LibraryMessagePanel extends Panel implements Resizable {
        private final Point messageLocation;
        private JXPanel messagePanel;
        private JButton closeButton;
        
        public LibraryMessagePanel(Point messageLocation, boolean librarySelected) {
            super(new BorderLayout());
            
            this.messageLocation = messageLocation;
           
            messagePanel = new JXPanel(new MigLayout("insets 3 3 3 3, gap 0!"));
            messagePanel.setOpaque(false);
            messageDecoratorProvider.get().decorateGreenRectangleMessage(messagePanel);
            
            // Create message components.
            closeButton = createCloseButton();
            
            JLabel arrowLabel = new JLabel();
            arrowLabel.setIcon(librarySelected ? leftArrowIcon : upArrowIcon);
            
            JLabel messageLabel = new JLabel();
            messageLabel.setFont(libraryFont);
            messageLabel.setForeground(messageForeground);
            messageLabel.setText(I18n.tr("Share with friends using your Private Shared list"));
            
            // Add components to container.
            if (librarySelected) {
                messagePanel.add(arrowLabel, "gap 6 0 0 0, aligny center");
            } else {
                messagePanel.add(arrowLabel, "gap 6 0 6 0, aligny top");
            }
            messagePanel.add(messageLabel, "gap 6 6 10 10, aligny center");
            messagePanel.add(closeButton , "aligny top");
            
            add(messagePanel, BorderLayout.CENTER);
        }

        @Override
        public void resize() {
            // Get preferred size.
            Dimension preferredSize = getPreferredSize();

            // Set bounds to position message in layered pane.
            setBounds(messageLocation.x, messageLocation.y, preferredSize.width, preferredSize.height);
        }
    }
    
    /**
     * Container for Sharing overlay message that may be displayed when the 
     * user signs on for private sharing.  SharingMessagePanel extends 
     * javax.swing.JPanel because it is displayed over the library table with 
     * a transparent background. 
     */
    private class SharingMessagePanel extends JPanel implements Resizable {
        private MessageComponent messageComponent;
        private JButton closeButton;
        private JLabel leftMessageLabel;
        private JLabel rightMessageLabel;

        public SharingMessagePanel() {
            setLayout(new MigLayout("insets 0 0 0 0"));
            setOpaque(false);
            
            // Create message component.
            messageComponent = new MessageComponent(5, 5, 18, 8);
            messageDecoratorProvider.get().decorateGreenMessage(messageComponent);
            
            // Create message elements.
            closeButton = createCloseButton();
            
            leftMessageLabel = new JLabel();
            leftMessageLabel.setFont(sharingFont);
            leftMessageLabel.setForeground(messageForeground);
            leftMessageLabel.setIcon(checkedIcon);
            leftMessageLabel.setText(I18n.tr("Share this list with friends"));
            
            rightMessageLabel = new JLabel();
            rightMessageLabel.setFont(sharingFont);
            rightMessageLabel.setForeground(messageForeground);
            rightMessageLabel.setIcon(addFileIcon);
            rightMessageLabel.setText(I18n.tr("Add files"));
            
            // Add message elements to component.
            messageComponent.addComponent(closeButton, "span 2, align right, push, wrap");
            //messageComponent.addComponent(titleLabel, "gap 15 15, span 2, align left, wrap");
            messageComponent.addComponent(leftMessageLabel , "gap 18 6, align left, wrap");
            messageComponent.addComponent(rightMessageLabel, "gap 15 15, align left");
            //messageComponent.addComponent(moreMessageLabel , "gap 15 15, span 2, align left");
            
            // Add message component to container.
            add(messageComponent, "pos 0.5al 0.5al");
        }
        
        @Override
        public void resize() {
            // Post event to perform delayed resizing.  This corrects an issue
            // with component sizes when the window is maximized or unmaximized.
            SwingUtilities.invokeLater(new Runnable() {
                @Override
				public void run() {
                    // Determine message location and size.
                    int y0 = topPanel.getSize().height;
                    Rectangle tableRect = libraryTableRect.get(); 
                    
                    // Set bounds to position message in layered pane.
                    setBounds(tableRect.x, y0 + tableRect.y, tableRect.width, tableRect.height);
                    
                    // Revalidate layered pane.
                    layeredPane.revalidate();
                }
            });
        }
    }
}
