/**
 * Created on Jul 3, 2005
 * $id:  $
 */
package org.jvnet.lookout.ui.core.controller;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Preferences.IPropertyChangeListener;
import org.eclipse.core.runtime.Preferences.PropertyChangeEvent;
import org.eclipse.ui.IEditorDescriptor;
import org.eclipse.ui.IEditorRegistry;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.browser.IWebBrowser;
import org.eclipse.ui.browser.IWorkbenchBrowserSupport;
import org.jvnet.lookout.core.adapter.SearchAdapter;
import org.jvnet.lookout.core.model.SearchQuery;
import org.jvnet.lookout.core.model.SearchResultLocation;
import org.jvnet.lookout.core.model.SearchResultProperty;
import org.jvnet.lookout.core.model.SearchResultType;
import org.jvnet.lookout.core.model.SearchQuery.Parameter;
import org.jvnet.lookout.core.preference.Preferences;
import org.jvnet.lookout.ui.core.Plugin;
import org.jvnet.lookout.ui.core.controller.job.SearchJob;
import org.jvnet.lookout.ui.core.controller.job.SearchJobListener;
import org.jvnet.lookout.ui.core.search.SearchResultsView;
import org.jvnet.lookout.ui.core.tree.SearchResultItem;
import org.jvnet.lookout.ui.core.tree.SearchResultsContentProvider;
import org.jvnet.lookout.ui.core.tree.TreeObject;
import org.jvnet.lookout.ui.core.tree.adapter.ModelToUIAdapter;
import org.jvnet.lookout.ui.core.util.Utilities;

public class SearchController {
        
    private static List<SearchAdapter> searchAdapters;
    private static List<SearchAdapter> activeSearchAdapters;

    private static final String BROWSER_ID_SUFFIX = "_BROWSER_ID";
    private static int CURRENT_BROWSER_ID = 1;
    private static final String SEPARATOR = ",";
    
    private static Map <SearchAdapter, ModelToUIAdapter> searchToUIAdapters;
    private static Map <FileType, String> fileTypeToEditorIds;
    private static PropertyChangeListener propertyChangeListener;
    private static org.jvnet.lookout.core.Plugin searchCorePlugin;
    private static int currentResultBatch = 1;
    private static boolean hasFoundResults = false;
        
    static {
        int defaultSize = 2;
        
        searchAdapters = new ArrayList<SearchAdapter>(defaultSize);
        searchToUIAdapters = new HashMap <SearchAdapter, ModelToUIAdapter>(defaultSize);
        activeSearchAdapters = new ArrayList<SearchAdapter>(defaultSize);      
        
        findEditors();
        
        propertyChangeListener = new PropertyChangeListener();
        searchCorePlugin = org.jvnet.lookout.core.Plugin.getDefault();
        
        //Listen to search adapter enablement changes...
        searchCorePlugin.getPluginPreferences().addPropertyChangeListener(propertyChangeListener);        
    }
    
    private static class PropertyChangeListener implements IPropertyChangeListener {
    	
    	public void propertyChange(PropertyChangeEvent event) {
    		String property = event.getProperty();
    		
    		if(Preferences.SearchableSearchAdapters.equals(property)) {
    			
    			List<String> adapterIds 
    			= org.jvnet.lookout.core.util.Utilities.createList((String)event.getNewValue(), ",");
    			
    			updateActiveSearchAdapterList(adapterIds);
    		}
    	}
    }
    
    private static void updateActiveSearchAdapterList(List<String> adapterIds) {
    	
    	activeSearchAdapters.clear();
    	
    	for (Iterator iter = adapterIds.iterator(); iter.hasNext();) {
			String adapterId = (String) iter.next();
			activeSearchAdapters.add(searchCorePlugin.getSearchAdapter(adapterId));			
		}    	
    }
    
    public static enum Action {
    	NEXT_RESULTS, 
    	PREVIOUS_RESULTS, 
        SEARCH_EXECUTION, 
        OPEN_SEARCH_RESULT, 
        OPEN_PREFERENCES_PAGE, 
        OPEN_DOCUMENTATION_PAGE,
        CLEAR_SEARCH_RESULTS,
        CLEAR_KEYWORDS
    };
    
