/*******************************************************************************
 * Copyright (c) 2000, 2006 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.jdt.internal.compiler.codegen;

import java.util.Arrays;

import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;

public class BranchLabel extends Label {
	
	private int[] forwardReferences = new int[10]; // Add an overflow check here.
	private int forwardReferenceCount = 0;
	BranchLabel delegate; //
	
	// Label tagbits
	public int tagBits;
	public final static int WIDE = 1;
	public final static int USED = 2;
	
public BranchLabel() {
	// for creating labels ahead of code generation
}

/**
 * @param codeStream org.eclipse.jdt.internal.compiler.codegen.CodeStream
 */
public BranchLabel(CodeStream codeStream) {
	super(codeStream);
}

/**
 * Add a forward refrence for the array.
 */
void addForwardReference(int pos) {
	if (this.delegate != null) {
		this.delegate.addForwardReference(pos);
		return;
	}
	final int count = this.forwardReferenceCount;
	if (count >= 1) {
		int previousValue = this.forwardReferences[count - 1];
		if (previousValue < pos) {
			int length;
			if (count >= (length = this.forwardReferences.length))
				System.arraycopy(this.forwardReferences, 0, (this.forwardReferences = new int[2*length]), 0, length);
			this.forwardReferences[this.forwardReferenceCount++] = pos;			
		} else if (previousValue > pos) {
			int[] refs = this.forwardReferences;
			// check for duplicates
			for (int i = 0, max = this.forwardReferenceCount; i < max; i++) {
				if (refs[i] == pos) return; // already recorded
			}
			int length;
			if (count >= (length = refs.length))
				System.arraycopy(refs, 0, (this.forwardReferences = new int[2*length]), 0, length);
			this.forwardReferences[this.forwardReferenceCount++] = pos;
			Arrays.sort(this.forwardReferences, 0, this.forwardReferenceCount);
		}
	} else {
		int length;
		if (count >= (length = this.forwardReferences.length))
			System.arraycopy(this.forwardReferences, 0, (this.forwardReferences = new int[2*length]), 0, length);
		this.forwardReferences[this.forwardReferenceCount++] = pos;
	}
}

/**
 * Makes the current label inline all references to the other label
 */
public void becomeDelegateFor(BranchLabel otherLabel) {
	// other label is delegating to receiver from now on
	otherLabel.delegate = this;
	
	// all existing forward refs to other label are inlined into current label
	final int otherCount = otherLabel.forwardReferenceCount;
	if (otherCount == 0) return;
	// need to merge the two sorted arrays of forward references
	int[] mergedForwardReferences = new int[this.forwardReferenceCount + otherCount];
	int indexInMerge = 0;
	int j = 0;
	int i = 0;
	int max = this.forwardReferenceCount;
	int max2 = otherLabel.forwardReferenceCount;
	loop1 : for (; i < max; i++) {
		final int value1 = this.forwardReferences[i];
		for (; j < max2; j++) {
			final int value2 = otherLabel.forwardReferences[j];
			if (value1 < value2) {
				mergedForwardReferences[indexInMerge++] = value1;
				continue loop1;
			} else if (value1 == value2) {
				mergedForwardReferences[indexInMerge++] = value1;
				j++;
				continue loop1;
			} else {
				mergedForwardReferences[indexInMerge++] = value2;
			}
		}
		mergedForwardReferences[indexInMerge++] = value1;
	}
	for (; j < max2; j++) {
		mergedForwardReferences[indexInMerge++] = otherLabel.forwardReferences[j];
	}
	this.forwardReferences = mergedForwardReferences;
	this.forwardReferenceCount = indexInMerge;
}

/*
* Put down  a reference to the array at the location in the codestream.
*/
void branch() {
	this.tagBits |= BranchLabel.USED;
	if (this.delegate != null) {
		this.delegate.branch();
		return;
	}
	if (this.position == Label.POS_NOT_SET) {
		addForwardReference(this.codeStream.position);
		// Leave two bytes free to generate the jump afterwards
		this.codeStream.position += 2;
		this.codeStream.classFileOffset += 2;
	} else {
		/*
		 * Position is set. Write it if it is not a wide branch.
		 */
		this.codeStream.writePosition(this);
	}
}

/*
* No support for wide branches yet
*/
void branchWide() {
	this.tagBits |= BranchLabel.USED;
	if (this.delegate != null) {
		this.delegate.branchWide();
		return;
	}
	if (this.position == Label.POS_NOT_SET) {
		addForwardReference(this.codeStream.position);
		// Leave 4 bytes free to generate the jump offset afterwards
		this.tagBits |= BranchLabel.WIDE;
		this.codeStream.position += 4;
		this.codeStream.classFileOffset += 4;
	} else { //Position is set. Write it!
		this.codeStream.writeWidePosition(this);
	}
}

