package org.limewire.ui.swing.search;

import java.awt.Color;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.LinkedList;
import java.util.List;

import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;

import net.miginfocom.swing.MigLayout;

import org.jdesktop.application.Resource;
import org.jdesktop.swingx.JXPanel;
import org.limewire.collection.AutoCompleteDictionary;
import org.limewire.core.api.search.SearchCategory;
import org.limewire.core.settings.SearchSettings;
import org.limewire.friend.api.FriendConnectionEvent;
import org.limewire.listener.EventListener;
import org.limewire.listener.ListenerSupport;
import org.limewire.listener.SwingEDTEvent;
import org.limewire.setting.evt.SettingEvent;
import org.limewire.setting.evt.SettingListener;
import org.limewire.ui.swing.action.AbstractAction;
import org.limewire.ui.swing.components.DropDownListAutoCompleteControl;
import org.limewire.ui.swing.components.IconButton;
import org.limewire.ui.swing.components.LimeComboBox;
import org.limewire.ui.swing.components.PromptTextField;
import org.limewire.ui.swing.components.LimeComboBox.SelectionListener;
import org.limewire.ui.swing.components.decorators.ComboBoxDecorator;
import org.limewire.ui.swing.components.decorators.TextFieldDecorator;
import org.limewire.ui.swing.painter.BorderPainter.AccentType;
import org.limewire.ui.swing.search.advanced.AdvancedPopupPanel;
import org.limewire.ui.swing.settings.SwingUiSettings;
import org.limewire.ui.swing.util.CategoryIconManager;
import org.limewire.ui.swing.util.GuiUtils;
import org.limewire.ui.swing.util.I18n;
import org.limewire.ui.swing.util.SwingUtils;
import org.limewire.util.I18NConvert;

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

/**
 * The search bar component at the top of the UI main window.  This includes
 * the drop-down search category box, search input field, and search button.
 */
public class SearchBar extends JXPanel {

    @Resource private Color searchBorder;
    
    private final LimeComboBox comboBox;
    private final PromptTextField searchField;
    private final IconButton searchButton;
    
    private final HistoryAndFriendAutoCompleter autoCompleter;
    private final SmartAutoCompleteFactory smartAutoCompleteFactory;
    private final AutoCompleteDictionary searchHistory;
    private final Provider<AdvancedPopupPanel> advancedPopupProvider;
    private final Action advancedAction;
    
    private SearchCategory categoryToSearch; 
    
