/*
 *  PHEX - The pure-java Gnutella-servent.
 *  Copyright (C) 2001 - 2004 Gregor Koukkoullis ( phex <at> kouk <dot> de )
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 *  --- CVS Information ---
 *  $Id: SearchResultsPanel.java,v 1.13 2004/06/19 18:24:09 gregork Exp $
 */
package phex.gui.tabs.search;

import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.awt.event.*;
import java.io.*;
import java.util.*;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import javax.swing.tree.TreePath;
import javax.xml.bind.JAXBException;

import com.jgoodies.forms.builder.PanelBuilder;
import com.jgoodies.forms.layout.*;
import com.jgoodies.forms.layout.CellConstraints;

import phex.chat.ChatManager;
import phex.common.ServiceManager;
import phex.download.RemoteFile;
import phex.download.swarming.*;
import phex.gui.actions.*;
import phex.gui.common.*;
import phex.gui.common.table.*;
import phex.gui.dialogs.DownloadConfigDialog;
import phex.gui.models.*;
import phex.gui.renderer.*;
import phex.host.HostAddress;
import phex.utils.*;
import phex.xml.*;

/**
 * The search result panel containing a JTreeTable to group search results together and all its
 * actions.
 */
public class SearchResultsPanel extends JPanel
{
    private static final String SEARCH_TREE_TABLE_IDENTIFIER = "SearchTreeTable";
    private static final RemoteFile[] EMPTY_REMOTE_FILE_ARRAY = new RemoteFile[0];
    
    private SearchTab searchTab;
    private JPopupMenu searchResultPopup;
    
    private JTreeTable searchTreeTable;
    private SearchTreeTableModel searchTreeTableModel;
    private FWTableColumnModel searchTreeColumnModel;
    
    private JScrollPane searchTreeTableScrollPane;
    
    public SearchResultsPanel( SearchTab tab )
    {
        super( );
        searchTab = tab;
    }
    