    private static enum FileType {
    	HTML,PDF,DOC,XSL,PPT,PS,RTF,TXT,JAVA,CLASS,XML,XSD,
    	CPP,C,PROPERTIES, INI, PNG, GIF, JPEG, JPG, BMP, WMV,
    	MPEG, MOV, QT, MPGA, MP1, MP2, MP3, M3U, AVI, WMA, RAM,
    	MIDI, AU, AIFF, CDA, MID, RMI
    	};
    
    private static void findEditors() {
        
        if(PlatformUI.isWorkbenchRunning()) {
            IEditorRegistry registry = PlatformUI.getWorkbench().getEditorRegistry();
            
            fileTypeToEditorIds = new HashMap <FileType, String> (FileType.values().length);
            
            for (FileType fileType : FileType.values()) {
                
                String dummyFile = "dummy."+ fileType;
                
                //1st: Try the default from the resgistry
                IEditorDescriptor editorDescriptor = registry.getDefaultEditor(dummyFile); 
                
                //2nd: Try the default external 
                if(null == editorDescriptor) {
                    editorDescriptor = registry.findEditor(IEditorRegistry.SYSTEM_EXTERNAL_EDITOR_ID);                  
                }
                
                //3rd: Try the default inplace
                if(null == editorDescriptor) {
                    editorDescriptor = registry.findEditor(IEditorRegistry.SYSTEM_INPLACE_EDITOR_ID);
                }
                
                if(null == editorDescriptor) {
                    Utilities.showWarningMessage(Plugin.getResourceString("no-editor-found") + fileType);
                } else {
                    fileTypeToEditorIds.put(fileType, editorDescriptor.getId());    
                }
            }
        }
    }
    
    public static void triggerAction(Action action, SearchResultsView searchResultsView) {
        triggerAction(action, searchResultsView, null);
    }
    
    /**
     * Triggers an action to be executed by this controller.
     * This method takes an additional parameter that can be used to provide 
     * additional information to the controller action implementation.
     *  
     * @param searchResultsView
     * @param action
     * @param arguments
     */
    public static void triggerAction(Action action, SearchResultsView searchResultsView, Object arguments) {
        
        switch (action) {
        
        case PREVIOUS_RESULTS:
        	
        	//Don't want to go zero...
        	if(currentResultBatch > 1) {
        		currentResultBatch--;
        	}
            triggerSearchExecution(searchResultsView);    
            break;
        
        case NEXT_RESULTS:
        	//Increase
        	currentResultBatch++;
        	triggerSearchExecution(searchResultsView);    
            break;
        
        case SEARCH_EXECUTION:
        	//Reset
        	currentResultBatch = 1;
            triggerSearchExecution(searchResultsView);    
            break;
            
        case OPEN_SEARCH_RESULT:
            
            if(arguments instanceof SearchResultItem) {
                triggerOpenSearchResult((SearchResultItem)arguments);
            }
            break;
            
        case OPEN_DOCUMENTATION_PAGE:
            triggerOpenDocumentationPage(searchResultsView);
            break;
            
        case OPEN_PREFERENCES_PAGE:
            triggerOpenPreferencesPage(searchResultsView);
            break;
            
        case CLEAR_SEARCH_RESULTS:
            triggerClearSearchResults(searchResultsView);
            break;    
            
        case CLEAR_KEYWORDS:
            triggerClearKeywords(searchResultsView);
            break;  
            
        default:
            Utilities.showWarningMessage(Plugin.getResourceString("unknown-controller-action" + action));
            break;
        }        
    }
    
