/*******************************************************************************
 * Copyright (c) 2010, 2016 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Tasktop Technologies - initial API and implementation
 *     Daniel Kruegler <daniel.kruegler@gmail.com> - Bug 471310
 ******************************************************************************/

package org.eclipse.ui.internal.progress;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.TaskItem;
import org.eclipse.ui.progress.IProgressConstants;
import org.eclipse.ui.progress.IProgressConstants2;
import org.eclipse.ui.progress.WorkbenchJob;

/**
 * The TaskBarProgressManager is the class that displays progress in the
 * application TaskBar if the job specifies that it should show progress (@see
 * {@link IProgressConstants2#SHOW_IN_TASKBAR_ICON_PROPERTY}
 *
 * @since 3.6
 */
public class TaskBarProgressManager {

	private IJobProgressManagerListener listener;

	private WorkbenchJob animationUpdateJob;

	private boolean isAnimated = false;

	private List<Job> jobs = Collections.synchronizedList(new ArrayList<Job>());

	private Map<Job, JobInfo> jobInfoMap = Collections.synchronizedMap(new HashMap<Job, JobInfo>());

	private final TaskItem taskItem;

	private ImageDescriptor overlayDescriptor;

	private Image overlayImage;

	public TaskBarProgressManager(TaskItem taskItem) {
		Assert.isNotNull(taskItem);
		this.taskItem = taskItem;
		animationUpdateJob = getAnimationUpdateJob();
		animationUpdateJob.setSystem(true);
		listener = getProgressListener();

		// Register the IJobProgressManagerListener so we can display progress
		// on the application TaskBar
		ProgressManager.getInstance().addListener(listener);

		taskItem.addDisposeListener(e -> dispose());
	}

	/**
	 * Remove the listener and stop the animation
	 */
	public void dispose() {
		ProgressManager.getInstance().removeListener(listener);
		setAnimated(false);
		disposeOverlay();
	}

	private WorkbenchJob getAnimationUpdateJob() {
		return new WorkbenchJob(ProgressMessages.AnimationManager_AnimationStart) {

			@Override
			public IStatus runInUIThread(IProgressMonitor monitor) {
				if (isAnimated) {
					if (!taskItem.isDisposed() && !jobs.isEmpty()) {
						Job job = jobs.get(0);
						JobInfo jobInfo = jobInfoMap.get(job);
						if (job != null && jobInfo != null) {
							int percentDone = getPercentDone(jobInfo);
							if (percentDone == IProgressMonitor.UNKNOWN || (jobInfo.hasTaskInfo()
									&& jobInfo.getTaskInfo().totalWork == IProgressMonitor.UNKNOWN)) {
								setProgressState(SWT.INDETERMINATE);
							} else {
								setProgressState(SWT.NORMAL);
								if (!taskItem.isDisposed()) {
									taskItem.setProgress(percentDone);
								}
							}
						} else {
							setProgressState(SWT.DEFAULT);
						}
						updateImage(job);
					} else {
						updateImage(null);
					}
				} else {
					setProgressState(SWT.DEFAULT);
					updateImage(null);
				}

				if (isAnimated && taskItem != null && !taskItem.isDisposed()) {
					schedule(400);
				}
				return Status.OK_STATUS;
			}

			private void setProgressState(int state) {
				if (!taskItem.isDisposed() && taskItem.getProgressState() != state) {
					taskItem.setProgressState(SWT.DEFAULT);
					taskItem.setProgressState(state);
				}
			}

			private int getPercentDone(JobTreeElement info) {
				if (info.isJobInfo()) {
					return ((JobInfo) info).getPercentDone();
				}

				if (info.hasChildren()) {
					Object[] roots = ((GroupInfo) info).getChildren();
					if (roots.length == 1 && roots[0] instanceof JobTreeElement) {
						TaskInfo ti = ((JobInfo) roots[0]).getTaskInfo();
						if (ti != null) {
							return ti.getPercentDone();
						}
					}
					return ((GroupInfo) info).getPercentDone();
				}
				return 0;
			}
		};
	}

