package org.limewire.ui.swing.properties;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;

import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
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.core.api.FilePropertyKey;
import org.limewire.core.api.library.LibraryManager;
import org.limewire.core.api.library.LocalFileItem;
import org.limewire.core.api.library.MagnetLinkFactory;
import org.limewire.core.api.library.PropertiableFile;
import org.limewire.ui.swing.action.AbstractAction;
import org.limewire.ui.swing.components.HyperlinkButton;
import org.limewire.ui.swing.components.TextFieldClipboardControl;
import org.limewire.ui.swing.painter.factories.BarPainterFactory;
import org.limewire.ui.swing.properties.FileInfoDialog.FileInfoType;
import org.limewire.ui.swing.search.model.VisualSearchResult;
import org.limewire.ui.swing.search.resultpanel.CopyMagnetLinkToClipboardAction;
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.IconManager;
import org.limewire.ui.swing.util.ResizeUtils;
import org.limewire.util.CommonUtils;
import org.limewire.util.FileUtils;
import org.limewire.util.OSUtils;

import com.google.inject.Provider;

/**
 * Displays general information about a PropertiableFile.
 */
class FileInfoOverviewPanel implements FileInfoPanel {

    @Resource private Color foreground;
    @Resource private Font smallFont;
    @Resource private Font headerFont;
    @Resource private Font headerFont2;
    
    private final FileInfoType type;
    private PropertiableFile propertiableFile;
    private final MagnetLinkFactory magnetLinkFactory;
    private final CategoryIconManager categoryIconManager;
    private final LibraryManager libraryManager;
    private final RenameAction renameAction;
    
    private final JXPanel component;
    private JButton renameButton;
    private JTextField nameLabel;
    private JTextField renameTextField;
    
    public FileInfoOverviewPanel(FileInfoType type, PropertiableFile propertiableFile, 
            Provider<IconManager> iconManager, MagnetLinkFactory magnetLinkFactory, 
            CategoryIconManager categoryIconManager,
            LibraryManager libraryManager,
            BarPainterFactory barPainterFactory) {
        this.type = type;
        this.propertiableFile = propertiableFile;
        this.magnetLinkFactory = magnetLinkFactory;
        this.categoryIconManager = categoryIconManager;
        this.libraryManager = libraryManager;
        this.renameAction = new RenameAction();
        
        GuiUtils.assignResources(this);
        
        component = new JXPanel(new MigLayout("fill, gap 0, insets 10 10 16 14"));
        component.setBackgroundPainter(barPainterFactory.createPopUpBarPainter());
        
        init();
    }
    
    @Override
	public JComponent getComponent() {
        return component;
    }
    
    @Override
    public boolean hasChanged() {
        return false;
    }

    @Override
    public void save() {
        // if file is currently being renamed, attempt to save the changes
        // before exiting
        if(renameTextField != null && renameTextField.isVisible())
            saveFileName();
    }
    
    @Override
    public void dispose() {
        //no listeners registered
    }
    

    @Override
    public void updatePropertiableFile(PropertiableFile file) {
        this.propertiableFile = file;
        nameLabel.setText(propertiableFile.getFileName());
    }
    
    public void enableRename() {
        renameAction.actionPerformed(null);
    }
    
    private void init() {
        component.setOpaque(false);

        addOverviewCategory();
        
        HyperlinkButton copyToClipboard = null;
        if(type == FileInfoType.LOCAL_FILE){
            if(propertiableFile instanceof LocalFileItem && ((LocalFileItem)propertiableFile).isShareable()) {
                copyToClipboard = new HyperlinkButton();
                copyToClipboard.setFont(smallFont);
                copyToClipboard.setAction(new AbstractAction(I18n.tr("Copy Magnet Link")) {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        StringSelection sel = new StringSelection(magnetLinkFactory.createMagnetLink((LocalFileItem)propertiableFile));
                        Toolkit.getDefaultToolkit().getSystemClipboard().setContents(sel, sel);
                    }
                });
            }
        } else if(type == FileInfoType.REMOTE_FILE) {
            if(propertiableFile instanceof VisualSearchResult) {
                copyToClipboard = new HyperlinkButton();
                copyToClipboard.setFont(smallFont);
                copyToClipboard.setAction(new CopyMagnetLinkToClipboardAction((VisualSearchResult)propertiableFile));
            }
        } 
        //HyperlinkButton moreFileInfo = new HyperlinkButton(new BitziLookupAction(propertiableFile));
        //moreFileInfo.setFont(smallFont);
      