    private static void triggerSearchExecution(SearchResultsView searchResultsView) {
    	    	
    	if(activeSearchAdapters.isEmpty()) {
    		Utilities.showInformationMessage("There are no Search Engines activated. See the preferences section to activate the available Search Engines.");
    		return;
    	}
    	
    	//reset this flag...
    	hasFoundResults  = false;
    	        
        //Create SearchQuery
        SearchQuery query = createSearchQuery(searchResultsView);
        
        query.addParameter(Parameter.BATCH, "" + currentResultBatch);
        
        //Perform search and adapt results to UI ..
        
        SearchJobListener searchJobListener = new SearchJobListener(searchResultsView, activeSearchAdapters.size());
                
        for (SearchAdapter searchAdapter : activeSearchAdapters) {
            
        	SearchJob searchJob = new SearchJob(
        			searchCorePlugin.getSearchAdapterName(searchAdapter), 
        			searchAdapter, 
        			searchToUIAdapters.get(searchAdapter),
        			query);
        	
        	searchJob.addJobChangeListener(searchJobListener);        	
        	searchJob.schedule();        	
        }
    }
	    
    public static boolean getHasFoundResults() {
		return hasFoundResults;
	}
    
    public static void setHasFoundResults(boolean foundResults) {
		hasFoundResults = foundResults;
	}
 
    private static void triggerClearSearchResults(SearchResultsView searchResultsView) {     
        
        SearchResultsContentProvider contentProvider = 
            (SearchResultsContentProvider)searchResultsView.getSearchResultsTreeViewer().getContentProvider();
        
        contentProvider.setContent(new ArrayList<TreeObject>());
    }

    private static void triggerClearKeywords(SearchResultsView searchResultsView) {             
        searchResultsView.getKeywords().setText("");
    }

    private static void triggerOpenSearchResult(SearchResultItem item) {
                
        try {
            
            String urlToBeOpened = (String)item.getPropertyValue(SearchResultProperty.URL);
            
            SearchResultLocation itemLocation = (SearchResultLocation)item.getPropertyValue(SearchResultProperty.LOCATION);
            
            SearchResultType itemType = (SearchResultType)item.getPropertyValue(SearchResultProperty.TYPE);
            
            if(canBeOpened(item)) {
            	//If this is a remote result open in browser...
            	if(SearchResultLocation.REMOTE.equals(itemLocation) ||
            			SearchResultType.HTML.equals(itemType) || 
            			SearchResultType.EMAIL.equals(itemType)) {                
            		
            		String itemName = (String)item.getPropertyValue(SearchResultProperty.NAME);
                    
            		//Open with a browser...
            		openSearchResultInBrowser(itemName, urlToBeOpened);
            	} else {
            		
            		//Open in editor...
            		openSearchResultInEditor(urlToBeOpened, item);
            	}
            } else {
            	Utilities.showInformationMessage(Plugin.getResourceString("cannot-open-file-type"));
            }
        } catch (Exception e) {
        	Utilities.showErrorMessage(Plugin.getResourceString("cannot-open-search-result") + e.getLocalizedMessage());
        }        
    }
    
    private static boolean canBeOpened(SearchResultItem item) {
    	boolean canOpen = false;
    	
    	SearchResultType itemType = (SearchResultType)item.getPropertyValue(SearchResultProperty.TYPE);
        
      	if(
      			SearchResultType.C.equals(itemType) ||
      			SearchResultType.CPP.equals(itemType) ||
      			SearchResultType.DOC.equals(itemType) ||
      			SearchResultType.EMAIL.equals(itemType) ||
      			SearchResultType.HTML.equals(itemType) ||
      			SearchResultType.INI.equals(itemType) ||
      			SearchResultType.JAVA.equals(itemType) ||
      			SearchResultType.PDF.equals(itemType) ||
      			SearchResultType.PPT.equals(itemType) ||
      			SearchResultType.PROPERTIES.equals(itemType) ||
      			SearchResultType.TXT.equals(itemType) ||
      			SearchResultType.XML.equals(itemType) ||
      			SearchResultType.XSD.equals(itemType)      			
      	) {
      		canOpen = true;      		
      	} 
    	return canOpen;
    }
    
    private static void triggerOpenDocumentationPage(SearchResultsView searchResultsView) {
        Utilities.showInformationMessage("Opening Search Documentation right now...");
    }
    
    private static void triggerOpenPreferencesPage(SearchResultsView searchResultsView) {
        Utilities.showInformationMessage("Opening Search Preferences right now...");
    }
    
