/*******************************************************************************
 * Copyright (c) 2004, 2005 QNX Software Systems 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:
 * QNX Software Systems - Initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.debug.internal.ui.propertypages; 

import java.util.ArrayList;
import java.util.List;

import org.eclipse.cdt.debug.core.model.ICBreakpointFilterExtension;
import org.eclipse.cdt.debug.core.model.ICDebugTarget;
import org.eclipse.cdt.debug.core.model.ICThread;
import org.eclipse.cdt.debug.ui.CDebugUIPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.model.IDebugElement;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.IStackFrame;
import org.eclipse.debug.core.model.IThread;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTreeViewer;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
 
public class ThreadFilterEditor {

	/**
	 * Comment for ThreadFilterEditor.
	 */
	public class CheckHandler implements ICheckStateListener {

		/* (non-Javadoc)
		 * @see org.eclipse.jface.viewers.ICheckStateListener#checkStateChanged(org.eclipse.jface.viewers.CheckStateChangedEvent)
		 */
		public void checkStateChanged( CheckStateChangedEvent event ) {
			Object element = event.getElement();
			if ( element instanceof IDebugTarget ) {
				checkTarget( (IDebugTarget)element, event.getChecked() );
			}
			else if ( element instanceof IThread ) {
				checkThread( (IThread)element, event.getChecked() );
			}
		}

		/**
		 * Check or uncheck a debug target in the tree viewer.
		 * When a debug target is checked, attempt to
		 * check all of the target's threads by default.
		 * When a debug target is unchecked, uncheck all
		 * its threads.
		 */
		protected void checkTarget( IDebugTarget target, boolean checked ) {
			getThreadViewer().setChecked( target, checked );
			getThreadViewer().setGrayed( target, false );
			getThreadViewer().expandToLevel( target, AbstractTreeViewer.ALL_LEVELS );
			IThread[] threads;
			try {
				threads = target.getThreads();
			}
			catch( DebugException exception ) {
				CDebugUIPlugin.log( exception );
				return;
			}
			for( int i = 0; i < threads.length; i++ ) {
				getThreadViewer().setChecked( threads[i], checked );
				getThreadViewer().setGrayed( threads[i], false );
			}
		}
	
		/**
		 * Check or uncheck a thread.
		 * Update the thread's debug target.
		 */
		protected void checkThread( IThread thread, boolean checked ) {
			getThreadViewer().setChecked( thread, checked );
			IDebugTarget target = (thread).getDebugTarget();
			IThread[] threads;
			try {
				threads = target.getThreads();
			}
			catch( DebugException exception ) {
				CDebugUIPlugin.log( exception );
				return;
			}
			int checkedNumber = 0;
			for( int i = 0; i < threads.length; i++ ) {
				if ( getThreadViewer().getChecked( threads[i] ) ) {
					++checkedNumber;
				}
			}
			if ( checkedNumber == 0 ) {
				getThreadViewer().setChecked( target, false );
				getThreadViewer().setGrayed( target, false );
			}
			else if ( checkedNumber == threads.length ) {
				getThreadViewer().setChecked( target, true );
				getThreadViewer().setGrayed( target, false );
			}
			else {
				getThreadViewer().setGrayChecked( target, true );
			}
		}
	}

	/**
	 * Comment for ThreadFilterEditor.
	 */
	public class ThreadFilterContentProvider implements ITreeContentProvider {

		/** 
		 * Constructor for ThreadFilterContentProvider. 
		 */
		public ThreadFilterContentProvider() {
			super();
			// TODO Auto-generated constructor stub
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.viewers.ITreeContentProvider#getChildren(java.lang.Object)
		 */
		public Object[] getChildren( Object parent ) {
			if ( parent instanceof IDebugTarget ) {
				ICDebugTarget target = (ICDebugTarget)((IDebugTarget)parent).getAdapter( ICDebugTarget.class );
				if ( target != null ) {
					try {
						return ((ICDebugTarget)parent).getThreads();
					}
					catch( DebugException e ) {
						CDebugUIPlugin.log( e );
					}
				}
			}
			if ( parent instanceof ILaunchManager ) {
				List children = new ArrayList();
				ILaunch[] launches = ((ILaunchManager)parent).getLaunches();
				IDebugTarget[] targets;
				ICDebugTarget target;
				for( int i = 0, numLaunches = launches.length; i < numLaunches; i++ ) {
					targets = launches[i].getDebugTargets();
					for( int j = 0, numTargets = targets.length; j < numTargets; j++ ) {
						target = (ICDebugTarget)targets[j].getAdapter( ICDebugTarget.class );
						if ( target != null && !target.isDisconnected() && !target.isTerminated() ) {
							children.add( target );
						}
					}
				}
				return children.toArray();
			}
			return new Object[0];
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.viewers.ITreeContentProvider#getParent(java.lang.Object)
		 */
		public Object getParent( Object element ) {
			if ( element instanceof IThread ) {
				return ((IThread)element).getDebugTarget();
			}
			if ( element instanceof IDebugTarget ) {
				return ((IDebugElement)element).getLaunch();
			}
			if ( element instanceof ILaunch ) {
				return DebugPlugin.getDefault().getLaunchManager();
			}
			return null;
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.viewers.ITreeContentProvider#hasChildren(java.lang.Object)
		 */
		public boolean hasChildren( Object element ) {
			if ( element instanceof IStackFrame ) {
				return false;
			}
			if ( element instanceof IDebugElement ) {
				return getChildren( element ).length > 0;
			}
			if ( element instanceof ILaunch ) {
				return true;
			}
			if ( element instanceof ILaunchManager ) {
				return ((ILaunchManager)element).getLaunches().length > 0;
			}
			return false;
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object)
		 */
		public Object[] getElements( Object inputElement ) {
			return getChildren( inputElement );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.viewers.IContentProvider#dispose()
		 */
		public void dispose() {
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer, java.lang.Object, java.lang.Object)
		 */
		public void inputChanged( Viewer viewer, Object oldInput, Object newInput ) {
		}
	}

	private CBreakpointFilteringPage fPage;

	private CheckboxTreeViewer fThreadViewer;

	private ThreadFilterContentProvider fContentProvider;

	private CheckHandler fCheckHandler;

	/** 
	 * Constructor for ThreadFilterEditor. 
	 */
	public ThreadFilterEditor( Composite parent, CBreakpointFilteringPage page ) {
		fPage = page;
		fContentProvider = new ThreadFilterContentProvider();
		fCheckHandler = new CheckHandler();
		createThreadViewer( parent );
	}

	protected CBreakpointFilteringPage getPage() {
		return fPage;
	}

	private void createThreadViewer( Composite parent ) {
		Label label = new Label( parent, SWT.NONE );
		label.setText( PropertyPageMessages.getString( "ThreadFilterEditor.0" ) ); //$NON-NLS-1$
		label.setFont( parent.getFont() );
		label.setLayoutData( new GridData() );
		GridData data = new GridData( GridData.FILL_BOTH );
		data.heightHint = 100;
		fThreadViewer = new CheckboxTreeViewer( parent, SWT.BORDER );
		fThreadViewer.addCheckStateListener( fCheckHandler );
		fThreadViewer.getTree().setLayoutData( data );
		fThreadViewer.getTree().setFont( parent.getFont() );
		fThreadViewer.setContentProvider( fContentProvider );
		fThreadViewer.setLabelProvider( DebugUITools.newDebugModelPresentation() );
		fThreadViewer.setInput( DebugPlugin.getDefault().getLaunchManager() );
		setInitialCheckedState();
	}

	/**
	 * Returns the debug targets that appear in the tree
	 */
	protected IDebugTarget[] getDebugTargets() {
		Object input = fThreadViewer.getInput();
		if ( !(input instanceof ILaunchManager) ) {
			return new IDebugTarget[0];
		}
		ILaunchManager launchManager = (ILaunchManager)input;
		return launchManager.getDebugTargets();
	}

	protected CheckboxTreeViewer getThreadViewer() {
		return fThreadViewer;
	}

	/**
	 * Sets the initial checked state of the tree viewer.
	 * The initial state should reflect the current state
	 * of the breakpoint. If the breakpoint has a thread
	 * filter in a given thread, that thread should be
	 * checked.
	 */
	protected void setInitialCheckedState() {
	    ICBreakpointFilterExtension filterExtension = fPage.getFilterExtension(); 
		try {
			ICDebugTarget[] targets = filterExtension.getTargetFilters();
			for( int i = 0; i < targets.length; i++ ) {
				ICThread[] filteredThreads = filterExtension.getThreadFilters( targets[i] );
				if ( filteredThreads != null ) {
					for ( int j = 0; j < filteredThreads.length; ++j )
						fCheckHandler.checkThread( filteredThreads[j], true );
				}
				else {
					fCheckHandler.checkTarget( targets[i], true );
				}
			}
		}
		catch( CoreException e ) {
			CDebugUIPlugin.log( e );
		}
	}

	protected void doStore() {
		ICBreakpointFilterExtension filterExtension = fPage.getFilterExtension();
		IDebugTarget[] targets = getDebugTargets();
		for ( int i = 0; i < targets.length; ++i ) {
			if ( !(targets[i] instanceof ICDebugTarget) )
				continue;
			try {
				if ( getThreadViewer().getChecked( targets[i] ) ) {
					if ( getThreadViewer().getGrayed( targets[i] ) ) {
						ICThread[] threads = getTargetThreadFilters( (ICDebugTarget)targets[i] );
						filterExtension.setThreadFilters( threads );
					}
					else {
						filterExtension.setTargetFilter( (ICDebugTarget)targets[i] );
					}
				}
				else {
					filterExtension.removeTargetFilter( (ICDebugTarget)targets[i] );
				}
				DebugPlugin.getDefault().getBreakpointManager().fireBreakpointChanged( fPage.getBreakpoint() );
			}
			catch( CoreException e ) {
				CDebugUIPlugin.log( e );
			}
		}
	}

	private ICThread[] getTargetThreadFilters( ICDebugTarget target ) {
		Object[] threads = ((ITreeContentProvider)getThreadViewer().getContentProvider()).getChildren( target );
		ArrayList list = new ArrayList( threads.length );
		for ( int i = 0; i < threads.length; ++i ) {
			if ( getThreadViewer().getChecked( threads[i] ) )
				list.add( threads[i] );
		}
		return (ICThread[])list.toArray( new ICThread[list.size()] );
	}
}