    public void initializeComponent( XJBGUISettings guiSettings )
    {
        CellConstraints cc = new CellConstraints();
        FormLayout layout = new FormLayout(
            "fill:d:grow", // columns
            "fill:d:grow, 1dlu, p"); //rows
        PanelBuilder panelBuilder = new PanelBuilder( this, layout );

        MouseHandler mouseHandler = new MouseHandler();
        searchTreeTableModel = new SearchTreeTableModel();
        searchTreeTable = new JTreeTable( searchTreeTableModel );
        buildSearchTreeTableColumnModel( guiSettings );
        searchTreeTable.setColumnModel( searchTreeColumnModel );
        searchTreeTable.activateHeaderPopupMenu();
        searchTreeTable.activateColumnResizeToFit();
        JTableHeader header = searchTreeTable.getTableHeader();
        header.setDefaultRenderer( new SortedTableHeaderRenderer(searchTreeTable) );
        header.addMouseListener( new TableHeaderMouseHandler() );
        searchTreeTable.addMouseListener( mouseHandler );
        searchTreeTable.setAutoResizeMode( JTable.AUTO_RESIZE_OFF );
        searchTreeTable.getSelectionModel().addListSelectionListener(
            new SearchTreeTableSelectionListener() );
        searchTreeTable.getTree().setCellRenderer( new SearchTreeCellRenderer() );
        
        SearchResultsRowRenderer resultRowRenderer = new SearchResultsRowRenderer(
            searchTreeTableModel );
        Enumeration enum = searchTreeColumnModel.getColumns();
        while ( enum.hasMoreElements() )
        {
            TableColumn column = (TableColumn)enum.nextElement();
            column.setCellRenderer( resultRowRenderer );
        }
        searchTreeTableScrollPane = FWTable.createFWTableScrollPane( searchTreeTable );
        searchTreeTableScrollPane.addMouseListener( mouseHandler );
        panelBuilder.add( searchTreeTableScrollPane, cc.xy( 1, 1 ) );
        
        FWToolBar resultToolbar = new FWToolBar( JToolBar.HORIZONTAL );
        resultToolbar.setBorderPainted( false );
        resultToolbar.setFloatable( false );
        panelBuilder.add( resultToolbar, cc.xy( 1, 3 ) );

        // init popup menu
        searchResultPopup = new JPopupMenu();

        // add actions to toolbar
        FWAction action = new SWQuickDownloadAction();
        searchTab.addTabAction( QUICK_DOWNLOAD_ACTION_KEY, action );
        resultToolbar.addAction( action );
        searchResultPopup.add( action );

        action = new SWConfigDownloadAction();
        searchTab.addTabAction( CONFIG_DOWNLOAD_ACTION_KEY, action );
        resultToolbar.addAction( action );
        searchResultPopup.add( action );

        //action = new AddAsCandidateAction();
        //addTabAction( ADD_AS_CANDIDATE_ACTION_KEY, action );
        //resultToolbar.addAction( action );
        //searchResultPopup.add( action );

        action = new ViewBitziTicketAction();
        searchTab.addTabAction( VIEW_BITZI_TICKET_ACTION_KEY, action );
        resultToolbar.addAction( action );
        searchResultPopup.add( action );
        searchResultPopup.addSeparator();
        
        action = new AddToFavoritesAction();
        searchTab.addTabAction( ADD_TO_FAVORITES_ACTION_KEY, action );
        searchResultPopup.add( action );

        action = new BrowseHostAction();
        searchTab.addTabAction( BROWSE_HOST_ACTION_KEY, action );
        resultToolbar.addAction( action );
        searchResultPopup.add( action );

        action = new ChatToHostAction();
        searchTab.addTabAction( CHAT_TO_HOST_ACTION_KEY, action );
        resultToolbar.addAction( action );
        searchResultPopup.add( action );

        action = new BanHostAction();
        searchTab.addTabAction( FILTER_HOST_ACTION_KEY, action );
        resultToolbar.addAction( action );
        searchResultPopup.add( action );
        searchResultPopup.addSeparator();
        
        searchResultPopup.add( searchTab.getTabAction(
            SearchTab.CREATE_NEW_SEARCH_ACTION ) );
        searchResultPopup.add( searchTab.getTabAction(
            SearchTab.CLEAR_SEARCH_RESULTS_ACTION ) );
        searchResultPopup.add( searchTab.getTabAction(
            SearchTab.REMOVE_FILTER_ACTION ) );
        searchResultPopup.add( searchTab.getTabAction(
            SearchTab.CLOSE_SEARCH_ACTION ) );
    }
    
    public void setDisplayedSearch( SearchResultsDataModel searchResultsDataModel )
    {
        searchTreeTableModel.setDisplayedSearch( searchResultsDataModel );
    }
        
    private void buildSearchTreeTableColumnModel( XJBGUISettings guiSettings )
    {
        XJBGUITable xjbTable = GUIUtils.getXJBGUITableByIdentifier( guiSettings,
            SEARCH_TREE_TABLE_IDENTIFIER );
        int[] columnIds = SearchTreeTableModel.getColumnIdArray();
        XJBGUITableColumnList columnList = null;
        if ( xjbTable != null )
        {
            columnList = xjbTable.getTableColumnList();
        }
        searchTreeColumnModel = new FWTableColumnModel(
            (FWTableModel)searchTreeTable.getModel(),
            columnIds, columnList );
    }
    