        if(type == FileInfoType.LOCAL_FILE && propertiableFile instanceof LocalFileItem) {
            renameButton = new HyperlinkButton(renameAction);
            renameButton.setFont(smallFont);
            component.add(renameButton, "cell 1 1, alignx right");
            
        }
        //component.add(moreFileInfo, "cell 1 2, alignx right");
        if(copyToClipboard != null)
            component.add(copyToClipboard, "cell 1 2, alignx right");
    }
    

    /**
     * Loads values for the Overview Panel. If a given field is null or 
     * doesn't exist, the field is ignored.
     */
    private void addOverviewCategory() {
        Icon icon = getIcon(propertiableFile);
        JPanel iconDock = new JPanel();
        iconDock.setOpaque(false);
        iconDock.add(new JLabel(icon));
        
        String title = propertiableFile.getPropertyString(FilePropertyKey.TITLE);
        
        component.add(iconDock, "split, aligny top, growy, gapafter 0");
        if (title != null) {
            nameLabel = createLabelField(title);
        } 
        else {
            nameLabel = createLabelField(propertiableFile.getFileName());
        }
        nameLabel.setFont(headerFont);
        nameLabel.setPreferredSize(new Dimension(440, 26));
        component.add(nameLabel, "growx, span, wrap");
                
        if (title != null) {
            JTextField fileNameField = createLabelField(propertiableFile.getFileName());
            fileNameField.setFont(headerFont2);
            component.add(fileNameField, "gapbefore 4, growx, wrap");
        }
        
        String info = "";
        if (!("".equals(FileInfoUtils.getFileSize(propertiableFile)))) {
            info = FileInfoUtils.getFileSize(propertiableFile) + " (" + FileInfoUtils.getFileSizeBytes(propertiableFile)+ ")";
        }
            
        switch(propertiableFile.getCategory()) {
            case AUDIO:
                String time = FileInfoUtils.getLength(propertiableFile);
                String bitrate = propertiableFile.getPropertyString(FilePropertyKey.BITRATE);
            
                if(time != null) {
                    if (!("".equals(info))) {
                        info += ", ";
                    }

                    info += time;
                }
            
                if(bitrate != null) {
                    String quality = FileInfoUtils.getQuality(propertiableFile);
                    
                    if (!("".equals(info))) {
                        info += ", ";
                    }
                    
                    if(quality != null) {
                        info += bitrate + " kbps (" + quality + ")";
                    }
                    else {
                        info += bitrate + " kbps";
                    }
                }
                break;

        }
        
        if (!("".equals(info))) {
            component.add(createLabelField(info), "gapbefore 4, growx, wrap");
        }
    }
   
    private JTextField createLabelField(String text) {
        JTextField field = new JTextField(text);
        TextFieldClipboardControl.install(field);
        field.setCaretPosition(0);
        field.setEditable(false);
        field.setOpaque(false);
        field.setFont(smallFont);
        field.setForeground(foreground);
        field.setBorder(BorderFactory.createEmptyBorder(0,1,0,1));
        return field;
    }
    
    /**
     * Returns the appropriate Icon for the file type.
     */
    private Icon getIcon(PropertiableFile propertiableFile) {
        switch(type){
        case LOCAL_FILE:
            return categoryIconManager.getIcon(propertiableFile);
        case DOWNLOADING_FILE:
        case REMOTE_FILE:
        default:
            return categoryIconManager.getIcon(propertiableFile);
        }
    }
    
    /**
     * Creates a textfield used exclusively while renaming the file.
     */
    private void createRenameTextField() {
        renameTextField = new JTextField();
        renameTextField.setFont(headerFont);
        renameTextField.setForeground(foreground);
        renameTextField.setVisible(false);

        TextFieldClipboardControl.install(renameTextField);
        
        // close when enter key is typed
        renameTextField.addKeyListener(new KeyAdapter(){
            @Override
            public void keyTyped(KeyEvent e) {
                if(e.getKeyChar() == KeyEvent.VK_ENTER) {
                    enableRenameMode(false);
                }
            }
        });
        
        // close when mouse is clicked somewhere else
        component.addMouseListener(new MouseAdapter(){
            @Override
            public void mouseClicked(MouseEvent e) {
                enableRenameMode(false);
            }            
        });

        component.add(renameTextField, "hidemode 3, pos " + (nameLabel.getLocation().x-3) + " " + (nameLabel.getLocation().y-3));
        component.revalidate();
    }
       
    /**
     * Updates components and saves states depending on whether 
     * rename mode is activated or deactivated.
     */
    private void enableRenameMode(boolean enable) {
        if(enable) {
            setRenameText();
            setRenameEnabled(true);        
            renameTextField.requestFocusInWindow();
            renameTextField.selectAll();
        } else {
            saveFileName();
            setRenameEnabled(false);
            component.repaint();
        }
    }
    
    /**
     * Updates the text of the editable renameTextField. Once the text is updated,
     * the editable textfield is made visible, allowing the user to change the 
     * filename.
     */
    private void setRenameText() {
        if(propertiableFile instanceof LocalFileItem) {
            int maxFileLength = getMaxFileSize(((LocalFileItem)propertiableFile).getFile());
            renameTextField.setDocument(new FileNameDocument(maxFileLength));
        }
        String fileName = FileUtils.getFilenameNoExtension(nameLabel.getText());
        renameTextField.setText(fileName);
        ResizeUtils.forceWidth(renameTextField, nameLabel.getWidth());
    }
    
    
    /**
     * Updates components that change when in edit mode.
     * 
     * @isRenaming is true when the mode will be switched to
     * edit mode, false when mode is switched out of edit mode.
     */
    private void setRenameEnabled(boolean isRenaming) {
        renameTextField.setVisible(isRenaming);
        nameLabel.setVisible(!isRenaming);
        renameButton.setEnabled(!isRenaming);
    }
    
    /**
     * Attempts to save the filename changes to disk.
     */
    private void saveFileName() {
        String newFileName = renameTextField.getText().trim();
        LocalFileItem oldFileItem = (LocalFileItem) propertiableFile;
        // check the new file name is valid
        if(!isValidFileName(newFileName)) {
            renameTextField.setText(oldFileItem.getName());
            return;
        }
      
        // check if the name hasn't changed, just return
        if(newFileName.equals(oldFileItem.getName())) {
            return;
        }

        File oldFile = oldFileItem.getFile();
      
        newFileName = newFileName + "." + FileUtils.getFileExtension(oldFile);
        File newFile = new File(oldFile.getParentFile(), newFileName);
      
        // try performing the file rename, if something goes wrong, revert textfield.
        if(FileUtils.forceRename(oldFile, newFile)) {
            updateFileNameInLibrary(oldFile, newFile);
        } else {
            newFile.delete();
            renameTextField.setText(oldFileItem.getName());
        }
    }
    
    /**
     * Notifies the library that the fileName has been changed.
     */
    private void updateFileNameInLibrary(File oldFile, File newFile) {
        libraryManager.getLibraryManagedList().fileRenamed(oldFile, newFile);
    }
    
    /**
     * Returns true if the text is a valid file name.
     */
    private boolean isValidFileName(String fileName) {
        return fileName != null && fileName.length() > 0 && CommonUtils.santizeString(fileName).equals(fileName);
    }
    
    /**
     * Handles renaming of the file name. This is handled using
     * a popup menu to get the support of loosing focus and
     * properlly disappearing.
     */
    private class RenameAction extends AbstractAction {
        public RenameAction() {
            super(I18n.tr("Rename File"));
        }
        
        @Override
        public void actionPerformed(ActionEvent e) {
            if(renameTextField == null)
                createRenameTextField();
            enableRenameMode(true);
        }
    }
    
    /**
     * Returns the maximum length the filename can be.
     */
    private int getMaxFileSize(File file) {
        String fileName = FileUtils.getFilenameNoExtension(file.getName());
        return OSUtils.getMaxPathLength() - file.getAbsolutePath().length() + fileName.length();
    }
    
    /**
     * Prevents a TextField from having a String beyond a certain length. Also
     * prevents non-valid filename characters from being entered.
     */
    private class FileNameDocument extends PlainDocument {
        
        private int maxLength = Integer.MAX_VALUE;
        
        public FileNameDocument(int length) {
            this.maxLength = length;
        }
        
        @Override
        public void insertString(int offset, String str, AttributeSet attr) throws BadLocationException {
            if(str == null)
                return;
            String santize = CommonUtils.santizeString(str);
            // only add the string if it contains valid characters and is below the max length
            if(santize.equals(str) && getLength() + str.length() <= maxLength) {
                super.insertString(offset, str, attr);
            } else {
                Toolkit.getDefaultToolkit().beep();
            }
        }
    }
}
