/*
 * Copyright (C) 2008-2009, Johannes E. Schindelin <johannes.schindelin@gmx.de> and others
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Distribution License v. 1.0 which is available at
 * https://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

package org.eclipse.jgit.diff;

/**
 * A modified region detected between two versions of roughly the same content.
 * <p>
 * An edit covers the modified region only. It does not cover a common region.
 * <p>
 * Regions should be specified using 0 based notation, so add 1 to the start and
 * end marks for line numbers in a file.
 * <p>
 * An edit where {@code beginA == endA && beginB < endB} is an insert edit, that
 * is sequence B inserted the elements in region <code>[beginB, endB)</code> at
 * <code>beginA</code>.
 * <p>
 * An edit where {@code beginA < endA && beginB == endB} is a delete edit, that
 * is sequence B has removed the elements between <code>[beginA, endA)</code>.
 * <p>
 * An edit where {@code beginA < endA && beginB < endB} is a replace edit, that
 * is sequence B has replaced the range of elements between
 * <code>[beginA, endA)</code> with those found in <code>[beginB, endB)</code>.
 */
public class Edit {
	/** Type of edit */
	public enum Type {
		/** Sequence B has inserted the region. */
		INSERT,

		/** Sequence B has removed the region. */
		DELETE,

		/** Sequence B has replaced the region with different content. */
		REPLACE,

		/** Sequence A and B have zero length, describing nothing. */
		EMPTY;
	}

	int beginA;

	int endA;

	int beginB;

	int endB;

	/**
	 * Create a new empty edit.
	 *
	 * @param as
	 *            beginA: start and end of region in sequence A; 0 based.
	 * @param bs
	 *            beginB: start and end of region in sequence B; 0 based.
	 */
	public Edit(int as, int bs) {
		this(as, as, bs, bs);
	}

	/**
	 * Create a new edit.
	 *
	 * @param as
	 *            beginA: start of region in sequence A; 0 based.
	 * @param ae
	 *            endA: end of region in sequence A; must be &gt;= as.
	 * @param bs
	 *            beginB: start of region in sequence B; 0 based.
	 * @param be
	 *            endB: end of region in sequence B; must be &gt; = bs.
	 */
	public Edit(int as, int ae, int bs, int be) {
		beginA = as;
		endA = ae;

		beginB = bs;
		endB = be;
	}

	/**
	 * Get type
	 *
	 * @return the type of this region
	 */
	public final Type getType() {
		if (beginA < endA) {
			if (beginB < endB) {
				return Type.REPLACE;
			}
			return Type.DELETE;

		}
		if (beginB < endB) {
			return Type.INSERT;
		}
		// beginB == endB)
		return Type.EMPTY;
	}

	/**
	 * Whether edit is empty
	 *
	 * @return {@code true} if the edit is empty (lengths of both a and b is
	 *         zero)
	 */
	public final boolean isEmpty() {
		return beginA == endA && beginB == endB;
	}

	/**
	 * Get start point in sequence A
	 *
	 * @return start point in sequence A
	 */
	public final int getBeginA() {
		return beginA;
	}

	/**
	 * Get end point in sequence A
	 *
	 * @return end point in sequence A
	 */
	public final int getEndA() {
		return endA;
	}

	/**
	 * Get start point in sequence B
	 *
	 * @return start point in sequence B
	 */
	public final int getBeginB() {
		return beginB;
	}

	/**
	 * Get end point in sequence B
	 *
	 * @return end point in sequence B
	 */
	public final int getEndB() {
		return endB;
	}

	/**
	 * Get length of the region in A
	 *
	 * @return length of the region in A
	 */
	public final int getLengthA() {
		return endA - beginA;
	}

	/**
	 * Get length of the region in B
	 *
	 * @return return length of the region in B
	 */
	public final int getLengthB() {
		return endB - beginB;
	}

	/**
	 * Move the edit region by the specified amount.
	 *
	 * @param amount
	 *            the region is shifted by this amount, and can be positive or
	 *            negative.
	 * @since 4.8
	 */
	public final void shift(int amount) {
		beginA += amount;
		endA += amount;
		beginB += amount;
		endB += amount;
	}

	/**
	 * Construct a new edit representing the region before cut.
	 *
	 * @param cut
	 *            the cut point. The beginning A and B points are used as the
	 *            end points of the returned edit.
	 * @return an edit representing the slice of {@code this} edit that occurs
	 *         before {@code cut} starts.
	 */
	public final Edit before(Edit cut) {
		return new Edit(beginA, cut.beginA, beginB, cut.beginB);
	}

	/**
	 * Construct a new edit representing the region after cut.
	 *
	 * @param cut
	 *            the cut point. The ending A and B points are used as the
	 *            starting points of the returned edit.
	 * @return an edit representing the slice of {@code this} edit that occurs
	 *         after {@code cut} ends.
	 */
	public final Edit after(Edit cut) {
		return new Edit(cut.endA, endA, cut.endB, endB);
	}

	/**
	 * Increase {@link #getEndA()} by 1.
	 */
	public void extendA() {
		endA++;
	}

	/**
	 * Increase {@link #getEndB()} by 1.
	 */
	public void extendB() {
		endB++;
	}

	/**
	 * Swap A and B, so the edit goes the other direction.
	 */
	public void swap() {
		final int sBegin = beginA;
		final int sEnd = endA;

		beginA = beginB;
		endA = endB;

		beginB = sBegin;
		endB = sEnd;
	}

	@Override
	public int hashCode() {
		return beginA ^ endA;
	}

	@Override
	public boolean equals(Object o) {
		if (o instanceof Edit) {
			final Edit e = (Edit) o;
			return this.beginA == e.beginA && this.endA == e.endA
					&& this.beginB == e.beginB && this.endB == e.endB;
		}
		return false;
	}

	@SuppressWarnings("nls")
	@Override
	public String toString() {
		final Type t = getType();
		return t + "(" + beginA + "-" + endA + "," + beginB + "-" + endB + ")";
	}
}
