/*
 * Copyright 2011 gitblit.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gitblit.utils;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Type;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.TreeSet;

import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;

import com.gitblit.GitBlit;
import com.gitblit.Keys;
import com.gitblit.models.Activity;
import com.gitblit.models.GravatarProfile;
import com.gitblit.models.RefModel;
import com.gitblit.models.RepositoryCommit;
import com.gitblit.models.RepositoryModel;
import com.google.gson.reflect.TypeToken;

/**
 * Utility class for building activity information from repositories.
 * 
 * @author James Moger
 * 
 */
public class ActivityUtils {

	/**
	 * Gets the recent activity from the repositories for the last daysBack days
	 * on the specified branch.
	 * 
	 * @param models
	 *            the list of repositories to query
	 * @param daysBack
	 *            the number of days back from Now to collect
	 * @param objectId
	 *            the branch to retrieve. If this value is null or empty all
	 *            branches are queried.
	 * @param timezone
	 *            the timezone for aggregating commits
	 * @return
	 */
	public static List<Activity> getRecentActivity(List<RepositoryModel> models, int daysBack,
			String objectId, TimeZone timezone) {

		// Activity panel shows last daysBack of activity across all
		// repositories.
		Date thresholdDate = new Date(System.currentTimeMillis() - daysBack * TimeUtils.ONEDAY);

		// Build a map of DailyActivity from the available repositories for the
		// specified threshold date.
		DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
		df.setTimeZone(timezone);
		Calendar cal = Calendar.getInstance();
		cal.setTimeZone(timezone);
		
		// aggregate author exclusions
		Set<String> authorExclusions = new TreeSet<String>();
		authorExclusions.addAll(GitBlit.getStrings(Keys.web.metricAuthorExclusions));
		for (RepositoryModel model : models) {
			if (!ArrayUtils.isEmpty(model.metricAuthorExclusions)) {
				authorExclusions.addAll(model.metricAuthorExclusions);
			}
		}

		Map<String, Activity> activity = new HashMap<String, Activity>();
		for (RepositoryModel model : models) {
			if (!model.isShowActivity()) {
				// skip this repository
				continue;
			}
			if (model.hasCommits && model.lastChange.after(thresholdDate)) {
				if (model.isCollectingGarbage) {
					continue;
				}
				Repository repository = GitBlit.self()
						.getRepository(model.name);
				List<String> branches = new ArrayList<String>();
				if (StringUtils.isEmpty(objectId)) {
					for (RefModel local : JGitUtils.getLocalBranches(
							repository, true, -1)) {
			        	if (!local.getDate().after(thresholdDate)) {
							// branch not recently updated
			        		continue;
			        	}
						branches.add(local.getName());
					}
				} else {
					branches.add(objectId);
				}

				for (String branch : branches) {
					String shortName = branch;
					if (shortName.startsWith(Constants.R_HEADS)) {
						shortName = shortName.substring(Constants.R_HEADS.length());
					}
					List<RepositoryCommit> commits = CommitCache.instance().getCommits(model.name, repository, branch, thresholdDate);
					if (model.maxActivityCommits > 0 && commits.size() > model.maxActivityCommits) {
						// trim commits to maximum count
						commits = commits.subList(0,  model.maxActivityCommits);
					}
					for (RepositoryCommit commit : commits) {						
						Date date = commit.getCommitDate();
						String dateStr = df.format(date);
						if (!activity.containsKey(dateStr)) {
							// Normalize the date to midnight
							cal.setTime(date);
							cal.set(Calendar.HOUR_OF_DAY, 0);
							cal.set(Calendar.MINUTE, 0);
							cal.set(Calendar.SECOND, 0);
							cal.set(Calendar.MILLISECOND, 0);
							Activity a = new Activity(cal.getTime());
							a.excludeAuthors(authorExclusions);
							activity.put(dateStr, a);
						}
						activity.get(dateStr).addCommit(commit);
					}
				}
				
				// close the repository
				repository.close();
			}
		}

		List<Activity> recentActivity = new ArrayList<Activity>(activity.values());
		return recentActivity;
	}

	/**
	 * Returns the Gravatar profile, if available, for the specified email
	 * address.
	 * 
	 * @param emailaddress
	 * @return a Gravatar Profile
	 * @throws IOException
	 */
	public static GravatarProfile getGravatarProfileFromAddress(String emailaddress)
			throws IOException {
		return getGravatarProfile(StringUtils.getMD5(emailaddress.toLowerCase()));
	}

	/**
	 * Creates a Gravatar thumbnail url from the specified email address.
	 * 
	 * @param email
	 *            address to query Gravatar
	 * @param width
	 *            size of thumbnail. if width <= 0, the default of 50 is used.
	 * @return
	 */
	public static String getGravatarIdenticonUrl(String email, int width) {
		if (width <= 0) {
			width = 50;
		}
		String emailHash = StringUtils.getMD5(email);
		String url = MessageFormat.format(
				"https://www.gravatar.com/avatar/{0}?s={1,number,0}&d=identicon", emailHash, width);
		return url;
	}
	
	/**
	 * Creates a Gravatar thumbnail url from the specified email address.
	 * 
	 * @param email
	 *            address to query Gravatar
	 * @param width
	 *            size of thumbnail. if width <= 0, the default of 50 is used.
	 * @return
	 */
	public static String getGravatarThumbnailUrl(String email, int width) {
		if (width <= 0) {
			width = 50;
		}
		String emailHash = StringUtils.getMD5(email);
		String url = MessageFormat.format(
				"https://www.gravatar.com/avatar/{0}?s={1,number,0}&d=mm", emailHash, width);
		return url;
	}

	/**
	 * Returns the Gravatar profile, if available, for the specified hashcode.
	 * address.
	 * 
	 * @param hash
	 *            the hash of the email address
	 * @return a Gravatar Profile
	 * @throws IOException
	 */
	public static GravatarProfile getGravatarProfile(String hash) throws IOException {
		String url = MessageFormat.format("https://www.gravatar.com/{0}.json", hash);
		// Gravatar has a complex json structure
		Type profileType = new TypeToken<Map<String, List<GravatarProfile>>>() {
		}.getType();
		Map<String, List<GravatarProfile>> profiles = null;
		try {
			profiles = JsonUtils.retrieveJson(url, profileType);
		} catch (FileNotFoundException e) {
		}
		if (profiles == null || profiles.size() == 0) {
			return null;
		}
		// due to the complex json structure we need to pull out the profile
		// from a list 2 levels deep
		GravatarProfile profile = profiles.values().iterator().next().get(0);
		return profile;
	}
}
