package org.limewire.ui.swing.friends.login;

import static org.limewire.ui.swing.util.I18n.tr;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Locale;

import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;

import net.miginfocom.swing.MigLayout;

import org.jdesktop.application.Resource;
import org.jdesktop.swingx.JXButton;
import org.jdesktop.swingx.JXPanel;
import org.limewire.core.settings.FriendSettings;
import org.limewire.friend.api.FriendConnectionEvent;
import org.limewire.friend.api.FriendConnectionFactory;
import org.limewire.listener.EventListener;
import org.limewire.listener.ListenerSupport;
import org.limewire.listener.SwingEDTEvent;
import org.limewire.ui.swing.action.AbstractAction;
import org.limewire.ui.swing.components.Disposable;
import org.limewire.ui.swing.components.HyperlinkButton;
import org.limewire.ui.swing.components.PromptPasswordField;
import org.limewire.ui.swing.components.PromptTextField;
import org.limewire.ui.swing.components.decorators.ButtonDecorator;
import org.limewire.ui.swing.components.decorators.TextFieldDecorator;
import org.limewire.ui.swing.friends.settings.FriendAccountConfiguration;
import org.limewire.ui.swing.friends.settings.FriendAccountConfigurationManager;
import org.limewire.ui.swing.painter.BorderPainter.AccentType;
import org.limewire.ui.swing.painter.factories.BarPainterFactory;
import org.limewire.ui.swing.settings.SwingUiSettings;
import org.limewire.ui.swing.util.GuiUtils;
import org.limewire.ui.swing.util.I18n;
import org.limewire.ui.swing.util.ResizeUtils;

import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;

public class XMPPUserEntryLoginPanel extends JPanel implements Disposable {
    
    private static final String CUSTOM_SERVICE_NAME = "Jabber";
    
    // Used for hack to show email/username depending on if the service is gmail or not
    private static final String GMAIL_SERVICE_NAME = "gmail.com";
    
    @Resource private Font headerTextFont;
    @Resource private Color headerTextForeground;
    @Resource private Font descriptionTextFont;
    @Resource private Color descriptionTextForeground;
    @Resource private Font inputTextFont;
    @Resource private Color inputTextForeground;
    @Resource private Font goBackTextFont;
    
    @Resource(key="XMPPUserEntryLoginPanel.authFailedLabel.foreground")
    private Color warningForeground;
    @Resource(key="XMPPUserEntryLoginPanel.authFailedLabel.font")
    private Font warningFont;
    @Resource(key="XMPPUserEntryLoginPanel.authFailedLabel.icon")
    private Icon warningIcon;
    
    @Resource(key="XMPPUserEntryLoginPanel.signInButton.foreground")
    private Color signInButtonForeground;
    @Resource(key="XMPPUserEntryLoginPanel.signInButton.font") 
    private Font signInButtonFont;
    @Resource(key="XMPPUserEntryLoginPanel.autoLoginCheckBox.icon") 
    private Icon autoLoginCheckBoxIcon;
    @Resource(key="XMPPUserEntryLoginPanel.autoLoginCheckBox.selectedIcon")
    private Icon autoLoginCheckBoxSelectedIcon;
    @Resource(key="XMPPUserEntryLoginPanel.autoLoginCheckBox.font") 
    private Font autoLoginCheckBoxFont;
    
    private static final String SIGNIN_ENABLED_TEXT = tr("Sign In");
    private static final String SIGNIN_DISABLED_TEXT = tr("Signing in...");
    private static final String AUTHENTICATION_ERROR = tr("The user name or password you have entered is incorrect.");
    private static final String NETWORK_ERROR = tr("Network error.");
    
    private PromptTextField serviceField;
    private PromptTextField usernameField;
    private PromptPasswordField passwordField;
    private JCheckBox autoLoginCheckBox;
    private JLabel authFailedLabel;
    private JXButton signInButton;
    private final SignInAction signinAction = new SignInAction();
    
    private ListenerSupport<FriendConnectionEvent> connectionSupport = null;
    
    private final FriendAccountConfiguration accountConfig;
    private final LoginPopupPanel parent;
    private final FriendAccountConfigurationManager accountManager;
    private EventListener<FriendConnectionEvent> connectionListener;
    private JLabel serviceLabel;
    private JComponent serviceRecenter;
    
    private boolean connectionHasBeenInitiated = false;
    private final FriendConnectionFactory friendConnectionFactory;
    