    public RemoteFile[] getSelectedRemoteFiles()
    {
        ISearchDataModel searchDataModel = searchTreeTableModel.getDisplayedResultsData();
        if (searchDataModel == null)
        {
            return EMPTY_REMOTE_FILE_ARRAY;
        }
        
        TreePath[] selectionPaths = searchTreeTable.getTree().getSelectionPaths();
        if ( selectionPaths.length == 0 )
        {
            return EMPTY_REMOTE_FILE_ARRAY;
        }
        HashSet remoteFileSet = new HashSet();
        for ( int i = 0; i < selectionPaths.length; i++ )
        {
            SearchResultElement element = (SearchResultElement)selectionPaths[i].getPathComponent( 1 );
            RemoteFile[] files = element.getRemoteFiles();
            remoteFileSet.addAll( Arrays.asList( files ) );
        }
        RemoteFile[] result = new RemoteFile[ remoteFileSet.size() ];
        remoteFileSet.toArray( result );
        return result;
    }

    /**
     * Returns the selected RemoteFile. In case useRepresent is true and a
     * SearchResultElement containing multiple RemoteFiles is selected a
     * representiv RemoteFile for this collection is returned, if useRepresent is
     * false null is returned in this case.
     * Null is also returned when there are no displayed results datas,
     * or the tree selection path is null.
     * @return the selected RemoteFile or null.
     */
    private RemoteFile getSelectedRemoteFile( boolean useRepresent )
    {
        ISearchDataModel searchDataModel = searchTreeTableModel.getDisplayedResultsData();
        if (searchDataModel == null)
        {
            return null;
        }
        
        TreePath selectionPath = searchTreeTable.getTree().getSelectionPath();
        if ( selectionPath == null )
        {
            return null;
        }
        RemoteFile remoteFile;
        if ( selectionPath.getPathCount() == 3 )
        {
            remoteFile = (RemoteFile)selectionPath.getPathComponent( 2 );
        }
        else
        {
            SearchResultElement element = (SearchResultElement)selectionPath.getPathComponent( 1 );
            if ( !useRepresent && element.getRemoteFileListCount() != 0 )
            {
                return null;
            }
            remoteFile = element.getSingleRemoteFile();
        }
        
        return remoteFile;
    }
    
    
    /**
     * This is overloaded to update the combo box size on
     * every UI update. Like font size change!
     */
    public void updateUI()
    {
        super.updateUI();
        if ( searchTreeTableScrollPane != null )
        {
            FWTable.updateFWTableScrollPane( searchTreeTableScrollPane );
        }
    }
    
    public void appendXJBGUISettings( XJBGUISettings xjbSettings )
        throws JAXBException
    {
        XJBGUITableColumnList xjbList = searchTreeColumnModel.createXJBGUITableColumnList();
        XJBGUITable xjbTable = ObjectFactory.createXJBGUITable();
        xjbTable.setTableColumnList( xjbList );
        xjbTable.setTableIdentifier( SEARCH_TREE_TABLE_IDENTIFIER );
        xjbSettings.getTableList().getTableList().add( xjbTable );
    }
    
    /////////////////////// Start Table Actions///////////////////////////////////

    private static final String QUICK_DOWNLOAD_ACTION_KEY = "SWQuickDownloadAction";
    private static final String CONFIG_DOWNLOAD_ACTION_KEY = "SWConfigDownloadAction";
    //private static final String ADD_AS_CANDIDATE_ACTION_KEY = "AddAsCandidateAction";
    private static final String VIEW_BITZI_TICKET_ACTION_KEY = "ViewBitziTicketAction";
    private static final String CHAT_TO_HOST_ACTION_KEY = "ChatToHostAction";
    private static final String BROWSE_HOST_ACTION_KEY = "BrowseHostAction";
    private static final String FILTER_HOST_ACTION_KEY = "FilterHostAction";
    private static final String ADD_TO_FAVORITES_ACTION_KEY = "AddToFavoritesAction";

    private class SWQuickDownloadAction extends FWAction
    {
        public SWQuickDownloadAction()
        {
            super( Localizer.getString( "QuickDownload" ),
                GUIRegistry.getInstance().getIconFactory().getIcon("Download"),
                Localizer.getString( "TTTQuickDownload" ) );
            refreshActionState();
        }

