package org.limewire.ui.swing.properties;

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

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.WindowConstants;

import net.miginfocom.swing.MigLayout;

import org.jdesktop.application.Resource;
import org.limewire.bittorrent.Torrent;
import org.limewire.core.api.FilePropertyKey;
import org.limewire.core.api.download.DownloadItem;
import org.limewire.core.api.library.LibraryManager;
import org.limewire.core.api.library.LocalFileItem;
import org.limewire.core.api.library.PropertiableFile;
import org.limewire.ui.swing.action.AbstractAction;
import org.limewire.ui.swing.components.LimeJDialog;
import org.limewire.ui.swing.properties.FileInfoTabPanel.FileInfoTabListener;
import org.limewire.ui.swing.properties.FileInfoTabPanel.Tabs;
import org.limewire.ui.swing.util.GuiUtils;
import org.limewire.ui.swing.util.I18n;

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

/**
 * A Dialog for displaying FileInfo about a file. This dialog changes 
 * depending on the state of the file and the location of the file. 
 */
public class FileInfoDialog extends LimeJDialog {
    
    /**
     * Type of FileInfoDialog that can be created.
     */
    public static enum FileInfoType {
        /**
         * File exists within the current LimeWire.
         */
        LOCAL_FILE,
        
        /**
         * File is in the process of downloading or a portion
         *  of the file has been downloaded already,
         */
        DOWNLOADING_FILE,
        
        /**
         * File it contained on one or more networked computers but not
         *  within this client.
         */
        REMOTE_FILE,
        
        /**
         * The file represents a current upload.
         */
        UPLOADING_FILE;
    }
    
    @Resource private Color backgroundColor;

    private final LibraryManager libraryManager;
    
    private final FileInfoTabPanel tabPanel;
    private final FileInfoOverviewPanel overviewPanel;
    private final JPanel cardPanel;
    private final Map<Tabs, FileInfoPanel> cards;
    private JButton okButton;
    private PropertiableFile propertiableFile;
    private RenameListener renameListener;
    
    @Inject
    public FileInfoDialog(@Assisted PropertiableFile propertiable, @Assisted final FileInfoType type,
                        FileInfoTabPanel fileInfoTabPanel, final FileInfoPanelFactory fileInfoFactory,
                        LibraryManager libraryManager) {
        super(GuiUtils.getMainFrame());
        
        this.libraryManager = libraryManager;
        
        tabPanel = fileInfoTabPanel;
        cardPanel = new JPanel(new BorderLayout());
        cardPanel.setPreferredSize(new Dimension(400,600));
        cards = new HashMap<Tabs, FileInfoPanel>();
        this.propertiableFile = propertiable;
        
        GuiUtils.assignResources(this);
        
        cardPanel.setOpaque(false);
        createTabs(propertiableFile, type);
        cards.put(Tabs.GENERAL, fileInfoFactory.createGeneralPanel(type, propertiableFile));
        cardPanel.add(cards.get(Tabs.GENERAL).getComponent());
        
        updateTitle(propertiableFile.getFileName());
        
        setLayout(new MigLayout("gap 0, insets 0, fill"));
        getContentPane().setBackground(backgroundColor);

        overviewPanel = (FileInfoOverviewPanel) fileInfoFactory.createOverviewPanel(type, propertiableFile);
        
        add(overviewPanel.getComponent(), "dock north");
        add(tabPanel.getComponent(), "dock north");
        
        add(cardPanel, "gapleft 10, grow, gapright 10");
        createFooter();
    
        setPreferredSize(new Dimension(500,565));
        setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

        if (type == FileInfoType.LOCAL_FILE) {
            // TODO: In KDE there is a problem where minimise is possible and
            //  a minimised modal dialogue can't be restored.  The dialogue
            //  disappears and control can never be restored to the rest of the
            //  app.  Stopping resizability prevents this problem.
            setModalityType(ModalityType.APPLICATION_MODAL);
        }
        pack();

        setLocationRelativeTo(GuiUtils.getMainFrame());

        // listen to the visibility setting/ disposes of the dialog when it becomes invisible
        addComponentListener(new ComponentListener(){
            @Override
            public void componentHidden(ComponentEvent e) {
                dispose();
            }

            @Override
            public void componentMoved(ComponentEvent e) {}
            @Override
            public void componentResized(ComponentEvent e) {}
            @Override
            public void componentShown(ComponentEvent e) {
                if(okButton != null)
                    okButton.requestFocusInWindow();
            }
        });  

        renameListener = new RenameListener(libraryManager);
        libraryManager.getLibraryManagedList().addPropertyChangeListener(renameListener);
        
        tabPanel.addSearchTabListener(new FileInfoTabListener(){
            @Override
            public void tabSelected(Tabs tab) {
                if(!cards.containsKey(tab)) {
                    if(tab == Tabs.GENERAL) {
                        cards.put(tab, fileInfoFactory.createGeneralPanel(type, propertiableFile));
                    } else if(tab == Tabs.SHARING) {
                        cards.put(tab, fileInfoFactory.createSharingPanel(type, propertiableFile));
                    } else if(tab== Tabs.LIMITS) {
                        Torrent torrent = (Torrent) propertiableFile.getProperty(FilePropertyKey.TORRENT);
                        
                        if (torrent != null && torrent.isEditable()) {
                            cards.put(tab, fileInfoFactory.createLimitsPanel(type, propertiableFile));
                        }
                    } else if (tab == Tabs.TRACKERS) {
                        cards.put(tab, fileInfoFactory.createTrackersPanel(type, propertiableFile));
                    } else if(tab == Tabs.TRANSFERS) {
                        cards.put(tab, fileInfoFactory.createTransferPanel(type, propertiableFile));    
                    } else if(tab == Tabs.BITTORENT) {
                        Torrent torrent = (Torrent) propertiableFile.getProperty(FilePropertyKey.TORRENT);
                        
                        //we can't show the file info panel for torrents without metadata.
                        if(torrent != null && torrent.hasMetaData()) {
                            cards.put(tab, fileInfoFactory.createBittorentPanel(torrent));
                        }
                    } else if (tab == Tabs.PIECES) {
                        if(propertiableFile instanceof DownloadItem) {
                            cards.put(tab, fileInfoFactory.createPiecesPanel(type, (DownloadItem)propertiableFile));    
                        } 
                        else {
                            // TODO: Pieces should still be available in some cases for torrents..
                            //        Need to overhaul file info.
                        }
                    } else {
                        throw new IllegalStateException("Unknown state:" + tab);
                    }
                }
                cardPanel.removeAll();
                cardPanel.add(cards.get(tab).getComponent());
                FileInfoDialog.this.validate();
                FileInfoDialog.this.repaint();
            }
        });
    }
    
