package org.limewire.ui.swing.search;

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

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.EnumMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.swing.Action;
import javax.swing.ButtonGroup;
import javax.swing.Icon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
import javax.swing.SwingUtilities;

import org.jdesktop.application.Resource;
import org.jdesktop.swingx.JXButton;
import org.limewire.core.api.search.SearchCategory;
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.Disposable;
import org.limewire.ui.swing.components.LimeComboBox;
import org.limewire.ui.swing.components.LimeComboBox.SelectionListener;
import org.limewire.ui.swing.components.decorators.ButtonDecorator;
import org.limewire.ui.swing.components.decorators.ComboBoxDecorator;
import org.limewire.ui.swing.painter.ButtonBackgroundPainter.DrawMode;
import org.limewire.ui.swing.search.model.SearchResultsModel;
import org.limewire.ui.swing.search.model.SortOption;
import org.limewire.ui.swing.settings.SwingUiSettings;
import org.limewire.ui.swing.util.GuiUtils;

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

/**
 * This class manages the UI components for filtering and sorting
 * search results.
 * 
 * @see org.limewire.ui.swing.search.SearchResultsPanel
 */
public class SortAndFilterPanel implements Disposable {

    private final ButtonDecorator buttonDecorator;

    /** Search results data model. */
    private final SearchResultsModel searchResultsModel;
    
    /** Map of sort options and actions. */ 
    private final Map<SortOption, Action> actionMap = new EnumMap<SortOption, Action>(SortOption.class); 

    @Resource private Icon listViewIcon;
    @Resource private Icon tableViewIcon;
    
    @Resource private Font sortLabelFont;

    private final LimeComboBox sortCombo;
    
    private final JLabel sortLabel = new JLabel(tr("Sort by:"));
    private final JXButton listViewToggleButton = new JXButton();
    private final JXButton tableViewToggleButton = new JXButton();
    
    private SortOption sortBy;
    
    private boolean repopulatingCombo;
    private SettingListener viewTypeListener;

    /**
     * Constructs a SortAndFilterPanel with the specified search results data
     * model and UI decorators.
     */
    @Inject
    SortAndFilterPanel(
            @Assisted SearchResultsModel searchResultsModel,
            ComboBoxDecorator comboBoxDecorator, 
            ButtonDecorator buttonDecorator) {
        
        GuiUtils.assignResources(this);
        
        this.buttonDecorator = buttonDecorator;
        this.searchResultsModel = searchResultsModel;
        
        // Initialize sort actions.
        populateActionList();
        
        // Initialize sort components.
        sortLabel.setFont(sortLabelFont);
        sortLabel.setForeground(Color.WHITE);
        
        sortCombo = new LimeComboBox();
        comboBoxDecorator.decorateDarkFullComboBox(sortCombo);
        sizeSortCombo();

        // Initialize components to select view type.
        listViewToggleButton.setModel(new JToggleButton.ToggleButtonModel());
        tableViewToggleButton.setModel(new JToggleButton.ToggleButtonModel());
        setSearchCategory(searchResultsModel.getSearchCategory());
        configureViewButtons();
        
        // Initialize sorting and filtering.
        configureSortFilter();
    }

    /**
     * Removes listeners to external resources. 
     */
    @Override
    public void dispose() {
        SwingUiSettings.SEARCH_VIEW_TYPE_ID.removeSettingListener(viewTypeListener);
    }

    /**
     * Updates the size of the sort combobox based on the display length of the
     * current sort options.
     */
    private void sizeSortCombo() {
        int widestActionText = 0;
        FontMetrics sortFontMetrics = sortCombo.getFontMetrics(sortCombo.getFont());
        
        for (SortOption sortOption : actionMap.keySet()) {
            widestActionText = Math.max(widestActionText, 
                    sortFontMetrics.stringWidth(getDisplayName(sortOption)));
        }
        
        //Width of text plus padding for the whitespace around the text and the drop down icon
        Dimension sortComboDimensions = new Dimension(widestActionText + 30, sortCombo.getPreferredSize().height);
        sortCombo.setPreferredSize(sortComboDimensions);
        sortCombo.setMinimumSize(sortComboDimensions);
        sortCombo.setMaximumSize(sortComboDimensions);
    }

