/*
 * Copyright (C) 2006-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
 * and other copyright owners as documented in the project's IP log.
 *
 * This program and the accompanying materials are made available
 * under the terms of the Eclipse Distribution License v1.0 which
 * accompanies this distribution, is reproduced below, and is
 * available at http://www.eclipse.org/org/documents/edl-v10.php
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials provided
 *   with the distribution.
 *
 * - Neither the name of the Eclipse Foundation, Inc. nor the
 *   names of its contributors may be used to endorse or promote
 *   products derived from this software without specific prior
 *   written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.eclipse.jgit.lib;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.MessageFormat;

import org.eclipse.jgit.JGitText;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.ObjectWritingException;

/**
 * Represents a named reference to another Git object of any type.
 */
public class Tag {
	private final Repository objdb;

	private ObjectId tagId;

	private PersonIdent tagger;

	private String message;

	private byte[] raw;

	private String type;

	private String tag;

	private ObjectId objId;

	/**
	 * Construct a new, yet unnamed Tag.
	 *
	 * @param db
	 */
	public Tag(final Repository db) {
		objdb = db;
	}

	/**
	 * Construct a Tag representing an existing with a known name referencing an known object.
	 * This could be either a simple or annotated tag.
	 *
	 * @param db {@link Repository}
	 * @param id target id.
	 * @param refName tag name or null
	 * @param raw data of an annotated tag.
	 */
	public Tag(final Repository db, final ObjectId id, String refName, final byte[] raw) {
		objdb = db;
		if (raw != null) {
			tagId = id;
			objId = ObjectId.fromString(raw, 7);
		} else
			objId = id;
		if (refName != null && refName.startsWith("refs/tags/"))
			refName = refName.substring(10);
		tag = refName;
		this.raw = raw;
	}

	/**
	 * @return tagger of a annotated tag or null
	 */
	public PersonIdent getAuthor() {
		decode();
		return tagger;
	}

	/**
	 * Set author of an annotated tag.
	 * @param a author identifier as a {@link PersonIdent}
	 */
	public void setAuthor(final PersonIdent a) {
		tagger = a;
	}

	/**
	 * @return comment of an annotated tag, or null
	 */
	public String getMessage() {
		decode();
		return message;
	}

	private void decode() {
		// FIXME: handle I/O errors
		if (raw != null) {
			try {
				BufferedReader br = new BufferedReader(new InputStreamReader(
						new ByteArrayInputStream(raw)));
				String n = br.readLine();
				if (n == null || !n.startsWith("object ")) {
					throw new CorruptObjectException(tagId, JGitText.get().corruptObjectNoObject);
				}
				objId = ObjectId.fromString(n.substring(7));
				n = br.readLine();
				if (n == null || !n.startsWith("type ")) {
					throw new CorruptObjectException(tagId, JGitText.get().corruptObjectNoType);
				}
				type = n.substring("type ".length());
				n = br.readLine();

				if (n == null || !n.startsWith("tag ")) {
					throw new CorruptObjectException(tagId, JGitText.get().corruptObjectNoTagName);
				}
				tag = n.substring("tag ".length());
				n = br.readLine();

				// We should see a "tagger" header here, but some repos have tags
				// without it.
				if (n == null)
					throw new CorruptObjectException(tagId, JGitText.get().corruptObjectNoTaggerHeader);

				if (n.length()>0)
					if (n.startsWith("tagger "))
						tagger = new PersonIdent(n.substring("tagger ".length()));
					else
						throw new CorruptObjectException(tagId, JGitText.get().corruptObjectNoTaggerBadHeader);

				// Message should start with an empty line, but
				StringBuilder tempMessage = new StringBuilder();
				char[] readBuf = new char[2048];
				int readLen;
				while ((readLen = br.read(readBuf)) > 0) {
					tempMessage.append(readBuf, 0, readLen);
				}
				message = tempMessage.toString();
				if (message.startsWith("\n"))
					message = message.substring(1);
			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				raw = null;
			}
		}
	}

	/**
	 * Set the message of an annotated tag
	 * @param m
	 */
	public void setMessage(final String m) {
		message = m;
	}

	/**
	 * Store a tag.
	 * If author, message or type is set make the tag an annotated tag.
	 *
	 * @throws IOException
	 */
	public void tag() throws IOException {
		if (getTagId() != null)
			throw new IllegalStateException(MessageFormat.format(JGitText.get().illegalStateExists, getTagId()));
		final ObjectId id;
		final RefUpdate ru;

		if (tagger!=null || message!=null || type!=null) {
			ObjectInserter odi = objdb.newObjectInserter();
			try {
				id = odi.insert(Constants.OBJ_TAG, odi.format(this));
				odi.flush();
				setTagId(id);
			} finally {
				odi.release();
			}
		} else {
			id = objId;
		}

		ru = objdb.updateRef(Constants.R_TAGS + getTag());
		ru.setNewObjectId(id);
		ru.setRefLogMessage("tagged " + getTag(), false);
		if (ru.forceUpdate() == RefUpdate.Result.LOCK_FAILURE)
			throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToLockTag, getTag()));
	}

	public String toString() {
		return "tag[" + getTag() + getType() + getObjId() + " " + getAuthor() + "]";
	}

	/**
	 * @return SHA-1 of this tag (if annotated and stored).
	 */
	public ObjectId getTagId() {
		return tagId;
	}

	/**
	 * Set SHA-1 of this tag. Used by writer.
	 *
	 * @param tagId
	 */
	public void setTagId(ObjectId tagId) {
		this.tagId = tagId;
	}

	/**
	 * @return creator of this tag.
	 */
	public PersonIdent getTagger() {
		decode();
		return tagger;
	}

	/**
	 * Set the creator of this tag
	 *
	 * @param tagger
	 */
	public void setTagger(PersonIdent tagger) {
		this.tagger = tagger;
	}

	/**
	 * @return tag target type
	 */
	public String getType() {
		decode();
		return type;
	}

	/**
	 * Set tag target type
	 * @param type
	 */
	public void setType(String type) {
		this.type = type;
	}

	/**
	 * @return name of the tag.
	 */
	public String getTag() {
		return tag;
	}

	/**
	 * Set the name of this tag.
	 *
	 * @param tag
	 */
	public void setTag(String tag) {
		this.tag = tag;
	}

	/**
	 * @return the SHA'1 of the object this tag refers to.
	 */
	public ObjectId getObjId() {
		return objId;
	}

	/**
	 * Set the id of the object this tag refers to.
	 *
	 * @param objId
	 */
	public void setObjId(ObjectId objId) {
		this.objId = objId;
	}
}