	private void updateImage(Job job) {
		if (taskItem == null || taskItem.isDisposed())
			return;

		if (job == null) {
			disposeOverlay();
			taskItem.setOverlayImage(null);
			return;
		}

		// first check whether the job specifies image property
		// if not check with progress manager for its family
		ImageDescriptor descriptor = (ImageDescriptor) job.getProperty(IProgressConstants.ICON_PROPERTY);
		if (descriptor != null) {

			// if the description is same, do nothing.
			// Else dispose old one and store this
			if (!descriptor.equals(overlayDescriptor)) {
				disposeOverlay();
				setOverlay(descriptor);
			}
		} else if (ProgressManager.getInstance().getIconFor(job) != null) {
			disposeOverlay();
			Image newImage = ProgressManager.getInstance().getIconFor(job);
			taskItem.setOverlayImage(newImage);
		} else {
			disposeOverlay();
			taskItem.setOverlayImage(null);
		}
	}

	private void setOverlay(ImageDescriptor descriptor) {
		overlayDescriptor = descriptor;
		overlayImage = descriptor.createImage();
		taskItem.setOverlayImage(overlayImage);
	}

	private void disposeOverlay() {
		overlayDescriptor = null;
		if (overlayImage != null) {
			overlayImage.dispose();
			overlayImage = null;
		}
	}

	private IJobProgressManagerListener getProgressListener() {
		return new IJobProgressManagerListener() {

			@Override
			public void addJob(JobInfo info) {
				// Don't count the animate job itself
				if (isNotTracked(info)) {
					return;
				}
				if (jobs.isEmpty()) {
					setAnimated(true);
				}
				if (!jobs.contains(info.getJob())) {
					jobs.add(info.getJob());
				}
				jobInfoMap.put(info.getJob(), info);
			}

			@Override
			public void refreshJobInfo(JobInfo info) {
				int state = info.getJob().getState();
				if (state == Job.RUNNING) {
					addJob(info);
				} else {
					removeJob(info);
				}
			}

			@Override
			public void refreshAll() {
				ProgressManager manager = ProgressManager.getInstance();
				jobs.clear();
				jobInfoMap.clear();
				setAnimated(false);
				for (JobInfo currentInfo : manager.getJobInfos(showsDebug())) {
					addJob(currentInfo);
				}
			}

			@Override
			public void removeJob(JobInfo info) {
				jobs.remove(info.getJob());
				jobInfoMap.remove(info.getJob());
				if (jobs.isEmpty()) {
					setAnimated(false);
				}
			}

			@Override
			public boolean showsDebug() {
				return false;
			}

			/**
			 * If the job isn't running or doesn't specify the
			 * IProgressConstants#SHOW_IN_TASKBAR_ICON_PROPERTY property, don't
			 * bother tracking it.
			 */
			private boolean isNotTracked(JobInfo info) {
				Job job = info.getJob();
				return job.getState() != Job.RUNNING || !shouldShowSystemProgress(info);
			}

			private boolean shouldShowSystemProgress(JobInfo info) {
				Boolean showInTaskBarIcon = Boolean.FALSE;
				Object property = info.getJob().getProperty(IProgressConstants2.SHOW_IN_TASKBAR_ICON_PROPERTY);

				if (property instanceof Boolean) {
					showInTaskBarIcon = (Boolean) property;
				}
				return showInTaskBarIcon.booleanValue();

			}

			@Override
			public void addGroup(GroupInfo info) {
				// Don't care about groups
			}

			@Override
			public void removeGroup(GroupInfo group) {
				// Don't care about groups
			}

			@Override
			public void refreshGroup(GroupInfo info) {
				// Don't care about groups
			}
		};
	}

	private synchronized void setAnimated(boolean animated) {
		isAnimated = animated;
		animationUpdateJob.schedule();
	}
}