    private static void openSearchResultInBrowser(String name, String url) {
        
        //CXB This is not yet impled, but it should be obtaind from preferences
        boolean shouldOpenExternalBrowser = false;
        boolean shouldOpenNewBrowser = false;
        
        IWorkbench workbench = PlatformUI.getWorkbench();
        
        IWebBrowser browser = null;

        try {
            
            if(shouldOpenExternalBrowser) {
                browser = workbench.getBrowserSupport().getExternalBrowser();
            } else {
                
                String browserId = (shouldOpenNewBrowser) ? 
                        (CURRENT_BROWSER_ID++) + BROWSER_ID_SUFFIX :
                            BROWSER_ID_SUFFIX;
                        
                        browser = workbench.getBrowserSupport().createBrowser( 
                        		IWorkbenchBrowserSupport.AS_EDITOR 
                        		| IWorkbenchBrowserSupport.LOCATION_BAR 
                        		| IWorkbenchBrowserSupport.NAVIGATION_BAR, 
                        		browserId, 
                        		name, name); 
            }
        
            if(!org.jvnet.lookout.core.util.Utilities.isValidUrl(url)) {
                url = org.jvnet.lookout.core.util.Utilities.makeUrlValid(url);
            }
            
            if(null != url) {
                browser.openURL(new URL(url));
            } else {
            	//CXB_EXTERNALIZE
            	Utilities.showErrorMessage("Cannot open search result as the URL below is not valid:\n"  + url);
            }
            
        } catch (PartInitException e) {
            Utilities.showErrorMessage(Plugin.getResourceString("cannot-open-browser") + e.getLocalizedMessage());
        } catch (MalformedURLException e) {
            Utilities.showErrorMessage(Plugin.getResourceString("malformed-url") + e.getLocalizedMessage());
        }         
    }
    
    private static void openSearchResultInEditor(String url, SearchResultItem item) {
        
        //Find the file type
        FileType fileType = getFileTypeForSearchResultItem(item);
        
        String editorId = fileTypeToEditorIds.get(fileType);
        
        if (null == editorId) {
            Utilities.showErrorMessage(Plugin.getResourceString("no-editor-found") + fileType);
            return;
        }   
        
        File file = new File(url);
        
        if(file.exists()) {
//            JavaFileEditorInput fileInput = new JavaFileEditorInput(file); 
//            
//            try {                      
//                IWorkbench workbench = PlatformUI.getWorkbench();
//                workbench.getActiveWorkbenchWindow().getActivePage().openEditor(fileInput, editorId);
//                
//            } catch (PartInitException e) {
//                Utilities.showErrorMessage(Plugin.getResourceString("cannot-open-search-result") + e.getLocalizedMessage());
//            }
        } else {
            //CXB_EXTERNALIZE
            Utilities.showWarningMessage("The file: " + url +  " no longer exists. \n\nIt is probably cached by the underlying search engine.");
            
            //CXB_TODO Provide option to:
            //-remove the current selection from the view...
            //- view cached version
        }
        
    }
    
    private static FileType getFileTypeForSearchResultItem(SearchResultItem item) {
        
        SearchResultType itemType = (SearchResultType)item.getPropertyValue(SearchResultProperty.TYPE);
        
        if(SearchResultType.DOC.equals(itemType)) {
            return FileType.DOC;
        } else if(SearchResultType.HTML.equals(itemType)) {
            return FileType.HTML;
        } else if(SearchResultType.PDF.equals(itemType)) {
            return FileType.PDF;
        } else if(SearchResultType.PPT.equals(itemType)) {
            return FileType.XSL;
        } else if(SearchResultType.RTF.equals(itemType)) {
            return FileType.RTF;
        } else if(SearchResultType.PS.equals(itemType)) {
            return FileType.PS;
        } else if(SearchResultType.JAVA.equals(itemType)) {
            return FileType.JAVA;
        } else if(SearchResultType.JAVA_CLASS.equals(itemType)) {
            return FileType.CLASS;
        } else if(SearchResultType.XML.equals(itemType)) {
            return FileType.XML;
        } else if(SearchResultType.XSD.equals(itemType)) {
            return FileType.XSD;
        } else if(SearchResultType.OTHER.equals(itemType)) {
            return FileType.TXT;
        } else {
            MultiStatus status = org.jvnet.lookout.core.util.Utilities.createPluginStatus(
					Plugin.getDefault(),
					//CXB_EXTERNALIZE
					"Using default text editor for unrecognized file type",
					IStatus.WARNING,
					new IllegalArgumentException(
							//CXB_EXTERNALIZE
							"The following file type is not recognonized: '" + 
							itemType + 
							"'")
			);  
			
			//Log it...
			Plugin.getDefault().getLog().log(status);
			
			//Will simply interpret it as a text document 
            return FileType.TXT;			
        }  
    }

