/*******************************************************************************
 * Copyright (c) 2010 Tasktop Technologies 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:
 *     Tasktop Technologies - initial API and implementation
 *******************************************************************************/

package org.eclipse.mylyn.builds.internal.core.operations;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.mylyn.builds.core.IBuild;
import org.eclipse.mylyn.builds.core.IBuildPlan;
import org.eclipse.mylyn.builds.core.IBuildServer;
import org.eclipse.mylyn.builds.core.spi.BuildPlanRequest;
import org.eclipse.mylyn.builds.core.spi.GetBuildsRequest;
import org.eclipse.mylyn.builds.core.spi.GetBuildsRequest.Kind;
import org.eclipse.mylyn.builds.internal.core.BuildPlan;
import org.eclipse.mylyn.builds.internal.core.BuildServer;
import org.eclipse.mylyn.builds.internal.core.BuildsCorePlugin;
import org.eclipse.mylyn.builds.internal.core.util.BuildRunnableWithResult;
import org.eclipse.mylyn.builds.internal.core.util.BuildRunner;
import org.eclipse.mylyn.commons.core.operations.IOperationMonitor;
import org.eclipse.mylyn.commons.core.operations.IOperationMonitor.OperationFlag;

/**
 * Manages refreshes for plans and builds. Each server has one associated session that may process several requests
 * concurrently.
 * 
 * @author Steffen Pingel
 */
public class RefreshSession {

	private final BuildServer server;

	public RefreshSession(IBuildServer server) {
		this.server = ((BuildServer) server).createWorkingCopy();
	}

	public BuildPlan getPlanById(List<IBuildPlan> plans, String id) {
		if (id != null) {
			for (IBuildPlan plan : plans) {
				if (id.equals(plan.getId())) {
					return (BuildPlan) plan;
				}
			}
		}
		return null;
	}

	private boolean isStale(IBuildPlan oldPlan, BuildPlan newPlan, IOperationMonitor monitor) {
		if (oldPlan.getStatus() != newPlan.getStatus() || oldPlan.getState() != newPlan.getState()) {
			return true;
		}
		if (oldPlan.getLastBuild() != null && newPlan.getLastBuild() != null) {
			// only refresh if there is a new build or if build status has changed
			return isStale(oldPlan.getLastBuild(), newPlan.getLastBuild(), monitor);
		}
		return oldPlan.getLastBuild() == null && newPlan.getLastBuild() == null;
	}

	private boolean isStale(IBuild oldBuild, IBuild newBuild, IOperationMonitor monitor) {
		if (oldBuild.getBuildNumber() != newBuild.getBuildNumber()) {
			return true;
		}
		return false;
	}

	protected void markStale(RefreshRequest request, BuildPlan newPlan) {
		request.stalePlans.add(newPlan);
	}

	public void refresh(RefreshRequest request, IOperationMonitor monitor) throws CoreException {
		// initialize
		request.stalePlans = Collections.synchronizedList(new ArrayList<IBuildPlan>());

		// refresh selected or all plans
		refreshPlans(request, monitor);

		// force refresh of selected plans
		if (request.plansToRefresh != null) {
			request.stalePlans.clear();
			request.stalePlans.addAll(request.plansToRefresh);
		}

		// refresh last build of stale plans
		for (IBuildPlan plan : request.stalePlans) {
			GetBuildsRequest buildRequest = new GetBuildsRequest(plan, Kind.LAST);
			refreshBuilds(request, buildRequest, monitor);
		}
	}

	public void refreshBuilds(final RefreshRequest request, final GetBuildsRequest buildRequest,
			final IOperationMonitor monitor) throws CoreException {
		final List<IBuild> result = BuildRunner.run(new BuildRunnableWithResult<List<IBuild>>() {
			@Override
			public List<IBuild> run() throws CoreException {
				return server.getBehaviour().getBuilds(buildRequest, monitor);
			}
		});

		if (result == null) {
			// indicates that plan was never built
			return;
		}

		// merge builds into model
		final BuildServer original = server.getOriginal();
		original.getLoader().getRealm().syncExec(new Runnable() {
			public void run() {
				Date refreshDate = new Date();
				for (IBuildPlan modelPlan : request.getModel().getPlans()) {
					if (modelPlan.getServer() == original && modelPlan.getId().equals(buildRequest.getPlan().getId())) {
						updateLastBuild(request, modelPlan, result.get(0), refreshDate);
					}
				}
			}
		});
	}

	private void updateLastBuild(final RefreshRequest request, IBuildPlan plan, IBuild build, Date refreshDate) {
		if (plan.getLastBuild() != null) {
			request.getModel().getBuilds().remove(plan.getLastBuild());
		}
		plan.setLastBuild(build);
		build.setPlan(plan);
		build.setServer(plan.getServer());
		build.setRefreshDate(refreshDate);
		request.getModel().getBuilds().add(build);
	}

	public void refreshPlans(final RefreshRequest request, final IOperationMonitor monitor) throws CoreException {
		final BuildServer original = server.getOriginal();

		// prepare
		final AtomicReference<List<String>> input = new AtomicReference<List<String>>();
		if (request.plansToRefresh != null) {
			// refresh selected plans
			List<String> planIds = new ArrayList<String>();
			for (IBuildPlan plan : request.plansToRefresh) {
				planIds.add(plan.getId());
			}
			input.set(planIds);
		} else {
			// refresh all plans for server
			original.getLoader().getRealm().syncExec(new Runnable() {
				public void run() {
					List<String> planIds = new ArrayList<String>();
					for (IBuildPlan oldPlan : request.getModel().getPlans()) {
						if (oldPlan.getServer() == original) {
							planIds.add(oldPlan.getId());
						}
					}
					input.set(planIds);
				}
			});
		}

		// execute
		final List<IBuildPlan> result = BuildRunner.run(new BuildRunnableWithResult<List<IBuildPlan>>() {
			@Override
			public List<IBuildPlan> run() throws CoreException {
				BuildPlanRequest planRequest = new BuildPlanRequest(input.get());
				return server.getBehaviour().getPlans(planRequest, monitor);
			}
		});

		// handle result
		if (result == null) {
			throw new CoreException(new Status(IStatus.ERROR, BuildsCorePlugin.ID_PLUGIN,
					"Server did not provide any plans."));
		}
		original.getLoader().getRealm().syncExec(new Runnable() {
			public void run() {
				Date refreshDate = new Date();
				original.setRefreshDate(refreshDate);
				for (IBuildPlan oldPlan : request.getModel().getPlans()) {
					if (oldPlan.getServer() == original) {
						BuildPlan newPlan = getPlanById(result, oldPlan.getId());
						if (newPlan != null) {
							newPlan.setRefreshDate(refreshDate);
							update(request, oldPlan, newPlan, monitor);
						} else {
							((BuildPlan) oldPlan).setOperationStatus(new Status(IStatus.ERROR,
									BuildsCorePlugin.ID_PLUGIN, "The plan does not exist."));
						}
					}
				}
			}
		});
	}

	protected void update(RefreshRequest request, IBuildPlan oldPlan, BuildPlan newPlan, IOperationMonitor monitor) {
		boolean stale = isStale(oldPlan, newPlan, monitor);
		if (stale || !monitor.hasFlag(OperationFlag.BACKGROUND)) {
			markStale(request, newPlan);
		}
		((BuildPlan) oldPlan).merge(newPlan);
		if (stale && newPlan.getLastBuild() != null) {
			updateLastBuild(request, oldPlan, newPlan.getLastBuild(), new Date());
		}
	}

}