        public void actionPerformed( ActionEvent e )
        {
            SwarmingManager swarmingMgr = SwarmingManager.getInstance();
            RemoteFile[] rfiles = getSelectedRemoteFiles();

            for ( int i = 0; i < rfiles.length; i++ )
            {
                rfiles[i].setInDownloadQueue( true );

                SWDownloadFile downloadFile = swarmingMgr.getDownloadFile(
                    rfiles[i].getFileSize(), rfiles[i].getURN() );

                if ( downloadFile != null )
                {
                    downloadFile.addDownloadCandidate( rfiles[i] );
                }
                else
                {
                    RemoteFile dfile = new RemoteFile( rfiles[i] );
                    String searchTerm = StrUtil.createNaturalSearchTerm( dfile.getFilename() );
                    swarmingMgr.addFileToDownload( dfile,
                        ServiceManager.sCfg.mDownloadDir + File.separator
                        + dfile.getFilename(), searchTerm );
                }
            }
        }

        public void refreshActionState()
        {
            if ( searchTreeTable.getSelectedRow() < 0 )
            {
                setEnabled( false );
            }
            else
            {
                setEnabled( true );
            }
        }
    }

    private class SWConfigDownloadAction extends FWAction
    {
        public SWConfigDownloadAction()
        {
            super( Localizer.getString( "Download" ),
                GUIRegistry.getInstance().getIconFactory().getIcon("ConfigDownload"),
                Localizer.getString( "TTTDownload" ) );
            refreshActionState();
        }

        public void actionPerformed( ActionEvent e )
        {
            SwarmingManager swarmingMgr = SwarmingManager.getInstance();
            RemoteFile[] rfiles = getSelectedRemoteFiles();

            for ( int i = 0; i < rfiles.length; i++ )
            {
                SWDownloadFile downloadFile = swarmingMgr.getDownloadFile(
                    rfiles[i].getFileSize(), rfiles[i].getURN() );

                if ( downloadFile != null )
                {
                    downloadFile.addDownloadCandidate( rfiles[i] );
                    rfiles[i].setInDownloadQueue( true );
                }
                else
                {
                    RemoteFile dfile = new RemoteFile( rfiles[i] );
                    
                    DownloadConfigDialog dialog = new DownloadConfigDialog( dfile );
                    dialog.show();
                    // copy status to item in search list.
                    if ( dfile.isInDownloadQueue() )
                    {
                        rfiles[i].setInDownloadQueue( true );
                    }                    
                }
            }
        }

        public void refreshActionState()
        {
            if ( searchTreeTable.getSelectedRow() < 0 )
            {
                setEnabled( false );
            }
            else
            {
                setEnabled( true );
            }
        }
    }

    private class ViewBitziTicketAction extends FWAction
    {
        public ViewBitziTicketAction()
        {
            super( Localizer.getString( "ViewBitziTicket" ),
                GUIRegistry.getInstance().getIconFactory().getIcon("Bitzi"),
                Localizer.getString( "TTTViewBitziTicket" ) );
            refreshActionState();
        }

        public void actionPerformed( ActionEvent e )
        {
            // since getSelectedRemoteFile() returns null on the top level
            // SearchResultElement when it has multiple childs, we use
            // getSelectedRemoteFiles() as a 
            RemoteFile rfile = getSelectedRemoteFile( true );
            if ( rfile == null )
            {
                return;
            }
            String url = URLUtil.buildBitziLookupURL( rfile.getURN() );
            try
            {
                BrowserLauncher.openURL( url );
            }
            catch ( IOException exp )
            {
                Logger.logWarning( exp );

                Object[] dialogOptions = new Object[]
                {
                    Localizer.getString( "Yes" ),
                    Localizer.getString( "No" )
                };

                int choice = JOptionPane.showOptionDialog( searchTab,
                    Localizer.getString( "FailedToLaunchBrowserURLInClipboard" ),
                    Localizer.getString( "FailedToLaunchBrowser" ),
                    JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null,
                    dialogOptions, Localizer.getString( "Yes" ) );
                if ( choice == 0 )
                {
                    Toolkit.getDefaultToolkit().getSystemClipboard().setContents(
                        new StringSelection( url ), null);
                }
            }
        }