    @Override
    public void dispose() {
        overviewPanel.dispose();
        //unregister any listeners used and dispose of dialog when made invisible
        for(FileInfoPanel panel : cards.values()) {
            panel.dispose();
        }
        libraryManager.getLibraryManagedList().removePropertyChangeListener(renameListener);
        
        super.dispose();
    }
    
    private void updateTitle(String fileName) {
        setTitle(I18n.tr("{0} Properties", propertiableFile.getFileName()));
    }
    
    public void renameFile() {
        overviewPanel.enableRename();
    }
    
    private void createTabs(PropertiableFile propertiableFile, final FileInfoType type) {
        List<Tabs> tabs = new ArrayList<Tabs>();
        // general tab is always shown
        tabs.add(Tabs.GENERAL);
        
        Torrent torrent = (Torrent)propertiableFile.getProperty(FilePropertyKey.TORRENT);

        if (torrent != null) {
            if (torrent.isEditable()) {
                tabs.add(Tabs.LIMITS);
            }

            if (torrent.hasMetaData()) {
                tabs.add(Tabs.BITTORENT);
                tabs.add(Tabs.TRACKERS);
            }

            if (torrent.isEditable()) {
                tabs.add(Tabs.TRANSFERS);
                
                // TODO: Pieces should still be available in some cases for torrents..
                //        Need to overhaul file info.
                if (type == FileInfoType.DOWNLOADING_FILE) {
                    tabs.add(Tabs.PIECES);
                }
            }
        } else {
            if (type == FileInfoType.LOCAL_FILE) {
                tabs.add(Tabs.SHARING);
            }
            if (type != FileInfoType.REMOTE_FILE) {
                tabs.add(Tabs.TRANSFERS);
            }
            if (type == FileInfoType.DOWNLOADING_FILE) {
                tabs.add(Tabs.PIECES);
            }
        }
        
        tabPanel.setTabs(tabs);
    }
    
    /**
     * Adds a footer with the cancel/ok button to close the dialog.
     */
    private void createFooter() {
        okButton = new JButton(new OKAction());
        JPanel footerPanel = new JPanel(new MigLayout("fill, insets 0 15 10 15"));
        footerPanel.add(okButton, "alignx right, aligny bottom, split, tag ok");
        footerPanel.add(new JButton(new CancelAction()), "aligny bottom, tag cancel");
        footerPanel.setBackground(backgroundColor);
        
        add(footerPanel, "grow, south");
    }

    /**
     * Closes the dialog and saves any data that may have changed.
     */
    private class OKAction extends AbstractAction {
        public OKAction() {
            super(tr("OK"));
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            overviewPanel.save();
            
            for(FileInfoPanel panel : cards.values()) {
                panel.save();
            }
            setVisible(false);
        }
    }

    /**
     * Closes the data and does not save any data even if it
     * has changed.
     */
    private class CancelAction extends AbstractAction {
        public CancelAction() {
            super(tr("Cancel"));
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            setVisible(false);
        }
    }
    
    /**
	 * Listens for rename events. If this file was renamed, 
     * updates all the created panels with the new 
     * propertiable file since the underlying FileDesc
     * has changed.
 	 */
    private class RenameListener implements PropertyChangeListener {
        private LibraryManager libraryManager;
        
        public RenameListener(LibraryManager libraryManager) {
            this.libraryManager = libraryManager;
        }
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            // if its a rename event, check if this propertiable file name changed.
            if(evt.getPropertyName().equals("rename")) {
                // if this file name changed, update the propertiable file on all the created panels so
                // they can properlly save
                if( propertiableFile instanceof LocalFileItem && ((LocalFileItem)propertiableFile).getFile().equals(evt.getOldValue())) {
                    propertiableFile = libraryManager.getLibraryManagedList().getFileItem((File)evt.getNewValue());
                    for(FileInfoPanel panel : cards.values()) {
                        panel.updatePropertiableFile(propertiableFile);
                    }
                    overviewPanel.updatePropertiableFile(propertiableFile);
                    updateTitle(propertiableFile.getFileName());
                }
            }
        }
    }
}
