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

import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.util.concurrent.ScheduledExecutorService;

import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.ScrollPaneConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.JTextComponent;

import org.jdesktop.application.Resource;
import org.limewire.logging.Log;
import org.limewire.logging.LogFactory;
import org.limewire.ui.swing.action.CopyAction;
import org.limewire.ui.swing.action.CutAction;
import org.limewire.ui.swing.action.DeleteAction;
import org.limewire.ui.swing.action.PasteAction;
import org.limewire.ui.swing.action.PopupUtil;
import org.limewire.ui.swing.action.SelectAllAction;
import org.limewire.ui.swing.util.GuiUtils;
import org.limewire.ui.swing.components.Disposable;
import org.limewire.collection.Periodic;
import org.limewire.friend.api.ChatState;
import org.limewire.friend.api.FriendException;
import org.limewire.friend.api.MessageWriter;

class ResizingInputPanel extends JPanel implements Displayable, Disposable {
    private static final Log LOG = LogFactory.getLog(ResizingInputPanel.class);
    private static final int DELAY_IN_MILLIS_FOR_CHAT_PAUSED = 3000;
    private final JTextArea text;
    private final MessageWriter writer;
    private final ChatStateManager chatStateManager;

    @Resource(key="ChatInputPanel.textFont") private Font inputTextFont;

    public ResizingInputPanel(MessageWriter writer, ScheduledExecutorService schedExecService) {
        super(new BorderLayout());

        this.writer = writer;
        
        GuiUtils.assignResources(this);

        chatStateManager = new ChatStateManager(schedExecService);
        text = new JTextArea();
        text.setFont(inputTextFont);
        text.setRows(2);
        text.setWrapStyleWord(true);
        text.setLineWrap(true);
        text.getInputMap().put(KeyStroke.getKeyStroke("ENTER"), "sendMessage");
        text.getActionMap().put("sendMessage", new SendMessage());
        text.getDocument().addDocumentListener(chatStateManager);
        text.setBorder(BorderFactory.createEmptyBorder(2,0,0,0));
        
        JPopupMenu popup = PopupUtil.addPopupMenus(text, new CutAction(text), new CopyAction(text), 
                new PasteAction(), new DeleteAction(text));
        popup.addSeparator();
        popup.add(new SelectAllAction());

        final JScrollPane scrollPane = new JScrollPane(text, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        scrollPane.getViewport().addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                if (text.getText().length() > 0 && scrollPane.getVerticalScrollBar().isVisible()) {
                    resizeTextBox(3);
                }
            }
        });
        scrollPane.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 0));
        add(scrollPane);
    }
    
    private void resizeTextBox(int rowCount) {
        if (text.getRows() != rowCount) {
            text.setRows(rowCount);
            revalidate();
        }
    }

    /**
     * Stop tracking chat state updates.
     */
    @Override
    public void dispose() {
        chatStateManager.cancelPendingChatStateUpdates();        
    }
    
    @Override
    public void handleDisplay() {
        text.requestFocusInWindow();
    }
    
    public JTextComponent getInputComponent() {
        return text;
    }

    private class SendMessage extends AbstractAction {
        @Override
        public void actionPerformed(ActionEvent e) {
            try {
                String message = text.getText();
                if (message.trim().length() > 0) {
                    writer.writeMessage(message);
                    text.setText("");
                    resizeTextBox(2);
                    chatStateManager.updateChatState(ChatState.active);
                }
            } catch (FriendException e1) {
                LOG.error("Unable to write message", e1);
            }
        }
    }
    
    private class ChatStateManager implements DocumentListener {
        private final Periodic chatPausedTimer;
        private volatile ChatState currentInputChatState = ChatState.active;

        ChatStateManager(ScheduledExecutorService schedExecService) {
            chatPausedTimer = new Periodic(new Runnable() {

                @Override
				public void run() {
                    if (currentInputChatState == ChatState.composing) {
                        updateChatState(ChatState.paused);
                    }
                }
            }, schedExecService);
        }

        void updateChatState(ChatState state) {
            if (currentInputChatState != state) {
                currentInputChatState = state;
                try {
                    writer.setChatState(currentInputChatState);
                } catch (FriendException e) {
                    LOG.error("Unable to set chat state", e);
                }
            }
        }

        void cancelPendingChatStateUpdates() {
            chatPausedTimer.unschedule();
        }

        @Override
        public void changedUpdate(DocumentEvent e) {
            updateChatStateComposing();
        }

        @Override
        public void insertUpdate(DocumentEvent e) {
            updateChatStateComposing();
        }

        @Override
        public void removeUpdate(DocumentEvent e) {
            if (e.getDocument().getLength() == 0) {
                updateChatState(ChatState.active);
            } else {
                updateChatStateComposing();
            }
        }

        private void updateChatStateComposing() {
            // update chat state to "composing", if necessary
            updateChatState(ChatState.composing);

            chatPausedTimer.rescheduleIfLater(DELAY_IN_MILLIS_FOR_CHAT_PAUSED);
        }
    }
}