        public void refreshActionState()
        {
            if ( searchTreeTable.getSelectedRowCount() == 1 )
            {
                RemoteFile rfile = getSelectedRemoteFile( true );
                if ( rfile != null && rfile.getURN() != null )
                {
                    setEnabled( true );
                    return;
                }
            }
            else
            {
                setEnabled( false );
            }
        }
    }

    private class ChatToHostAction extends FWAction
    {
        public ChatToHostAction()
        {
            super( Localizer.getString( "ChatToHost" ),
                GUIRegistry.getInstance().getIconFactory().getIcon("Chat"),
                Localizer.getString( "TTTChatToHost" ) );
            refreshActionState();
        }

        public void actionPerformed( ActionEvent e )
        {
            RemoteFile rfile = getSelectedRemoteFile( false );
            if ( rfile == null )
            {
                return;
            }

            if ( !rfile.getQueryHitHost().isChatSupported() )
            {
                return;
            }

            ChatManager.getInstance().openChat( rfile.getHostAddress() );
        }

        public void refreshActionState()
        {
            if ( searchTreeTable.getSelectedRowCount() == 1 )
            {
                RemoteFile rfile = getSelectedRemoteFile( false );
                if ( rfile != null
                    && rfile.getQueryHitHost().isChatSupported() )
                {
                    setEnabled( true );
                    return;
                }
            }
            setEnabled( false );
        }
    }

    private class BrowseHostAction extends FWAction
    {
        public BrowseHostAction()
        {
            super( Localizer.getString( "BrowseHost" ),
                GUIRegistry.getInstance().getIconFactory().getIcon("BrowseHost"),
                Localizer.getString( "TTTBrowseHost" ) );
            refreshActionState();
        }

        public void actionPerformed( ActionEvent e )
        {
            RemoteFile rfile = getSelectedRemoteFile( false );
            if ( rfile == null )
            {
                return;
            }
            if ( !rfile.getQueryHitHost().isBrowseHostSupported() )
            {
                return;
            }
            GUIActionPerformer.browseHost( rfile.getHostAddress() );
        }

        public void refreshActionState()
        {
            if ( searchTreeTable.getSelectedRowCount() == 1 )
            {
                RemoteFile rfile = getSelectedRemoteFile( false );
                if ( rfile != null
                    && rfile.getQueryHitHost().isBrowseHostSupported() )
                {
                    setEnabled( true );
                    return;
                }
            }
            setEnabled( false );
        }
    }

    private class BanHostAction extends FWAction
    {
        public BanHostAction()
        {
            super( Localizer.getString( "BanHost" ),
                GUIRegistry.getInstance().getIconFactory().getIcon("Ban"),
                Localizer.getString( "TTTBanHost" ) );
            refreshActionState();
        }

        public void actionPerformed( ActionEvent e )
        {
            RemoteFile[] files = getSelectedRemoteFiles();
            
            HostAddress[] addresses = new HostAddress[files.length];
            for (int i = 0; i < files.length; i++)
            {
                addresses[ i ] = files[i].getHostAddress();
            }
            GUIActionPerformer.banHosts( addresses );
        }

        public void refreshActionState()
        {
            if ( searchTreeTable.getSelectedRow() < 0 )
            {
                setEnabled( false );
            }
            else
            {
                setEnabled( true );
            }
        }
    }
    
    private class AddToFavoritesAction extends FWAction
    {
        public AddToFavoritesAction()
        {
            super( Localizer.getString( "AddToFavorites" ),
                GUIRegistry.getInstance().getIconFactory().getIcon( "FavoriteHost" ),
                Localizer.getString( "TTTAddToFavorites" ) );
            refreshActionState();
        }