    @Inject
    public XMPPUserEntryLoginPanel(@Assisted FriendAccountConfiguration accountConfig, LoginPopupPanel parent,
            FriendAccountConfigurationManager accountManager,
            ButtonDecorator buttonDecorator,
            TextFieldDecorator textFieldDecorator,
            BarPainterFactory barPainterFactory,
            FriendConnectionFactory friendConnectionFactory) {
    
        super(new BorderLayout());
    
        this.accountConfig = accountConfig;
        this.parent = parent;
        this.accountManager = accountManager;
        this.friendConnectionFactory = friendConnectionFactory;
        
        GuiUtils.assignResources(this);
        
        JXPanel headerPanel = new JXPanel(new MigLayout("gap 10, insets 15 20 15 20"));
        headerPanel.setBackgroundPainter(barPainterFactory.createPopUpBarPainter());
        
        JLabel headerLabel = new JLabel(I18n.tr("Sign in with {0}",accountConfig.getLabel()));
        JLabel headerIcon = new JLabel(accountConfig.getLargeIcon());
        headerLabel.setFont(headerTextFont);
        headerLabel.setForeground(headerTextForeground);
        headerPanel.add(headerIcon);
        headerPanel.add(headerLabel);
        
        add(headerPanel, BorderLayout.NORTH);
        
        initComponents(buttonDecorator, textFieldDecorator);
        initServiceField();
        setSignInComponentsEnabled(true);
    }
    
    @Inject
    void registerListener(ListenerSupport<FriendConnectionEvent> connectionSupport) {
        
        this.connectionSupport = connectionSupport;
        
        connectionListener = new EventListener<FriendConnectionEvent>() {
            @Override
            @SwingEDTEvent
            public void handleEvent(FriendConnectionEvent event) {
                switch(event.getType()) {
                case CONNECTING:
                    connecting();
                    break;
                case CONNECTED:
                    connected();
                    break;
                case CONNECT_FAILED:
                    
                    // Do not show connect failed messages from before a
                    //  connection has been attempted
                    if (!connectionHasBeenInitiated) {
                        break;
                    }
                    
                    // Ignore duplicate events caused by authentication
                    // errors and events caused by deliberately signing
                    // out or switching user
                    Exception reason = event.getException();
                    if(reason != null) {
                        disconnected(reason);
                    }
                }
            }
        };
        
        connectionSupport.addListener(connectionListener);
    }
   
