package org.limewire.ui.swing.options;

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.KeyStroke;

import net.miginfocom.swing.MigLayout;

import org.jdesktop.application.Resource;
import org.jdesktop.swingx.JXPanel;
import org.limewire.core.api.Application;
import org.limewire.ui.swing.components.FocusJOptionPane;
import org.limewire.ui.swing.components.LimeJDialog;
import org.limewire.ui.swing.options.OptionPanel.ApplyOptionResult;
import org.limewire.ui.swing.options.actions.ApplyOptionAction;
import org.limewire.ui.swing.options.actions.CancelOptionAction;
import org.limewire.ui.swing.options.actions.HelpAction;
import org.limewire.ui.swing.options.actions.TabAction;
import org.limewire.ui.swing.painter.factories.BarPainterFactory;
import org.limewire.ui.swing.util.GuiUtils;
import org.limewire.ui.swing.util.I18n;
import org.limewire.util.OSUtils;

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

/**
 * Main Dialog for the Options.
 */
public class OptionsDialog extends LimeJDialog implements OptionsTabNavigator {

    @Resource
    private Icon advancedIcon;
    @Resource
    private Icon transfersIcon;
    @Resource
    private Icon libraryIcon;
    @Resource
    private Icon miscIcon;
    @Resource
    private Icon searchIcon;
    
    
    public static final String LIBRARY = I18n.tr("My Files");
    public static final String SEARCH = I18n.tr("Search");
    public static final String TRANSFERS = I18n.tr("Transfers");
    public static final String SECURITY = I18n.tr("Security");
    public static final String MISC = I18n.tr("Miscellaneous");
    public static final String ADVANCED = I18n.tr("Advanced");
    
    private final Provider<LibraryOptionPanel> libraryOptionPanel;
    private final Provider<SearchOptionPanel> searchOptionPanel;
    private final Provider<TransferOptionPanel> transferOptionPanel;
    private final Provider<MiscOptionPanel> miscOptionPanel;
    private final Provider<AdvancedOptionPanel> advancedOptionPanel;
    private final UnsafeTypeOptionPanelStateManager unsafeTypeOptionPanelStateManager;
    private final Application application;
    
    private final Map<String, OptionTabItem> cards = new HashMap<String,OptionTabItem>();
    private final Map<String, OptionPanel> panels = new HashMap<String, OptionPanel>();
    private final List<String> list = new ArrayList<String>();
    private OptionTabItem selectedItem;
    
    private JPanel cardPanel;
    private CardLayout cardLayout;
    
    private JXPanel headerPanel;
    private JPanel footerPanel;
    
    private JButton helpButton;
    private JButton okButton;
    private JButton cancelButton;
    
    @Inject
    public OptionsDialog(Provider<LibraryOptionPanel> libraryOptionPanel, Provider<SearchOptionPanel> searchOptionPanel,
            Provider<TransferOptionPanel> transferOptionPanel,
            Provider<MiscOptionPanel> miscOptionPanel, Provider<AdvancedOptionPanel> advancedOptionPanel,
            BarPainterFactory barPainterFactory, UnsafeTypeOptionPanelStateManager unsafeTypeOptionPanelStateManager,
            Application application) {
        super(GuiUtils.getMainFrame(), (OSUtils.isMacOSX()) ? I18n.tr("Preferences") : I18n.tr("Options"), true);

        GuiUtils.assignResources(this); 
        
        this.application = application;
        this.libraryOptionPanel = libraryOptionPanel;
        this.searchOptionPanel = searchOptionPanel;
        this.transferOptionPanel = transferOptionPanel;
        this.miscOptionPanel = miscOptionPanel;
        this.advancedOptionPanel = advancedOptionPanel;
        this.unsafeTypeOptionPanelStateManager = unsafeTypeOptionPanelStateManager;
        
        if (!OSUtils.isMacOSX()) {
            setSize(725, 562);
            setPreferredSize(getSize());
        } else {
            setSize(835, 613);
            setPreferredSize(getSize());
        }
        setResizable(false);
        
        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        
        createComponents(barPainterFactory);
        
        pack();
    }
    
    public ApplyOptionResult applyOptions() {
        ApplyOptionResult result = null;
        boolean done = false;
        Iterator<OptionPanel> it = panels.values().iterator();
        while (it.hasNext() && !done) {
            OptionPanel panel = it.next();
            if (result == null)
                result = panel.applyOptions();
            else
                result.applyResult(panel.applyOptions());
            done = !result.isSuccessful();
        }
        
        if (result != null && result.isRestartRequired() && result.isSuccessful()) {
            FocusJOptionPane
                    .showMessageDialog(
                            this,
                            I18n.tr("One or more options will take effect the next time WireShare is restarted."),
                            I18n.tr("Message"), JOptionPane.INFORMATION_MESSAGE);

        }
        return result;

    }
    
