/*******************************************************************************
 * Copyright (c) 2000, 2008 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.wst.jsdt.internal.ui.preferences.formatter;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.dialogs.ContainerCheckedTreeViewer;
import org.eclipse.ui.part.PageBook;
import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin;
import org.eclipse.wst.jsdt.internal.ui.preferences.formatter.WhiteSpaceOptions.InnerNode;
import org.eclipse.wst.jsdt.internal.ui.preferences.formatter.WhiteSpaceOptions.Node;
import org.eclipse.wst.jsdt.internal.ui.preferences.formatter.WhiteSpaceOptions.OptionNode;
import org.eclipse.wst.jsdt.ui.JavaScriptUI;


public class WhiteSpaceTabPage extends FormatterTabPage {
	
    
    /**
     * Encapsulates a view of the options tree which is structured by
     * syntactical element.
     */
	
	private final class SyntaxComponent implements ISelectionChangedListener, ICheckStateListener, IDoubleClickListener {

	    private final String PREF_NODE_KEY= JavaScriptUI.ID_PLUGIN + "formatter_page.white_space_tab_page.node"; //$NON-NLS-1$
	    
	    private final List fIndexedNodeList;
		private final List fTree;
		
		private ContainerCheckedTreeViewer fTreeViewer;
		private Composite fComposite;
		
	    private Node fLastSelected= null;

	    public SyntaxComponent() {
	        fIndexedNodeList= new ArrayList();
			fTree= new WhiteSpaceOptions().createAltTree(fWorkingValues);
			WhiteSpaceOptions.makeIndexForNodes(fTree, fIndexedNodeList);
		}
	    
		public void createContents(final int numColumns, final Composite parent) {
			fComposite= new Composite(parent, SWT.NONE);
			fComposite.setLayoutData(createGridData(numColumns, GridData.HORIZONTAL_ALIGN_FILL, SWT.DEFAULT));
			fComposite.setLayout(createGridLayout(numColumns, false));
		    
            createLabel(numColumns, fComposite, FormatterMessages.WhiteSpaceTabPage_insert_space); 
            
	        fTreeViewer= new ContainerCheckedTreeViewer(fComposite, SWT.SINGLE | SWT.BORDER | SWT.V_SCROLL);
			fTreeViewer.setContentProvider(new ITreeContentProvider() {
				public Object[] getElements(Object inputElement) {
					return ((Collection)inputElement).toArray();
				}
				public Object[] getChildren(Object parentElement) {
					return ((Node)parentElement).getChildren().toArray();
				}
				public Object getParent(Object element) {
				    return ((Node)element).getParent(); 
				}
				public boolean hasChildren(Object element) {
					return ((Node)element).hasChildren();
				}
				public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {}
				public void dispose() {}
			});
			fTreeViewer.setLabelProvider(new LabelProvider());
			fTreeViewer.getControl().setLayoutData(createGridData(numColumns, GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL, SWT.DEFAULT));
			fDefaultFocusManager.add(fTreeViewer.getControl());
	    }
		
		public void initialize() {
			fTreeViewer.addCheckStateListener(this);
			fTreeViewer.addSelectionChangedListener(this);
			fTreeViewer.addDoubleClickListener(this);
		    fTreeViewer.setInput(fTree);
		    restoreSelection();
		    refreshState();
		}
		
		public void refreshState() {
		    final ArrayList checked= new ArrayList(100);
		    for (Iterator iter= fTree.iterator(); iter.hasNext();)
		        ((Node) iter.next()).getCheckedLeafs(checked);
		    fTreeViewer.setGrayedElements(new Object[0]);
		    fTreeViewer.setCheckedElements(checked.toArray());
		    fPreview.clear();
		    if (fLastSelected != null) {
		    	fPreview.addAll(fLastSelected.getSnippets());
		    }
		    doUpdatePreview();
		}

		public void selectionChanged(SelectionChangedEvent event) {
		    final IStructuredSelection selection= (IStructuredSelection)event.getSelection();
		    if (selection.isEmpty())
		        return;
		    final Node node= (Node)selection.getFirstElement();
		    if (node == fLastSelected)
		        return;
		    fDialogSettings.put(PREF_NODE_KEY, node.index);
		    fPreview.clear();
		    fPreview.addAll(node.getSnippets());
		    doUpdatePreview();
		    fLastSelected= node;
		}

		public void checkStateChanged(CheckStateChangedEvent event) {
			final Node node= (Node)event.getElement();
			node.setChecked(event.getChecked());
			doUpdatePreview();
			notifyValuesModified();
		}

		public void restoreSelection() {
			int index;
			try {
				index= fDialogSettings.getInt(PREF_NODE_KEY);
			} catch (NumberFormatException ex) {
				index= -1;
			}
			if (index < 0 || index > fIndexedNodeList.size() - 1) {
				index= 0;
			}
			final Node node= (Node)fIndexedNodeList.get(index);
			if (node != null) {
			    fTreeViewer.expandToLevel(node, 0);
			    fTreeViewer.setSelection(new StructuredSelection(new Node [] {node}));
			    fLastSelected= node;
			}
		}

        public void doubleClick(DoubleClickEvent event) {
            final ISelection selection= event.getSelection();
            if (selection instanceof IStructuredSelection) {
                final Node node= (Node)((IStructuredSelection)selection).getFirstElement();
                fTreeViewer.setExpandedState(node, !fTreeViewer.getExpandedState(node));
            }
        }
        
        public Control getControl() {
            return fComposite;
        }
	}
	
	
	
	private final class JavaElementComponent implements ISelectionChangedListener, ICheckStateListener {
	    
	    private final String PREF_INNER_INDEX= JavaScriptUI.ID_PLUGIN + "formatter_page.white_space.java_view.inner"; //$NON-NLS-1$ 
		private final String PREF_OPTION_INDEX= JavaScriptUI.ID_PLUGIN + "formatter_page.white_space.java_view.option"; //$NON-NLS-1$
		
	    private final ArrayList fIndexedNodeList;
	    private final ArrayList fTree;
	    
	    private InnerNode fLastSelected;
	    
	    private TreeViewer fInnerViewer;
	    private CheckboxTableViewer fOptionsViewer;
	    
	    private Composite fComposite;
	    
	    public JavaElementComponent() {
			fIndexedNodeList= new ArrayList();
			fTree= new WhiteSpaceOptions().createTreeByJavaElement(fWorkingValues);
			WhiteSpaceOptions.makeIndexForNodes(fTree, fIndexedNodeList);
	    }

	    public void createContents(int numColumns, Composite parent) {
			
			fComposite= new Composite(parent, SWT.NONE);
			fComposite.setLayoutData(createGridData(numColumns, GridData.HORIZONTAL_ALIGN_FILL, SWT.DEFAULT));
			fComposite.setLayout(createGridLayout(numColumns, false));
			
            createLabel(numColumns, fComposite, FormatterMessages.WhiteSpaceTabPage_insert_space, GridData.HORIZONTAL_ALIGN_BEGINNING); 
			
			final SashForm sashForm= new SashForm(fComposite, SWT.VERTICAL);
			sashForm.setLayoutData(createGridData(numColumns, GridData.FILL_BOTH, SWT.DEFAULT));
			
			fInnerViewer= new TreeViewer(sashForm, SWT.SINGLE | SWT.BORDER | SWT.V_SCROLL);

			fInnerViewer.setContentProvider(new ITreeContentProvider() {
				public Object[] getElements(Object inputElement) {
					return ((Collection)inputElement).toArray();
				}
				public Object[] getChildren(Object parentElement) {
				    final List children= ((Node)parentElement).getChildren();
				    final ArrayList innerChildren= new ArrayList();
				    for (final Iterator iter= children.iterator(); iter.hasNext();) {
                        final Object o= iter.next();
                        if (o instanceof InnerNode) innerChildren.add(o);
                    }
				    return innerChildren.toArray();
				}
				public Object getParent(Object element) {
				    if (element instanceof InnerNode)
				        return ((InnerNode)element).getParent();
				    return null;
				}
				public boolean hasChildren(Object element) {
				    final List children= ((Node)element).getChildren();
				    for (final Iterator iter= children.iterator(); iter.hasNext();)
                        if (iter.next() instanceof InnerNode) return true;
				    return false;
				}
				public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {}
				public void dispose() {}
			});
			
			fInnerViewer.setLabelProvider(new LabelProvider());
			
			final GridData innerGd= createGridData(numColumns, GridData.HORIZONTAL_ALIGN_FILL | GridData.FILL_VERTICAL, SWT.DEFAULT);
			innerGd.heightHint= fPixelConverter.convertHeightInCharsToPixels(3);
			fInnerViewer.getControl().setLayoutData(innerGd);
			
			fOptionsViewer= CheckboxTableViewer.newCheckList(sashForm, SWT.SINGLE | SWT.BORDER | SWT.H_SCROLL);
			fOptionsViewer.setContentProvider(new ArrayContentProvider());
			fOptionsViewer.setLabelProvider(new LabelProvider());
			
			final GridData optionsGd= createGridData(numColumns, GridData.HORIZONTAL_ALIGN_FILL | GridData.FILL_VERTICAL, SWT.DEFAULT);
			optionsGd.heightHint= fPixelConverter.convertHeightInCharsToPixels(3);
			fOptionsViewer.getControl().setLayoutData(optionsGd);
	        
			fDefaultFocusManager.add(fInnerViewer.getControl());
	        fDefaultFocusManager.add(fOptionsViewer.getControl());
			
			fInnerViewer.setInput(fTree);
		}
	    
	    public void refreshState() {
	    	if (fLastSelected != null) {
	    		innerViewerChanged(fLastSelected);
	    	}
	    }
	    
	    public void initialize() {
	        fInnerViewer.addSelectionChangedListener(this);
	        fOptionsViewer.addSelectionChangedListener(this);
	        fOptionsViewer.addCheckStateListener(this);
	        restoreSelections();
	        refreshState();
	    }
	    
	    private void restoreSelections() {
	        Node node;
	        final int innerIndex= getValidatedIndex(PREF_INNER_INDEX);
			node= (Node)fIndexedNodeList.get(innerIndex);
			if (node instanceof InnerNode) {
			    fInnerViewer.expandToLevel(node, 0);
			    fInnerViewer.setSelection(new StructuredSelection(new Object[] {node}));
			    fLastSelected= (InnerNode)node;
			}
			
	        final int optionIndex= getValidatedIndex(PREF_OPTION_INDEX);
			node= (Node)fIndexedNodeList.get(optionIndex);
			if (node instanceof OptionNode) {
			    fOptionsViewer.setSelection(new StructuredSelection(new Object[] {node}));
			}

	    }
	    
	    private int getValidatedIndex(String key) {
			int index;
			try {
				index= fDialogSettings.getInt(key);
			} catch (NumberFormatException ex) {
				index= 0;
			}
			if (index < 0 || index > fIndexedNodeList.size() - 1) {
				index= 0; 
			}
			return index;
	    }
	    
	    public Control getControl() {
	        return fComposite;
	    }

        public void selectionChanged(SelectionChangedEvent event) {
            final IStructuredSelection selection= (IStructuredSelection)event.getSelection();

            if (selection.isEmpty() || !(selection.getFirstElement() instanceof Node))
                return;

            final Node selected= (Node)selection.getFirstElement();

            if (selected == null || selected == fLastSelected) 
			    return;
            
            
            if (event.getSource() == fInnerViewer && selected instanceof InnerNode) {
                fLastSelected= (InnerNode)selected;
                fDialogSettings.put(PREF_INNER_INDEX, selected.index);
                innerViewerChanged((InnerNode)selected);
            }
            else if (event.getSource() == fOptionsViewer && selected instanceof OptionNode)
                fDialogSettings.put(PREF_OPTION_INDEX, selected.index);
        }
	
        private void innerViewerChanged(InnerNode selectedNode) {
            
			final List children= selectedNode.getChildren();
			
			final ArrayList optionsChildren= new ArrayList();
			for (final Iterator iter= children.iterator(); iter.hasNext();) {
			    final Object o= iter.next();
			    if (o instanceof OptionNode) optionsChildren.add(o);
			}
			
			fOptionsViewer.setInput(optionsChildren.toArray());
			
			for (final Iterator iter= optionsChildren.iterator(); iter.hasNext();) {
			    final OptionNode child= (OptionNode)iter.next();
                    fOptionsViewer.setChecked(child, child.getChecked());
			}
			
			fPreview.clear();
			fPreview.addAll(selectedNode.getSnippets());
			doUpdatePreview();
        }
        
        public void checkStateChanged(CheckStateChangedEvent event) {
			final OptionNode option= (OptionNode)event.getElement();
			if (option != null)
			    option.setChecked(event.getChecked());
			doUpdatePreview();
			notifyValuesModified();
        }
	}
	
	

	/**
	 * This component switches between the two view and is responsible for delegating
	 * the appropriate update requests.
	 */
	private final class SwitchComponent extends SelectionAdapter {
	    private final String PREF_VIEW_KEY= JavaScriptUI.ID_PLUGIN + "formatter_page.white_space_tab_page.view"; //$NON-NLS-1$
	    private final String [] fItems= new String [] {
	        FormatterMessages.WhiteSpaceTabPage_sort_by_java_element, 
	        FormatterMessages.WhiteSpaceTabPage_sort_by_syntax_element
	    };
	    
	    private Combo fSwitchCombo; 
	    private PageBook fPageBook;
	    private final SyntaxComponent fSyntaxComponent;
	    private final JavaElementComponent fJavaElementComponent;
	    
	    public SwitchComponent() {
	        fSyntaxComponent= new SyntaxComponent();
	        fJavaElementComponent= new JavaElementComponent();
	    }
	    
        public void widgetSelected(SelectionEvent e) {
            final int index= fSwitchCombo.getSelectionIndex();
            if (index == 0) {
    		    fDialogSettings.put(PREF_VIEW_KEY, false);
    		    fJavaElementComponent.refreshState();
                fPageBook.showPage(fJavaElementComponent.getControl());
            }
            else if (index == 1) { 
    		    fDialogSettings.put(PREF_VIEW_KEY, true);
    		    fSyntaxComponent.refreshState();
                fPageBook.showPage(fSyntaxComponent.getControl());
            }
        }

        public void createContents(int numColumns, Composite parent) {
             
            fPageBook= new PageBook(parent, SWT.NONE);
            fPageBook.setLayoutData(createGridData(numColumns, GridData.FILL_BOTH, SWT.DEFAULT));
            
            fJavaElementComponent.createContents(numColumns, fPageBook);		
            fSyntaxComponent.createContents(numColumns, fPageBook);
            
            fSwitchCombo= new Combo(parent, SWT.READ_ONLY);
            final GridData gd= createGridData(numColumns, GridData.HORIZONTAL_ALIGN_END, SWT.DEFAULT);
            fSwitchCombo.setLayoutData(gd);
            fSwitchCombo.setItems(fItems);
        }
        
        public void initialize() {
            fSwitchCombo.addSelectionListener(this);
    	    fJavaElementComponent.initialize();
    	    fSyntaxComponent.initialize();
    	    restoreSelection();
        }

        private void restoreSelection() {
            final boolean selectSyntax= fDialogSettings.getBoolean(PREF_VIEW_KEY);
            if (selectSyntax) {
            	fSyntaxComponent.refreshState();
                fSwitchCombo.setText(fItems[1]);
                fPageBook.showPage(fSyntaxComponent.getControl());
			} else {
            	fJavaElementComponent.refreshState();
			    fSwitchCombo.setText(fItems[0]);
			    fPageBook.showPage(fJavaElementComponent.getControl());
			}
        }
	}
	

	
	
	private final SwitchComponent fSwitchComponent;
	protected final IDialogSettings fDialogSettings; 

	protected SnippetPreview fPreview;


	/**
	 * Create a new white space dialog page.
	 * @param modifyDialog
	 * @param workingValues
	 */
	public WhiteSpaceTabPage(ModifyDialog modifyDialog, Map workingValues) {
		super(modifyDialog, workingValues);
		fDialogSettings= JavaScriptPlugin.getDefault().getDialogSettings();
		fSwitchComponent= new SwitchComponent();
	}

	protected void doCreatePreferences(Composite composite, int numColumns) {
		fSwitchComponent.createContents(numColumns, composite);
	}

	protected void initializePage() {
        fSwitchComponent.initialize();
	}
	
    protected JavaPreview doCreateJavaPreview(Composite parent) {
        fPreview= new SnippetPreview(fWorkingValues, parent);
        return fPreview;
    }

    protected void doUpdatePreview() {
    	super.doUpdatePreview();
        fPreview.update();
    }
}