    private static volatile int categorySwitched = 0;
    
    
    @Inject
    public SearchBar(ComboBoxDecorator comboBoxDecorator, 
            SmartAutoCompleteFactory smartAutoCompleteFactory,
            @Named("searchHistory") AutoCompleteDictionary searchHistory,
            final HistoryAndFriendAutoCompleter autoCompleter,
            Provider<AdvancedPopupPanel> advancedPopupProvider,
            CategoryIconManager categoryIconManager,
            TextFieldDecorator textFieldDecorator) {
        super(new MigLayout("ins 0, gapx 0, gapy 0"));
    
        GuiUtils.assignResources(this);
        
        this.smartAutoCompleteFactory = smartAutoCompleteFactory;
        this.searchHistory = searchHistory;
        this.autoCompleter = autoCompleter;
        this.advancedPopupProvider = advancedPopupProvider;
        this.categoryToSearch = SearchCategory.forId(SwingUiSettings.DEFAULT_SEARCH_CATEGORY_ID.getValue());
        this.advancedAction = new AdvancedAction();
        
        Action actionToSelect = null;
        
        List<Action> typeActions = new LinkedList<Action>();
        for (SearchCategory cat : SearchCategory.values()) {
            if (cat == SearchCategory.OTHER) {
                continue;
            }

            Icon icon = null;
            if(cat != SearchCategory.ALL) {
                icon = categoryIconManager.getIcon(cat.getCategory());                
            }
            Action action = new CategoryAction(cat, icon);
            if (cat == this.categoryToSearch) {
                actionToSelect = action;
            }

            typeActions.add(action);
        }
        typeActions.add(advancedAction);

        comboBox = new LimeComboBox(typeActions);
        comboBoxDecorator.decorateLightFullComboBox(comboBox);
        comboBox.setName("SearchBar.comboBox");
        // Disable fields when Advanced Search selected.
        comboBox.addSelectionListener(new SelectionListener() {
            @Override
            public void selectionChanged(Action action) {
                boolean advanced = action instanceof AdvancedAction;
                searchField.setEnabled(!advanced);
                searchButton.setEnabled(!advanced);
            }
        });
                
        searchField = new PromptTextField(I18n.tr("Search..."));
        textFieldDecorator.decoratePromptField(searchField, AccentType.BUBBLE, searchBorder);
        searchField.setName("SearchBar.searchField");
        searchField.setDocument(new SearchFieldDocument());
        // Show Advanced Search popup when field disabled.
        searchField.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (!searchField.isEnabled()) {
                    advancedAction.actionPerformed(null);
                }
            }
        });
        
        searchButton = new IconButton();
        searchButton.removeActionHandListener();
        searchButton.setName("SearchBar.searchButton");
        searchButton.setFocusPainted(false);
        searchButton.setToolTipText(I18n.tr("Search Peer-to-Peer Network"));
        
        final DropDownListAutoCompleteControl autoCompleteControl = DropDownListAutoCompleteControl.install(this.searchField, autoCompleter);
        autoCompleteControl.setAutoComplete(true);
        autoCompleter.setHistoryDictionary(searchHistory);
        addSearchActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                // Get search text, and add to history if non-empty.
                String searchText = getSearchText();
                if (!searchText.isEmpty() && SwingUiSettings.KEEP_SEARCH_HISTORY.getValue()) {
                    SearchBar.this.searchHistory.addEntry(searchText);
                }
            }
        });
        SwingUiSettings.SHOW_SMART_SUGGESTIONS.addSettingListener(new SettingListener() {
            @Override
            public void settingChanged(SettingEvent evt) {
                SwingUtils.invokeNowOrLater(new Runnable() {
                    @Override
                    public void run() {
                        autoCompleter.setSuggestionsShown(SwingUiSettings.SHOW_SMART_SUGGESTIONS.getValue());
                    }
                });
            }
        });
        
        autoCompleter.setSuggestionsShown(SwingUiSettings.SHOW_SMART_SUGGESTIONS.getValue());
        if (actionToSelect != null) {
            autoCompleter.setSmartDictionary(smartAutoCompleteFactory.create(categoryToSearch));
            comboBox.setSelectedAction(actionToSelect);
        } else {
            autoCompleter.setSmartDictionary(smartAutoCompleteFactory.create(SearchCategory.ALL));
        }
        
        setOpaque(false);
        add(comboBox);
        add(searchField, "gap 5");
        add(searchButton, "gap 5");
    }

    /**
     * Performs additional Guice injection tasks.  This method adds a listener
     * to handle XMPP connection events.
     */
    @Inject
    void register(ListenerSupport<FriendConnectionEvent> connectionSupport) {
        connectionSupport.addListener(new EventListener<FriendConnectionEvent>() {
            @Override
            @SwingEDTEvent
            public void handleEvent(FriendConnectionEvent event) {
                switch (event.getType()) {
                case CONNECTED:
                    searchButton.setToolTipText(I18n.tr("Search Peer-to-Peer Network and Friends"));
                    break;
                default:
                    searchButton.setToolTipText(I18n.tr("Search Peer-to-Peer Network"));
                    break;
                }
            }
        });
    }
    
    public boolean requestSearchFocus() {
        // Request w/ temporary==true to indicate we don't
        // want to select everything.
        return searchField.requestFocus(true);
    }
    
    public void selectAllSearchText() {
        searchField.setCaretPosition(searchField.getDocument().getLength());
        searchField.selectAll();
    }

    /**
     * Sets the search text.
     */
    public void setText(String text) {
        searchField.setText(text);
    }
    
    /**
     * Sets the selected category in the combo box.
     */
    public void setCategory(SearchCategory category) {
        
        for ( Action a : comboBox.getActions() ) {
            if (a instanceof CategoryAction) {
                CategoryAction categoryAction = (CategoryAction) a;
                if (categoryAction.getCategory() == category) {
                    comboBox.selectAction(categoryAction);
                    return;
                }
            }
        }
    }
    
    public void addSearchActionListener(ActionListener actionListener) {
        searchField.addActionListener(actionListener);
        searchButton.addActionListener(actionListener);
    }
    
    /**
     * Returns the search text with leading and trailing whitespace removed.
     * The method returns an empty string if there are no non-whitespace
     * characters.
     */
    public String getSearchText() {
        // Get search text and trim whitespace.
        String searchText = searchField.getText();
        return searchText != null ? searchText.trim() : "";
    }
    
    public SearchCategory getCategory() {
        return categoryToSearch;
    }
    
    /**
     * Selects the advanced search option.
     */
    public void selectAdvancedSearch() {
        comboBox.selectAction(advancedAction);
    }
    
    /**
     * Action to display the advanced search popup dialog.
     */
    private class AdvancedAction extends AbstractAction {

        AdvancedAction() {
            super(I18n.tr("Advanced"));
            putValue(AbstractAction.SEPARATOR, true);
        }
        
        @Override
        public void actionPerformed(ActionEvent e) {
            // Display popup dialog.
            advancedPopupProvider.get().showPopup(comboBox, searchField.getX(), 
                    searchField.getY() + searchField.getHeight() - 1, searchField.getWidth());
        }
    }
    
    /**
     * Action representing a search category.
     */
    private class CategoryAction extends AbstractAction {
        private final SearchCategory category;
        
        CategoryAction(SearchCategory category, Icon icon) {
            super(SearchCategoryUtils.getName(category));
            
            putValue(SMALL_ICON, icon);
            this.category = category;
        }
        
        @Override
        public void actionPerformed(ActionEvent arg0) {
            categoryToSearch = category;            
            autoCompleter.setSmartDictionary(smartAutoCompleteFactory.create(category));
            categorySwitched++;
        }
        
        public SearchCategory getCategory() {
            return category;
        }
    }

    /**
     * Helper class that filters out all characters that make the search longer
     * than the maximum allowed length.  If characters entered make the search
     * field too long (normalized or not normalized), the system should beep.
     */
    private static class SearchFieldDocument extends PlainDocument {

        private static final int MAX_QUERY_LENGTH =
                SearchSettings.MAX_QUERY_LENGTH.getValue();


        @Override
        public void insertString(int offs,
                                 String str,
                                 AttributeSet a) throws BadLocationException {

            if(str == null) {
                return;
            }

            if(offs >= MAX_QUERY_LENGTH) {
                Toolkit.getDefaultToolkit().beep();
                return;
            }

            // Normalized String are maybe longer or shorter than MAX_QUERY_LENGTH
            String norm = I18NConvert.instance().getNorm(str);
            if (getMaxLength() + Math.max(str.length(), norm.length()) > MAX_QUERY_LENGTH) {
                Toolkit.getDefaultToolkit().beep();
                return;
            }
            super.insertString(offs, str, a);
        }

        /**
         * Returns the maximum length of the existing text normalized or not
         * normalized.
         */
        private int getMaxLength() {
            try {
                String text = getText(0, getLength());
                return Math.max(text.length(), I18NConvert.instance().getNorm(text).length());
            } catch (BadLocationException e) {
                return 0;
            }
        }
    }
}
