/*******************************************************************************
 * Copyright (c) 2003, 2017 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
 *     Trevor S. Kaufman <endante@gmail.com> - bug 156152
 *******************************************************************************/
package org.eclipse.team.internal.ui.synchronize;

import java.util.Date;

import org.eclipse.core.runtime.Adapters;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.internal.ui.*;
import org.eclipse.team.ui.synchronize.ISynchronizeParticipant;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.actions.ActionFactory;

import com.ibm.icu.text.DateFormat;
import com.ibm.icu.util.Calendar;

/**
 * Schedule to refresh a subscriber at a specified interval. The schedule can be disabled or enabled
 * and will create the refresh job.
 *
 * @since 3.0
 */
public class SubscriberRefreshSchedule {
	private long refreshInterval = 3600; // 1 hour default
	private Date refreshStart;
	private boolean runOnce = false;

	private boolean enabled = false;

	private RefreshParticipantJob job;

	private IRefreshable refreshable;

	private IRefreshEvent lastRefreshEvent;

	/**
	 * Key for settings in memento
	 */
	private static final String CTX_REFRESHSCHEDULE_INTERVAL = TeamUIPlugin.ID + ".CTX_REFRESHSCHEDULE_INTERVAL"; //$NON-NLS-1$

	/**
	 * Key for schedule in memento
	 */
	private static final String CTX_REFRESHSCHEDULE_ENABLED = TeamUIPlugin.ID + ".CTX_REFRESHSCHEDULE_ENABLED"; //$NON-NLS-1$

	/**
	 * Key for start date in memento
	 */
	private static final String CTX_REFRESHSCHEDULE_START = TeamUIPlugin.ID + ".CTX_REFRESHSCHEDULE_START"; //$NON-NLS-1$

	/**
	 * Key for run once in memento
	 */
	private static final String CTX_REFRESHSCHEDULE_RUNONCE = TeamUIPlugin.ID + ".CTX_REFRESHSCHEDULE_RUNONCE"; //$NON-NLS-1$

	private IRefreshSubscriberListener refreshSubscriberListener = new IRefreshSubscriberListener() {
		@Override
		public void refreshStarted(IRefreshEvent event) {
		}
		@Override
		public ActionFactory.IWorkbenchAction refreshDone(final IRefreshEvent event) {
			if (getRefreshable(event.getParticipant()) == refreshable) {
				lastRefreshEvent = event;
				if(enabled && event.getRefreshType() == IRefreshEvent.SCHEDULED_REFRESH) {
					RefreshUserNotificationPolicy policy = new RefreshUserNotificationPolicy(refreshable.getParticipant());
					policy.refreshDone(event);
				}
			}
			return null;
		}
		private IRefreshable getRefreshable(ISynchronizeParticipant participant) {
			return Adapters.adapt(participant, IRefreshable.class);
		}
	};


	public SubscriberRefreshSchedule(IRefreshable refreshable) {
		this.refreshable = refreshable;
		RefreshParticipantJob.addRefreshListener(refreshSubscriberListener);

//		Calendar cal = Calendar.getInstance();
//		cal.clear();
//		refreshStart = cal.getTime();
	}

	/**
	 * @return Returns the enabled.
	 */
	public boolean isEnabled() {
		return enabled;
	}

	/**
	 * @param enabled The enabled to set.
	 * @param allowedToStart Is the job allowed to start.
	 */
	public void setEnabled(boolean enabled, boolean allowedToStart) {
		boolean wasEnabled = isEnabled();
		this.enabled = enabled;
		if(enabled && ! wasEnabled) {
			if(allowedToStart) {
				startJob();
			}
		} else {
			stopJob();
		}
	}

	/**
	 * @return Returns the refreshInterval in seconds.
	 */
	public long getRefreshInterval() {
		return refreshInterval;
	}

	public ISynchronizeParticipant getParticipant() {
		return refreshable.getParticipant();
	}

	/**
	 * @param refreshInterval The refreshInterval to set.
	 */
	public void setRefreshInterval(long refreshInterval) {
		if(refreshInterval != getRefreshInterval()) {
			stopJob();
			this.refreshInterval = refreshInterval;
			runOnce = false;
			if(isEnabled()) {
				startJob();
			}
		}
	}

	public void startJob() {
		if(job == null) {
			job = refreshable.createJob(getRefreshIntervalAsString());
			job.setUser(false);
		} else if(job.getState() != Job.NONE){
			stopJob();
		}
		job.setRefreshInterval(getRefreshInterval());
		job.setRestartOnCancel(true);
		job.setReschedule(!runOnce);
		if (refreshStart != null) {
			job.schedule(getJobDelay());
		} else {
			job.schedule();
		}
	}

	/**
	 * @return schedule delay in milliseconds
	 */
	private long getJobDelay() {
		Calendar now = Calendar.getInstance();
		Calendar start = Calendar.getInstance();
		start.setTime(refreshStart);
		while (now.after(start)) {
			start.add(Calendar.DATE, 1); // tomorrow
		}
		return start.getTimeInMillis() - now.getTimeInMillis();
	}