    /**
     * Initializes the collection of sort actions. 
     */
    private void populateActionList() {
        for (SortOption sortOption : SortOption.values()) {
            actionMap.put(sortOption, new SortAction(sortOption));
        }
    }

    /**
     * Configures the UI components used to select the view type.
     */
    private void configureViewButtons() {

        buttonDecorator.decorateDarkFullImageButton(listViewToggleButton, DrawMode.LEFT_ROUNDED);
        listViewToggleButton.setIcon(listViewIcon);
        listViewToggleButton.setPressedIcon(listViewIcon);
        listViewToggleButton.setToolTipText(tr("List view"));
        listViewToggleButton.setMargin(new Insets(0, 10, 0, 6));

        listViewToggleButton.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent event) {
                if (event.getStateChange() == ItemEvent.SELECTED) {
                    SwingUiSettings.SEARCH_VIEW_TYPE_ID.setValue(SearchViewType.LIST.getId());
                    if (searchResultsModel.getSearchCategory() == SearchCategory.TORRENT) {
                        SwingUiSettings.TORRENT_SEARCH_VIEW_TYPE_ID.setValue(SearchViewType.LIST.getId());
                    }
                    selectListView();
                }
            }
        });

        buttonDecorator.decorateDarkFullImageButton(tableViewToggleButton, DrawMode.RIGHT_ROUNDED);
        tableViewToggleButton.setIcon(tableViewIcon);
        tableViewToggleButton.setPressedIcon(tableViewIcon);
        tableViewToggleButton.setToolTipText(tr("Classic view"));
        tableViewToggleButton.setMargin(new Insets(0, 6, 0, 10));
        
        tableViewToggleButton.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent event) {
                if (event.getStateChange() == ItemEvent.SELECTED) {
                    SwingUiSettings.SEARCH_VIEW_TYPE_ID.setValue(SearchViewType.TABLE.getId());
                    SwingUiSettings.SHOW_CLASSIC_REMINDER.setValue(false);
                    if (searchResultsModel.getSearchCategory() == SearchCategory.TORRENT) {
                        SwingUiSettings.TORRENT_SEARCH_VIEW_TYPE_ID.setValue(SearchViewType.TABLE.getId());
                    }
                    selectTableView();
                }
            }
        });

        viewTypeListener = new SettingListener() {
            @Override
            public void settingChanged(SettingEvent evt) {
                SwingUtilities.invokeLater(new Runnable(){
                    @Override
					public void run() {
                        int newViewTypeId = SwingUiSettings.SEARCH_VIEW_TYPE_ID.getValue();
                        SearchViewType newSearchViewType = SearchViewType.forId(newViewTypeId);
                        updateView(newSearchViewType);                        
                    }
                });
            }
        };
        SwingUiSettings.SEARCH_VIEW_TYPE_ID.addSettingListener(viewTypeListener);
        ButtonGroup viewGroup = new ButtonGroup();
        viewGroup.add(listViewToggleButton);
        viewGroup.add(tableViewToggleButton);
        
        updateView(SearchViewType.getSearchViewType(searchResultsModel.getSearchCategory()));
    }

    /**
     * Updates the UI components based on the specified view type.
     */
    private void updateView(SearchViewType newSearchViewType) {
        switch (newSearchViewType) {
        case LIST:
            selectListView();
            break;
        case TABLE:
            selectTableView();
            break;
        }
    }
    
    /**
     * Updates the UI components when the List view is selected. 
     */
    private void selectListView() {
        tableViewToggleButton.setSelected(false);
        listViewToggleButton.setSelected(true);
        sortLabel.setVisible(true);
        sortCombo.setVisible(true);
    }

    /**
     * Updates the UI components when the Table view is selected. 
     */
    private void selectTableView() {
        tableViewToggleButton.setSelected(true);
        listViewToggleButton.setSelected(false);
        sortLabel.setVisible(false);
        sortCombo.setVisible(false);
    }

    /**
     * Configures the sort and filter components to work with the data model.
     */
    private void configureSortFilter() {
        // Initialize sort option in data model.
        searchResultsModel.setSortOption(SortOption.getDefault());
        
        // Install combobox listener to update sort order.
        SelectionListener listener = new SelectionListener() {
            @Override
            public void selectionChanged(Action action) {
                SortOption option = ((SortAction) action).getSortOption();
                if (!repopulatingCombo && !option.equals(sortBy)) {
                    // changing sort order
                    searchResultsModel.setSortOption(option);
                    sortBy = option;
                }
            }
        };
        sortCombo.addSelectionListener(listener);
        
        // Trigger the initial sort.
        sortCombo.setSelectedAction(actionMap.get(SortOption.getDefault()));
    }
    
    /**
     * Adds the sorting and filtering components to the specified panel.
     */
    public void layoutComponents(JPanel panel) {        
        panel.add(sortLabel, "gapafter 5, gapbottom 2, hidemode 0");
        panel.add(sortCombo, "gapafter 10, hidemode 0");
        panel.add(listViewToggleButton, "right");
        panel.add(tableViewToggleButton, "gapafter 5");
    }

    /**
     * Clears the filter text field.
     *
    public void clearFilterBox() {
        filterBox.setText("");
    }*/
    
    /**
     * Sets the state of the view toggle buttons.
     * @param mode the current mode ... LIST or TABLE
     */
    public void setMode(SearchViewType mode) {
        if (mode == SearchViewType.LIST) {
            listViewToggleButton.setSelected(true);
            tableViewToggleButton.setSelected(false);
        } else if (mode == SearchViewType.TABLE) {
            listViewToggleButton.setSelected(false);
            tableViewToggleButton.setSelected(true);
        }
    }

    /**
     * Sets the search category, and updates the sort combobox with the 
     * appropriate list of sort selections.
     */
    public void setSearchCategory(SearchCategory category) {
        Action currentItem = sortCombo.getSelectedAction();
        boolean currentValid = false;
        
        repopulatingCombo = true;
        sortCombo.removeAllActions();
        
        // Get sort options for category.
        SortOption[] options = SortOption.getSortOptions(category);

        // Create list of sort actions.  We also determine if the current
        // sort action is valid for the new category.
        List<Action> actionList = new LinkedList<Action>();
        for (SortOption option : options) {
            actionList.add(actionMap.get(option));
            if (actionMap.get(option).equals(currentItem)) {
                currentValid = true;
            }
        }
        
        sortCombo.addActions(actionList);
        
        sizeSortCombo();

        repopulatingCombo = false;

        // Set combobox to current action if valid.  Otherwise, set search 
        // model to use first sort option.
        if (currentValid) {
            sortCombo.setSelectedAction(currentItem);
        } else {
            searchResultsModel.setSortOption(options[0]);
            sortBy = options[0];
        }
    }

    /**
     * Returns the display name for the specified SortOption.
     */
    private String getDisplayName(SortOption sortOption) {
        switch (sortOption) {
        case COMPANY: return tr("Company");
        case PLATFORM: return tr("Platform");
        case TYPE: return tr("Type");
        case DATE_CREATED: return tr("Date Created");
        case QUALITY: return tr("Quality");
        case YEAR: return tr("Year");
        case FILE_EXTENSION: return tr("File Extension");
        case TITLE: return tr("Title");
        case LENGTH: return tr("Length");
        case ALBUM: return tr("Album");
        case ARTIST: return tr("Artist");
        case SIZE_LOW_TO_HIGH: return tr("Size (low to high)");
        case SIZE_HIGH_TO_LOW: return tr("Size (high to low)");
        case CATEGORY: return tr("Category");
        case NAME: return tr("Name");
        case RELEVANCE_ITEM: return tr("Relevance");
        default: return sortOption.name();
        }
    }

    /**
     * An Action implementation for a sort option.
     */
    private class SortAction extends AbstractAction {
        private final SortOption sortOption;
        
        public SortAction(SortOption sortOption) {
            super(getDisplayName(sortOption));
            this.sortOption = sortOption;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
        }

        public SortOption getSortOption() {
            return sortOption;
        }
    }
}
