/*******************************************************************************
 * Copyright (c) 2011, 2014 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
 *******************************************************************************/
package org.eclipse.orion.server.git.objects;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Comparator;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevTag;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.orion.server.core.ProtocolConstants;
import org.eclipse.orion.server.core.resources.Property;
import org.eclipse.orion.server.core.resources.ResourceShape;
import org.eclipse.orion.server.core.resources.annotations.PropertyDescription;
import org.eclipse.orion.server.core.resources.annotations.ResourceDescription;
import org.eclipse.orion.server.git.GitConstants;
import org.eclipse.orion.server.git.servlets.GitServlet;
import org.eclipse.orion.server.git.servlets.GitUtils;
import org.json.JSONException;
import org.json.JSONObject;

@ResourceDescription(type = Tag.TYPE)
public class Tag extends GitObject {

	private enum TagType {
		LIGHTWEIGHT, ANNOTATED;

		public static TagType valueOf(Tag t) {
			t.parseTag();
			if (t.tag != null)
				return ANNOTATED;
			else if (t.ref != null)
				return LIGHTWEIGHT;
			throw new IllegalArgumentException("Illegal tag type: " + t);
		}
	}

	public static final String RESOURCE = "tag"; //$NON-NLS-1$
	public static final String TYPE = "Tag"; //$NON-NLS-1$
	public static final Comparator<Tag> COMPARATOR = new Comparator<Tag>() {
		@Override
		public int compare(Tag o1, Tag o2) {
			return o1.getTime() < o2.getTime() ? 1 : (o1.getTime() > o2.getTime() ? -1 : o2.getName(false, false).compareTo(o1.getName(false, false)));
		}
	};

	private static final ResourceShape DEFAULT_RESOURCE_SHAPE = new ResourceShape();
	{
		Property[] defaultProperties = new Property[] { //
		new Property(ProtocolConstants.KEY_LOCATION), // super
				new Property(GitConstants.KEY_CLONE), // super
				new Property(ProtocolConstants.KEY_NAME), //
				new Property(GitConstants.KEY_COMMIT), //
				new Property(GitConstants.KEY_TREE), //
				new Property(ProtocolConstants.KEY_LOCAL_TIMESTAMP), //
				new Property(GitConstants.KEY_TAG_TYPE), //
				new Property(ProtocolConstants.KEY_FULL_NAME) };
		DEFAULT_RESOURCE_SHAPE.setProperties(defaultProperties);
	}

	private RevTag tag;
	private Ref ref;
	private URI tagLocation;
	private URI commitLocation;
	private RevCommit commit;

	public Tag(URI cloneLocation, Repository db, Ref ref) throws IOException, CoreException {
		super(cloneLocation, db);
		this.ref = ref;
	}

	@Override
	public JSONObject toJSON() throws JSONException, URISyntaxException, IOException, CoreException {
		return jsonSerializer.serialize(this, DEFAULT_RESOURCE_SHAPE);
	}

	@PropertyDescription(name = ProtocolConstants.KEY_NAME)
	private String getName() {
		return getName(false, false);
	}

	@PropertyDescription(name = GitConstants.KEY_TAG_TYPE)
	private TagType getTagType() {
		return TagType.valueOf(this);
	}

	@PropertyDescription(name = ProtocolConstants.KEY_FULL_NAME)
	private String getFullName() {
		return getName(true, false);
	}

	private RevTag parseTag() {
		parseCommit();
		return this.tag;
	}

	private RevCommit parseCommit() {
		if (this.commit == null) {
			RevWalk rw = new RevWalk(db);
			RevObject any;
			try {
				any = rw.parseAny(this.ref.getObjectId());
				if (any instanceof RevTag) {
					this.tag = (RevTag) any;
					RevObject o = rw.peel(any);
					if (o instanceof RevCommit) {
						this.commit = (RevCommit) rw.peel(any);
					}
				} else if (any instanceof RevCommit) {
					this.commit = (RevCommit) any;
				}
			} catch (IOException e) {
			} finally {
				rw.dispose();
			}
		}
		return commit;
	}

	public JSONObject toJSON(JSONObject log) throws JSONException, URISyntaxException, IOException, CoreException {
		JSONObject tagJSON = this.toJSON();
		tagJSON.put(GitConstants.KEY_TAG_COMMIT, log);
		return tagJSON;
	}

	private String getName(boolean fullName, boolean encode) {
		String name = null;
		if (tag != null)
			name = fullName ? Constants.R_TAGS + tag.getTagName() : tag.getTagName();
		if (ref != null)
			name = fullName ? ref.getName() : Repository.shortenRefName(ref.getName());
		if (name == null)
			return null;
		if (encode)
			name = GitUtils.encode(name);
		return name;
	}

	@Override
	protected URI getLocation() throws URISyntaxException {
		if (tagLocation == null) {
			IPath p = new Path(cloneLocation.getPath());
			p = p.uptoSegment(1).append(RESOURCE).append(getName(false, true)).addTrailingSeparator().append(p.removeFirstSegments(2));
			tagLocation = new URI(cloneLocation.getScheme(), cloneLocation.getUserInfo(), cloneLocation.getHost(), cloneLocation.getPort(), p.toString(),
					cloneLocation.getQuery(), cloneLocation.getFragment());
		}
		return tagLocation;
	}

	@PropertyDescription(name = GitConstants.KEY_COMMIT)
	private URI getCommitLocation() throws URISyntaxException {
		if (commitLocation == null && this.commit != null) {
			IPath p = new Path(cloneLocation.getPath());
			p = p.uptoSegment(1).append(Commit.RESOURCE).append(parseCommit().getName()).addTrailingSeparator().append(p.removeFirstSegments(2));
			commitLocation = new URI(cloneLocation.getScheme(), cloneLocation.getUserInfo(), cloneLocation.getHost(), cloneLocation.getPort(), p.toString(),
					cloneLocation.getQuery(), cloneLocation.getFragment());
		}
		return commitLocation;
	}

	@PropertyDescription(name = GitConstants.KEY_TREE)
	private URI getTreeLocation() throws URISyntaxException {
		return createTreeLocation(null);
	}

	private URI createTreeLocation(String path) throws URISyntaxException {
		// remove /gitapi/clone from the start of path
		IPath clonePath = new Path(cloneLocation.getPath()).removeFirstSegments(2);

		IPath result = new Path(GitServlet.GIT_URI).append(Tree.RESOURCE).append(clonePath).append(GitUtils.encode(this.getName()));
		if (path != null) {
			result.append(path);
		}
		return new URI(cloneLocation.getScheme(), cloneLocation.getUserInfo(), cloneLocation.getHost(), cloneLocation.getPort(), result.makeAbsolute()
				.toString(), cloneLocation.getQuery(), cloneLocation.getFragment());
	}

	@PropertyDescription(name = ProtocolConstants.KEY_LOCAL_TIMESTAMP)
	private long getTime() {
		RevCommit c = parseCommit();
		if (c != null)
			return (long) c.getCommitTime() * 1000;
		return 0;
	}

	public String getRevCommitName() {
		RevCommit c = parseCommit();
		if (c == null) {
			return null;
		} else {
			return parseCommit().getName();
		}
	}
}