	protected void stopJob() {
		if(job != null) {
			job.setRestartOnCancel(false /* don't restart the job */);
			job.setReschedule(false);
			job.cancel();
			job = null;
		}
	}

	public void dispose() {
		stopJob();
		RefreshParticipantJob.removeRefreshListener(refreshSubscriberListener);
	}

	public void saveState(IMemento memento) {
		memento.putString(CTX_REFRESHSCHEDULE_ENABLED, Boolean.toString(enabled));
		memento.putInteger(CTX_REFRESHSCHEDULE_INTERVAL, (int)refreshInterval);
		if (refreshStart != null)
			memento.putString(CTX_REFRESHSCHEDULE_START, Long.toString(refreshStart.getTime()));
		memento.putString(CTX_REFRESHSCHEDULE_RUNONCE, Boolean.toString(runOnce));
	}

	public static SubscriberRefreshSchedule init(IMemento memento, IRefreshable refreshable) {
		SubscriberRefreshSchedule schedule = new SubscriberRefreshSchedule(refreshable);
		if(memento != null) {
			String enabled = memento.getString(CTX_REFRESHSCHEDULE_ENABLED);
			int interval = memento.getInteger(CTX_REFRESHSCHEDULE_INTERVAL).intValue();
			String startString = memento.getString(CTX_REFRESHSCHEDULE_START);
			String runOnce = memento.getString(CTX_REFRESHSCHEDULE_RUNONCE);
			if (startString != null) {
				long start = Long.parseLong(startString);
				schedule.setRefreshStartTime(new Date(start));
			}
			schedule.setRefreshInterval(interval);
			schedule.setRunOnce("true".equals(runOnce) ? true : false); //$NON-NLS-1$
			schedule.setEnabled("true".equals(enabled) ? true : false, false /* don't start job */); //$NON-NLS-1$
		}
		// Use the defaults if a schedule hasn't been saved or can't be found.
		return schedule;
	}

	public static String refreshEventAsString(IRefreshEvent event) {
		if(event == null) {
			return TeamUIMessages.SyncViewPreferencePage_lastRefreshRunNever;
		}
		long stopMills = event.getStopTime();
		StringBuilder text = new StringBuilder();
		if(stopMills <= 0) {
			text.append(TeamUIMessages.SyncViewPreferencePage_lastRefreshRunNever);
		} else {
			Date lastTimeRun = new Date(stopMills);
			text.append(DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(lastTimeRun));
		}
		int changeCount = event.getChangeDescription().getChangeCount();
		if (changeCount == 0) {
			text.append(TeamUIMessages.RefreshSchedule_7);
		} else if (changeCount == 1) {
			text.append(NLS.bind(TeamUIMessages.RefreshSchedule_changesSingular, new String[] { Integer.toString(changeCount) }));
		} else {
			text.append(NLS.bind(TeamUIMessages.RefreshSchedule_changesPlural, new String[] { Integer.toString(changeCount) }));
		}
		return text.toString();
	}

	public IRefreshEvent getLastRefreshEvent() {
		return lastRefreshEvent;
	}

	private String getRefreshIntervalAsString() {
		if (runOnce)
			return TeamUIMessages.RefreshSchedule_16;
		boolean hours = false;
		long seconds = getRefreshInterval();
		if(seconds <= 60) {
			seconds = 60;
		}
		long minutes = seconds / 60;
		if(minutes >= 60) {
			minutes = minutes / 60;
			hours = true;
		}
		String unit;
		if(minutes >= 1) {
			unit = (hours ? TeamUIMessages.RefreshSchedule_9 : TeamUIMessages.RefreshSchedule_10);
		} else {
			unit = (hours ? TeamUIMessages.RefreshSchedule_11 : TeamUIMessages.RefreshSchedule_12);
		}
		return NLS.bind(TeamUIMessages.RefreshSchedule_13, new String[] { Long.toString(minutes), unit });
	}

	public IRefreshable getRefreshable() {
		return refreshable;
	}

	/**
	 * @return The time when the job should start or <code>null</code> when it
	 *         should be run immediately.
	 */
	public Date getRefreshStartTime() {
		return refreshStart;
	}

	public void setRefreshStartTime(Date refreshStart) {
		if(refreshStart==null || refreshStart != getRefreshStartTime()) {
			stopJob();
			this.refreshStart = refreshStart;
			if(isEnabled()) {
				startJob();
			}
		}
	}

	/**
	 * @return Return <code>false</code> if the job should be run again when
	 *         finished, or <code>true</code> otherwise.
	 */
	public boolean getRunOnce() {
		return runOnce;
	}

	public void setRunOnce(boolean runOnce) {
		if (runOnce != getRunOnce()) {
			stopJob();
			this.runOnce = runOnce;
			if (isEnabled()) {
				startJob();
			}
		}
	}
}