public int forwardReferenceCount() {
	if (this.delegate != null) this.delegate.forwardReferenceCount();
	return forwardReferenceCount;
}
public int[] forwardReferences() {
	if (this.delegate != null) this.delegate.forwardReferences();
	return forwardReferences;
}
public void initialize(CodeStream stream) {
    this.codeStream = stream;
   	this.position = Label.POS_NOT_SET;
	this.forwardReferenceCount = 0;
	this.delegate = null;
}
public boolean isCaseLabel() {
	return false;
}
public boolean isStandardLabel(){
	return true;
}

/*
* Place the label. If we have forward references resolve them.
*/
public void place() { // Currently lacking wide support.
	if (CodeStream.DEBUG) System.out.println("\t\t\t\t<place at: "+this.codeStream.position+" - "+ this); //$NON-NLS-1$ //$NON-NLS-2$
//	if ((this.tagBits & USED) == 0 && this.forwardReferenceCount == 0) {
//		return;
//	}

	//TODO how can position be set already ? cannot place more than once
	if (this.position == Label.POS_NOT_SET) {
		if ((this.tagBits & BranchLabel.USED) != 0 || this.forwardReferenceCount != 0) {
			this.position = this.codeStream.getPosition();
		} else {
			this.position = this.codeStream.position;
		}
		this.codeStream.addLabel(this);
		int oldPosition = this.position;
		boolean isOptimizedBranch = false;
		if (this.forwardReferenceCount != 0) {
			isOptimizedBranch = (this.forwardReferences[this.forwardReferenceCount - 1] + 2 == this.position) && (this.codeStream.bCodeStream[this.codeStream.classFileOffset - 3] == Opcodes.OPC_goto);
			if (isOptimizedBranch) {
				if (this.codeStream.lastAbruptCompletion == this.position) {
					this.codeStream.lastAbruptCompletion = -1;
				}
				this.codeStream.position = (this.position -= 3);
				this.codeStream.classFileOffset -= 3;
				this.forwardReferenceCount--;
				// also update the PCs in the related debug attributes
				/* OLD CODE
					int index = codeStream.pcToSourceMapSize - 1;
						while ((index >= 0) && (codeStream.pcToSourceMap[index][1] == oldPosition)) {
							codeStream.pcToSourceMap[index--][1] = position;
						}
				*/
				// Beginning of new code
				if (this.codeStream.lastEntryPC == oldPosition) {
					this.codeStream.lastEntryPC = this.position;
				}
				// end of new code
				if ((this.codeStream.generateAttributes & (ClassFileConstants.ATTR_VARS | ClassFileConstants.ATTR_STACK_MAP)) != 0) {
					LocalVariableBinding locals[] = this.codeStream.locals;
					for (int i = 0, max = locals.length; i < max; i++) {
						LocalVariableBinding local = locals[i];
						if ((local != null) && (local.initializationCount > 0)) {
							if (local.initializationPCs[((local.initializationCount - 1) << 1) + 1] == oldPosition) {
								// we want to prevent interval of size 0 to have a negative size.
								// see PR 1GIRQLA: ITPJCORE:ALL - ClassFormatError for local variable attribute
								local.initializationPCs[((local.initializationCount - 1) << 1) + 1] = this.position;
							}
							if (local.initializationPCs[(local.initializationCount - 1) << 1] == oldPosition) {
								local.initializationPCs[(local.initializationCount - 1) << 1] = this.position;
							}
						}
					}
				}
			}
		}
		for (int i = 0; i < this.forwardReferenceCount; i++) {
			this.codeStream.writePosition(this, this.forwardReferences[i]);
		}
		// For all labels placed at that position we check if we need to rewrite the jump
		// offset. It is the case each time a label had a forward reference to the current position.
		// Like we change the current position, we have to change the jump offset. See 1F4IRD9 for more details.
		if (isOptimizedBranch) {
			this.codeStream.optimizeBranch(oldPosition, this);
		}
	}
}

/**
 * Print out the receiver
 */
public String toString() {
	String basic = getClass().getName();
	basic = basic.substring(basic.lastIndexOf('.')+1);
	StringBuffer buffer = new StringBuffer(basic); 
	buffer.append('@').append(Integer.toHexString(hashCode()));
	buffer.append("(position=").append(this.position); //$NON-NLS-1$
	if (this.delegate != null) buffer.append("delegate=").append(this.delegate); //$NON-NLS-1$
	buffer.append(", forwards = ["); //$NON-NLS-1$
	for (int i = 0; i < this.forwardReferenceCount - 1; i++)
		buffer.append(this.forwardReferences[i] + ", "); //$NON-NLS-1$
	if (this.forwardReferenceCount >= 1)
		buffer.append(this.forwardReferences[this.forwardReferenceCount-1]);
	buffer.append("] )"); //$NON-NLS-1$
	return buffer.toString();
}
}