    public static List<SearchAdapter> getActiveSearchAdapters() {        
        return activeSearchAdapters;
    }
    
    public static void activeSearchAdapter(SearchAdapter searchAdapter) {        
        if((searchAdapters.contains(searchAdapter)) && (!activeSearchAdapters.contains(searchAdapter))) {
            activeSearchAdapters.add(searchAdapter);
        
            String adapterId = searchCorePlugin.getSearchAdapterId(searchAdapter);
            
            String searchableAdapters = 
            	searchCorePlugin.getPluginPreferences().getString(Preferences.SearchableSearchAdapters);
            
            if(!org.jvnet.lookout.core.util.Utilities.containsInList(searchableAdapters, adapterId, SEPARATOR)) {
            	String updateList = org.jvnet.lookout.core.util.Utilities.addToList(searchableAdapters, adapterId, SEPARATOR);
            	searchCorePlugin.getPluginPreferences().setValue(Preferences.SearchableSearchAdapters, updateList);
            }
        }
    }
    
    public static void deactiveSearchAdapter(SearchAdapter searchAdapter) {    
        if((searchAdapters.contains(searchAdapter)) && (activeSearchAdapters.contains(searchAdapter))) {
         
        	activeSearchAdapters.remove(searchAdapter);
        
            String adapterId = searchCorePlugin.getSearchAdapterId(searchAdapter);
            
            String searchableAdapters = 
            	searchCorePlugin.getPluginPreferences().getString(Preferences.SearchableSearchAdapters);
            
            if(org.jvnet.lookout.core.util.Utilities.containsInList(searchableAdapters, adapterId, SEPARATOR)) {
            	String updateList = org.jvnet.lookout.core.util.Utilities.removeFromList(searchableAdapters, adapterId, SEPARATOR);
            	searchCorePlugin.getPluginPreferences().setValue(Preferences.SearchableSearchAdapters, updateList);
            }        
        }
    }
    
    /**
     * Registers a SearchAdapter with this controller. It does not automatically activates this adapter.
     * 
     * @param searchAdapter
     * @param adapter
     */
    public static void registerSearchAdapter(SearchAdapter searchAdapter, ModelToUIAdapter adapter) {
        searchAdapters.add(searchAdapter);
        searchToUIAdapters.put(searchAdapter, adapter);        
    }

    /**
     * Removes the provided SearchAdapter from the list of registered SearchAdapters. It does not automatically 
     * deactivates the SearchAdapter.
     *  
     * @param searchAdapter
     */
    public static void unregisterSearchAdapter(SearchAdapter searchAdapter) {
        searchAdapters.remove(searchAdapter);
        searchToUIAdapters.remove(searchAdapter);
    }
    
    /**
     * Creates and populates the SearchQuery needed for the search request to be executed.
     *  
     * @param searchResultsView
     * @return
     */
    private static SearchQuery createSearchQuery(SearchResultsView searchResultsView) {
        
        //Create SearchQuery instance... 
        SearchQuery query = new SearchQuery();
        
        //Extract parameters
        String searchKeywords = searchResultsView.getKeywords().getText();
        
        //Populate query
        query.addParameter(Parameter.KEYWORDS, searchKeywords);
        
        return query;        
    }
}