        public void actionPerformed( ActionEvent e )
        {
            RemoteFile[] files = getSelectedRemoteFiles();
            
            HostAddress[] addresses = new HostAddress[files.length];
            for (int i = 0; i < files.length; i++)
            {
                addresses[ i ] = files[i].getHostAddress();
            }
            GUIActionPerformer.addHostsToFavorites( addresses );
        }

        public void refreshActionState()
        {
            if ( searchTreeTable.getSelectedRow() < 0 )
            {
                setEnabled( false );
            }
            else
            {
                setEnabled( true );
            }
        }
    }

    /////////////////////// End Table Actions///////////////////////////////////
        
    private class MouseHandler extends MouseAdapter implements MouseListener
    {
        public void mouseClicked(MouseEvent e)
        {
            if (e.getClickCount() == 2)
            {
                if ( e.getSource() == searchTreeTable )
                {
                    searchTab.getTabAction( QUICK_DOWNLOAD_ACTION_KEY ).actionPerformed( null );
                }
            }
        }

        public void mouseReleased(MouseEvent e)
        {
            if (e.isPopupTrigger())
            {
                popupMenu((Component)e.getSource(), e.getX(), e.getY());
            }
        }

        public void mousePressed(MouseEvent e)
        {
            if (e.isPopupTrigger())
            {
                popupMenu((Component)e.getSource(), e.getX(), e.getY());
            }
        }

        private void popupMenu(Component source, int x, int y)
        {
            if ( source == searchTreeTable || source == searchTreeTableScrollPane )
            {
                searchResultPopup.show(source, x, y);
            }
        }
    }
    
    private class TableHeaderMouseHandler extends MouseAdapter implements MouseListener
    {
        public void mouseClicked(MouseEvent e)
        {
            FWTableColumn column = searchTreeTable.getResizingColumn( e.getPoint() );
            int clickCount = e.getClickCount();

            // only handle sorting on one click count and when not clicked
            // between two columns.
            if ( column == null && clickCount == 1 )
            {
                handleColumnSorting( e );
            }
        }
            
        /**
         * Handles sorting on a column on single click.
         */
        private void handleColumnSorting( MouseEvent e )
        {
            int viewIdx = searchTreeTable.getColumnModel().getColumnIndexAtX( e.getX() );
            int modelIdx = searchTreeTable.convertColumnIndexToModel( viewIdx );
            if( modelIdx == -1)
            {
                return;
            }

            // reverse order on each click
            FWTableColumn column = (FWTableColumn)searchTreeTable.getColumnModel().getColumn( viewIdx );
            boolean isAscending = column.reverseSortingOrder();

            searchTreeTableModel.sortByColumn( modelIdx, isAscending );
        }
    }
    
    /**
     * We like to select all expanded rows of the parent row in the table. 
     */
    private class SearchTreeTableSelectionListener implements ListSelectionListener
    {
        public void valueChanged( ListSelectionEvent e )
        {
            if ( !e.getValueIsAdjusting() )
            {
                 searchTab.refreshTabActions();
            }
            /*
             // causes all child rows to be selected...
            ListSelectionModel model = (ListSelectionModel)e.getSource();
            int startIdx = e.getFirstIndex();
            int endIdx = e.getLastIndex();
            for (int i = startIdx; i <= endIdx; i++)
            {
                if ( model.isSelectedIndex(i) )
                {
                    Object node = searchTreeTable.getNodeOfRow(i);
                    if ( node == null )
                    {
                        continue;
                    }
                    if ( !searchTreeTableModel.isLeaf( node ) &&
                        searchTreeTable.getTree().isExpanded(i) )
                    {
                        int start = i + 1;
                        int stop = i + searchTreeTableModel.getChildCount( node );
                        model.addSelectionInterval(start, stop);
                    }
                }
            }
            */
        }
    }
}