    private void createComponents(BarPainterFactory barPainterFactory) {
        setLayout(new BorderLayout());
        
        cardLayout = new CardLayout();
        cardPanel = new JPanel();
        cardPanel.setLayout(cardLayout);
        
        headerPanel = new JXPanel();
        headerPanel.setBackgroundPainter(barPainterFactory.createTopBarPainter());
                
        footerPanel = new JPanel();
        footerPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 8, 0));
        
        createFooter();
        createHeader();
        
        select(LIBRARY);
        
        add(headerPanel, BorderLayout.NORTH);
        add(cardPanel, BorderLayout.CENTER);
        add(footerPanel, BorderLayout.SOUTH);
        

    }
    
    private void createHeader() {
        headerPanel.setLayout(new MigLayout("insets 0 0 0 0, gap 0"));
        
        MoveDown down = new MoveDown();
        MoveUp up = new MoveUp();
        
        createButton(LIBRARY, libraryIcon, libraryOptionPanel, down, up);
        createButton(SEARCH, searchIcon, searchOptionPanel, down, up);
        createButton(TRANSFERS, transfersIcon, transferOptionPanel, down, up);
        createButton(MISC, miscIcon, miscOptionPanel, down, up);
        createButton(ADVANCED, advancedIcon, advancedOptionPanel, down, up);
    }
    
    private void createButton(String title, Icon icon,  Provider<? extends OptionPanel> provider, MoveDown down, MoveUp up) {
        FancyOptionTabButton button = new FancyOptionTabButton(new TabAction(icon, addOptionTab(title, this, provider)));
        
        button.getActionMap().put(MoveDown.KEY, down);
        button.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), MoveDown.KEY);
        button.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), MoveDown.KEY);
        
        button.getActionMap().put(MoveUp.KEY, up);
        button.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), MoveUp.KEY);
        button.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), MoveUp.KEY);
        
        headerPanel.add(button);
    }
    
    private class MoveDown extends AbstractAction {
        final static String KEY = "MOVE_DOWN";
        
        @Override
        public void actionPerformed(ActionEvent e) {
            next();
        }
    }
    
    private class MoveUp extends AbstractAction {
        final static String KEY = "MOVE_UP";
        
        @Override
        public void actionPerformed(ActionEvent e) {
            previous();
        }
    }
    
    private void createFooter() {
        footerPanel.setLayout(new MigLayout("insets 0 15 0 15, aligny 50%"));
        footerPanel.setBackground(GuiUtils.getMainFrame().getBackground());
        
        helpButton = new JButton(new HelpAction(application));
        
        okButton = new JButton(I18n.tr("OK"));
        okButton.addActionListener(new ApplyOptionAction(this));
        
        cancelButton = new JButton(I18n.tr("Cancel"));
        cancelButton.addActionListener(new CancelOptionAction(this));
        
        footerPanel.add(helpButton, "push");
        footerPanel.add(okButton, "split, tag ok");
        footerPanel.add(cancelButton, "tag cancel");
    }

    @Override
    public OptionTabItem addOptionTab(final String title, final OptionsTabNavigator navigator, final Provider<? extends OptionPanel> optionProvider) {
        return new OptionsTabItemImpl(title, navigator, optionProvider);
    }
    
    public void next() {
        for(int i = 0; i < list.size(); i++) {
            if(list.get(i).equals(selectedItem.getId())) {
                if(i == list.size() -1) {
                    select(list.get(0));
                } else
                    select(list.get(i+1));
                break;
            }
        }
    }
    
    public void previous() {
        for(int i = 0; i < list.size(); i++) {
            if(list.get(i).equals(selectedItem.getId())) {
                if(i == 0) {
                    select(list.get(list.size()-1));
                } else
                    select(list.get(i-1));
                break;
            }
        }
    }

    @Override
    public void select(String title) {
        if(selectedItem != null)
            ((OptionsTabItemImpl)selectedItem).fireSelected(false);
        selectedItem = cards.get(title);
        if(!panels.containsKey(title)) {
            createPanel(selectedItem.getId(), selectedItem.getOptionPanel());
        }
        ((OptionsTabItemImpl)selectedItem).fireSelected(true);
        cardLayout.show(cardPanel, title);
    }
    
    /**
     * Lazily loads and inits a subPanel in the OptionDialog.
     */
    private void createPanel(String id, OptionPanel panel) {
        panel.setBackground(GuiUtils.getMainFrame().getBackground());
        panels.put(selectedItem.getId(), panel);
        cardPanel.add(panels.get(selectedItem.getId()), selectedItem.getId());
        // Library is always shown so it will always be initialized prior to being shown
        // no need to do it now.
        
        if(!selectedItem.getId().equals(LIBRARY)) 
            panel.initOptions();
    }
    
    private class OptionsTabItemImpl implements OptionTabItem {

        private final List<TabItemListener> listeners = new CopyOnWriteArrayList<TabItemListener>();
        private final String name;
        private final OptionsTabNavigator navigator;
        private final Provider<? extends OptionPanel> provider;
        
        public OptionsTabItemImpl(String title, OptionsTabNavigator navigator, Provider<? extends OptionPanel> optionProvider) {
            this.name = title;
            this.navigator = navigator;
            this.provider = optionProvider;
            
            cards.put(title, this);
            list.add(title);
        }
        
        @Override
        public String getId() {
            return name;
        }

        @Override
        public boolean isSelected() {
            return false;
        }

        @Override
        public void select() {
            navigator.select(name);
        }
        
        @Override
		public OptionPanel getOptionPanel() {
            OptionPanel panel = provider.get();
            panel.setOptionTabItem(this);
            
            return panel;
        }

        @Override
        public void addTabItemListener(TabItemListener listener) {
            listeners.add(listener);
        }

        @Override
        public void removeTabItemListener(TabItemListener listener) {
            listeners.remove(listener);
        }
        
        public void fireSelected(boolean selected) {
            for(TabItemListener listener : listeners) {
                listener.itemSelected(selected);
            }
        }
    }

    /**
     * Recalls init options on all created panels.
     */
    public void initOptions() {
        unsafeTypeOptionPanelStateManager.initOptions();
        
        for(OptionPanel optionPanel : panels.values()) {
            optionPanel.initOptions();
        }
    }
}