    private void initComponents(ButtonDecorator buttonDecorator,
            TextFieldDecorator textFieldDecorator) {
        
        serviceRecenter = new JPanel();
        ResizeUtils.forceSize(serviceRecenter, new Dimension(30,30));
        
        serviceLabel = new JLabel(tr("Domain"));
        serviceLabel.setFont(descriptionTextFont);
        serviceLabel.setForeground(descriptionTextForeground);
        JLabel usernameLabel = new JLabel(GMAIL_SERVICE_NAME.equals(accountConfig.getNetworkName()) ? tr("Email") : tr("Username"));
        usernameLabel.setFont(descriptionTextFont);
        usernameLabel.setForeground(descriptionTextForeground);
        JLabel passwordLabel = new JLabel(tr("Password"));
        passwordLabel.setFont(descriptionTextFont);
        passwordLabel.setForeground(descriptionTextForeground);
       
        /*
         * Populate the input fields on initialize. Otherwise, if the auto-login
         * option is selected in application settings and the user exits out of
         * the login panel with the auto-login option unchecked, settings are
         * cleared from application settings.
         */
        serviceField = new PromptTextField();
        textFieldDecorator.decoratePromptField(serviceField, AccentType.NONE);
        serviceField.setFont(inputTextFont);
        serviceField.setForeground(inputTextForeground);
        usernameField = new PromptTextField();
        textFieldDecorator.decoratePromptField(usernameField, AccentType.NONE);
        usernameField.setFont(inputTextFont);
        usernameField.setForeground(inputTextForeground);
        usernameField.setText(accountConfig.getUserInputLocalID());

        passwordField = new PromptPasswordField();
        textFieldDecorator.decoratePromptField(passwordField, AccentType.NONE);
        passwordField.setFont(inputTextFont);
        passwordField.setForeground(inputTextForeground);
        passwordField.setAction(signinAction);
        passwordField.setText(accountConfig.getPassword());

        ResizeUtils.forceSize(serviceField, new Dimension(224, 26));
        ResizeUtils.forceSize(usernameField, new Dimension(224, 26));
        ResizeUtils.forceSize(passwordField, new Dimension(224, 26));

        autoLoginCheckBox = new JCheckBox(tr("Sign in automatically"), true);
        autoLoginCheckBox.setFont(autoLoginCheckBoxFont);
        autoLoginCheckBox.setIcon(autoLoginCheckBoxIcon);
        autoLoginCheckBox.setSelectedIcon(autoLoginCheckBoxSelectedIcon);
        autoLoginCheckBox.setSelected(accountManager.getAutoLoginConfig() != null);
        
        autoLoginCheckBox.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                // When the user clears the auto-login checkbox,
                // forget the auto-login config
                if(!autoLoginCheckBox.isSelected()) {
                    accountManager.setAutoLoginConfig(null);
                }
                SwingUiSettings.REMEMBER_ME_CHECKED.setValue(autoLoginCheckBox.isSelected());
            }
        });
        autoLoginCheckBox.setOpaque(false);

        // Hack to get the enter key to start login for JCheckBox
        autoLoginCheckBox.addKeyListener(new KeyListener() {
            @Override
            public void keyPressed(KeyEvent e) {
            }
            @Override
            public void keyReleased(KeyEvent e) {
            }
            @Override
            public void keyTyped(KeyEvent e) {
                if (e.getKeyChar() == KeyEvent.VK_ENTER) {
                    login();
                }
            }
            
        });

        signInButton = new JXButton(signinAction);
        buttonDecorator.decorateGreenFullButton(signInButton);
        signInButton.setFont(signInButtonFont);
        signInButton.setForeground(signInButtonForeground);
        signInButton.setBorder(BorderFactory.createEmptyBorder(0,15,2,15));
        ResizeUtils.looseForceHeight(signInButton, 32);
        
        authFailedLabel = new JLabel();
        authFailedLabel.setVisible(false);
        authFailedLabel.setForeground(warningForeground);
        authFailedLabel.setFont(warningFont);
        authFailedLabel.setIcon(warningIcon);

        HyperlinkButton goBackButton = new HyperlinkButton(new AbstractAction(I18n.tr("Choose another account")) {
            @Override
            public void actionPerformed(ActionEvent arg0) {
                parent.restart();                
            }
        });
        goBackButton.setFont(goBackTextFont);
        
        JPanel contentPanel = new JPanel(new MigLayout("gap 0, insets 10, align center"));
        
        contentPanel.add(authFailedLabel, "dock north, gapleft 2, hidemode 3, gapbottom 3, wrap");
        
        contentPanel.add(serviceLabel, "hidemode 3, wrap");
        contentPanel.add(serviceField, "gapbottom 10, hidemode 3, grow, wrap");
        contentPanel.add(usernameLabel, "wrap");
        contentPanel.add(usernameField, "gapbottom 10, grow, wrap");
        contentPanel.add(passwordLabel, "wrap");
        contentPanel.add(passwordField, "gapbottom 6, grow, wrap");
        contentPanel.add(autoLoginCheckBox, "gapbottom 10, wmin 0, wrap");
        contentPanel.add(signInButton, "wrap");
        contentPanel.add(serviceRecenter, "hidemode 3, wrap");
        
        JPanel bottomPanel = new JPanel(new MigLayout("insets 0, gap 0, align center"));
        bottomPanel.add(goBackButton, "gapbottom 10");
        
        add(contentPanel, BorderLayout.CENTER);
        add(bottomPanel, BorderLayout.SOUTH);
    }
    
    private void setSignInComponentsEnabled(boolean isEnabled) {
        signinAction.setEnabled(isEnabled);
        signinAction.putValue(Action.NAME, isEnabled ? SIGNIN_ENABLED_TEXT : SIGNIN_DISABLED_TEXT);
        serviceField.setEnabled(isEnabled);
        usernameField.setEnabled(isEnabled);
        passwordField.setEnabled(isEnabled);
        autoLoginCheckBox.setEnabled(isEnabled);
    }
    
    private void initServiceField() {
        if(accountConfig.getLabel().equals(CUSTOM_SERVICE_NAME)) {
            serviceLabel.setVisible(true);
            serviceField.setVisible(true);
            serviceRecenter.setVisible(false);
        } else {
            serviceLabel.setVisible(false);
            serviceField.setVisible(false);
            serviceRecenter.setVisible(true);
        }
    }
    
    private void login() {
        FriendSettings.EVER_TRIED_TO_SIGN_IN.setValue(true);
        String user = usernameField.getText().trim();
        String password = new String(passwordField.getPassword());
        if(user.equals("") || password.equals("")) {
            return;
        }            
        if(accountConfig.getLabel().equals("Jabber")) {
            String service = serviceField.getText().trim();
            if(service.equals(""))
                return;
            accountConfig.setServiceName(service);
        }
        accountConfig.setUsername(user);
        accountConfig.setPassword(password);
        if(autoLoginCheckBox.isSelected()) {
            // Set this as the auto-login account
            accountManager.setAutoLoginConfig(accountConfig);
        } else {
            // If there was previously an auto-login account, delete it
            accountManager.setAutoLoginConfig(null);
        }
        
        authFailedLabel.setVisible(false);
        validate();
        repaint();
        friendConnectionFactory.login(accountConfig);         
    }

    void connected() {
        FriendSettings.EVER_SIGNED_IN.setValue(true);
        parent.finished();
    }

    void disconnected(Exception reason) {
        setSignInComponentsEnabled(true);
        if(reason !=null && reason.getMessage() != null && reason.getMessage().toLowerCase(Locale.US).contains("auth")) {
            authFailedLabel.setText(AUTHENTICATION_ERROR);
            passwordField.setText("");
        } else {
            authFailedLabel.setText(NETWORK_ERROR);
        }
        authFailedLabel.setVisible(true);
    }
    
    public void connecting() {
        connectionHasBeenInitiated = true;
        setSignInComponentsEnabled(false);
    }
    
    @Override
    public boolean requestFocusInWindow() {
        if (serviceField.isVisible()) {
            return serviceField.requestFocusInWindow();
        }
        else {
            return usernameField.requestFocusInWindow();
        }
    }
    
    class SignInAction extends AbstractAction {
        public SignInAction() {
            super();
        }

        @Override
		public void actionPerformed(ActionEvent e) {
            login();
        }
    }

    @Override
    public void dispose() {
        connectionSupport.removeListener(connectionListener);
    }
}
