/*
 *
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You 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 macromedia.abc;

import static macromedia.asc.embedding.avmplus.ActionBlockConstants.*;
import macromedia.asc.util.IntegerPool;
import macromedia.asc.util.IntList;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.*;

/**
 * abc encoder. If the encoder is provided with multiple constant pools, it will use do merging.
 *
 * @author Clement Wong
 */
public class Encoder implements Visitor
{
	public Encoder(int majorVersion, int minorVersion)
	{
		this.majorVersion = majorVersion;
		this.minorVersion = minorVersion;

		poolIndex = 0;
		peepHole = false;
		disableDebugging = false;
		removeMetadata = false;

    }

	private ConstantPool pool;
	private int majorVersion, minorVersion;
	private int poolIndex, opcodePass, exPass;
	private boolean disableDebugging, removeMetadata, peepHole;

    private HashSet<String> keep_metadata = new HashSet<String>();

	private BytecodeBuffer2 methodInfo;
	private ByteArrayPool2 metadataInfo;
	private BytecodeBuffer2 classInfo;
	private BytecodeBuffer2 scriptInfo;
	private BytecodeBuffer2 methodBodies;
	private BytecodeBuffer3 opcodes;
	private BytecodeBuffer exceptions;

	private BytecodeBuffer currentBuffer;

    private ConstantPool[] pools;

	public void enablePeepHole()
	{
		peepHole = true;
	}

	public void disableDebugging()
	{
		disableDebugging = true;
        pool.history.disableDebugging();
	}

	public void removeMetadata()
	{
		removeMetadata = true;
	}

    public void addMetadataToKeep(String meta_name)
    {
        keep_metadata.add(meta_name);
    }

	public void addConstantPools(ConstantPool[] pools)
	{
        this.pools = pools;
		pool = ConstantPool.merge(pools);
        if( disableDebugging )
            pool.history.disableDebugging();
	}
	
	public void test()
	{
		// C: testing only...
		for (int i = 0, size = pools.length; i < size; i++)
		{
			/*
			for (int j = 1, count = pools[i].intpositions.length; j < count; j++)
			{
				int original = pools[i].getInt(j);
				int real = pool.getInt(pool.history.getIndex(i, 0, j));

				if (original != real)
				{
					throw new DecoderException("Error (int) in constant pooling merging...");
				}
			}

			for (int j = 1, count = pools[i].uintpositions.length; j < count; j++)
			{
				long original = pools[i].getLong(j);
				long real = pool.getLong(pool.history.getIndex(i, 1, j));

				if (original != real)
				{
					throw new DecoderException("Error (uint) in constant pooling merging...");
				}
			}

			for (int j = 1, count = pools[i].doublepositions.length; j < count; j++)
			{
				double original = pools[i].getDouble(j);
				double real = pool.getDouble(pool.history.getIndex(i, 2, j));

				if (original != real)
				{
					throw new DecoderException("Error (double) in constant pooling merging...");
				}
			}

			for (int j = 1, count = pools[i].strpositions.length; j < count; j++)
			{
				String original = pools[i].getString(j);
				System.out.println("(" + i + "," + j + ")-->(0," + pool.history.getIndex(i, 3, j) + "): " + original);
			}

			for (int j = 1, count = pools[i].nspositions.length; j < count; j++)
			{
				String original = pools[i].getNamespaceName(j);
				String real = pool.getNamespaceName(pool.history.getIndex(i, 4, j));

				if (!original.equals(real))
				{
					throw new DecoderException("Error (namespace) in constant pooling merging...");
				}
			}

			for (int j = 1, count = pools[i].nsspositions.length; j < count; j++)
			{
				String[] original = pools[i].getNamespaceSet(j);
				String[] real = pool.getNamespaceSet(pool.history.getIndex(i, 5, j));

				if (original.length != real.length)
				{
					throw new DecoderException("Error (namespace set) in constant pooling merging...");
				}
				else
				{
					for (int k = 0, len = original.length; k < len; k++)
					{
						if (!original[k].equals(real[k]))
						{
							throw new DecoderException("Error (namespace set) in constant pooling merging...");
						}
					}
				}
			}

			for (int j = 1, count = pools[i].mnpositions.length; j < count; j++)
			{
				Object original = pools[i].getGeneralMultiname(j);
				Object real = pool.getGeneralMultiname(pool.history.getIndex(i, 6, j));

				if (original.getClass() != real.getClass())
				{
					throw new DecoderException("Error (multiname) in constant pooling merging...");
				}

				if (original instanceof QName && !((QName) original).equals(real))
				{
					throw new DecoderException("Error (multiname) in constant pooling merging...");
				}

				if (original instanceof MultiName && !((MultiName) original).equals(real))
				{
					throw new DecoderException("Error (multiname) in constant pooling merging...");
				}
			}
			*/
		}
	}

	public void configure(Decoder[] decoders)
	{
		int estimatedSize = 0, total = 0;
		int[] sizes = new int[decoders.length];
		for (int i = 0, size = sizes.length; i < size; i++)
		{
			estimatedSize += decoders[i].methodInfo.estimatedSize;
			sizes[i] = decoders[i].methodInfo.size();
			total += sizes[i];
		}
		methodInfo = new BytecodeBuffer2(estimatedSize, sizes);
		methodInfo.writeU32(total);


		estimatedSize = 0; total = 0;
		sizes = new int[decoders.length];
		for (int i = 0, size = sizes.length; i < size; i++)
		{
			estimatedSize += decoders[i].metadataInfo.estimatedSize;
			sizes[i] = decoders[i].metadataInfo.size();
			total += sizes[i];
		}
		metadataInfo = new ByteArrayPool2(sizes);


		estimatedSize = 0; total = 0;
		sizes = new int[decoders.length];
		for (int i = 0, size = sizes.length; i < size; i++)
		{
			estimatedSize += decoders[i].classInfo.estimatedSize;
			sizes[i] = decoders[i].classInfo.size();
			total += sizes[i];
		}
		classInfo = new BytecodeBuffer2(estimatedSize, sizes);
		classInfo.writeU32(total);


		estimatedSize = 0; total = 0;
		sizes = new int[decoders.length];
		for (int i = 0, size = sizes.length; i < size; i++)
		{
			estimatedSize += decoders[i].scriptInfo.estimatedSize;
			sizes[i] = decoders[i].scriptInfo.size();
			total += sizes[i];
		}
		scriptInfo = new BytecodeBuffer2(estimatedSize, sizes);
		scriptInfo.writeU32(total);


		estimatedSize = 0; total = 0;
		sizes = new int[decoders.length];
		for (int i = 0, size = sizes.length; i < size; i++)
		{
			estimatedSize += decoders[i].methodBodies.estimatedSize;
			sizes[i] = decoders[i].methodBodies.size();
			total += sizes[i];
		}
		methodBodies = new BytecodeBuffer2(estimatedSize, sizes);
		methodBodies.writeU32(total);

		opcodes = new BytecodeBuffer3(decoders, 4096);
		exceptions = new BytecodeBuffer(4096);
	}

	public void useConstantPool(int index)
	{
		poolIndex = index;
	}

	public byte[] toABC()
	{
		/*
		System.out.println();
		System.out.println("--Constant Pool--");
		System.out.println("total: " + pool.history.total + " duplicate: " + pool.history.duplicate);
		System.out.println("totalBytes: " + pool.history.totalBytes + " duplicateBytes: " + pool.history.duplicateBytes);
		System.out.println("--Method Info--");
		System.out.println("before: " + methodInfo.estimatedSize + " after: " + methodInfo.size());
		System.out.println("--Metadata Info--");
		System.out.println("before: " + metadataInfo.estimatedSize + " after: " + metadataInfo.size());
		System.out.println("--Class Info--");
		System.out.println("before: " + classInfo.estimatedSize + " after: " + classInfo.size());
		System.out.println("--Script Info--");
		System.out.println("before: " + scriptInfo.estimatedSize + " after: " + scriptInfo.size());
		System.out.println("--Method Bodies--");
		System.out.println("before: " + methodBodies.estimatedSize + " after: " + methodBodies.size());
		System.out.println();
		*/
		
		int size = pool.in.size() + methodInfo.size() + metadataInfo.size() + classInfo.size() + scriptInfo.size() + methodBodies.size();
		ByteArrayOutputStream baos = new ByteArrayOutputStream(size);

		try
		{
			baos.write((byte) minorVersion);
			baos.write((byte) (minorVersion >> 8));
			baos.write((byte) majorVersion);
			baos.write((byte) (majorVersion >> 8));

			pool.writeTo(baos);
			methodInfo.writeTo(baos);
			metadataInfo.writeTo(baos);
			classInfo.writeTo(baos);
			scriptInfo.writeTo(baos);
			methodBodies.writeTo(baos);
		}
		catch (IOException ex)
		{
			return null;
		}

		return baos.toByteArray();
	}

	public void methodInfo(int returnType, int[] paramTypes, int nativeName, int flags, int[] values, int[] value_kinds, int[] param_names)
	{
		if (paramTypes == null)
		{
			methodInfo.writeU32(0);
		}
		else
		{
			methodInfo.writeU32(paramTypes.length);
		}

		methodInfo.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, returnType));

		for (int i = 0, paramCount = (paramTypes == null) ? 0 : paramTypes.length; i < paramCount; i++)
		{
			methodInfo.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, paramTypes[i]));
		}

		methodInfo.writeU32((disableDebugging) ? 0 : pool.history.getIndex(poolIndex, IndexHistory.cp_string, nativeName));

        if( disableDebugging )
        {
            // Nuke the param names if we're getting rid of debugging info, don't want them showing
            // up in release code
            flags &= ~METHOD_HasParamNames;
        }

		methodInfo.writeU8(flags);

		if ((flags & METHOD_HasOptional) != 0)
		{
			if (values == null)
			{
				methodInfo.writeU32(0);
			}
			else
			{
				methodInfo.writeU32(values.length);
			}
		}

		for (int i = 0, optionalCount = (values == null) ? 0 : values.length; i < optionalCount; i++)
		{
			int kind = -1;

			switch (value_kinds[i])
			{
		    case CONSTANT_Utf8:
				kind = IndexHistory.cp_string;
				break;
		    case CONSTANT_Integer:
				kind = IndexHistory.cp_int;
				break;
		    case CONSTANT_UInteger:
				kind = IndexHistory.cp_uint;
				break;
		    case CONSTANT_Double:
				kind = IndexHistory.cp_double;
				break;
		    case CONSTANT_Decimal:
				kind = IndexHistory.cp_decimal;
				break;
		    case CONSTANT_Namespace:
		    case CONSTANT_PrivateNamespace:
            case CONSTANT_PackageNamespace:
            case CONSTANT_PackageInternalNs:
            case CONSTANT_ProtectedNamespace:
            case CONSTANT_ExplicitNamespace:
            case CONSTANT_StaticProtectedNs:
				kind = IndexHistory.cp_ns;
				break;
			case CONSTANT_Qname:
			case CONSTANT_QnameA:
		    case CONSTANT_Multiname:
		    case CONSTANT_MultinameA:
            case CONSTANT_TypeName:
                kind = IndexHistory.cp_mn;
				break;
		    case CONSTANT_Namespace_Set:
				kind = IndexHistory.cp_nsset;
				break;
			}

			int newIndex = 0;

			switch(value_kinds[i])
			{
			case 0:
			case CONSTANT_False:
			case CONSTANT_True:
			case CONSTANT_Null:
				// The index doesn't matter, as long as its non 0
				// there are no boolean values in any cpool, instead the value will be determined by the kind byte
				newIndex = values[i];
				break;
			default:
			{
				if (kind == -1)
				{
					System.out.println("writing MethodInfo: don't know what constant type it is... " + value_kinds[i] + "," + values[i]);
				}
				newIndex = pool.history.getIndex(poolIndex, kind, values[i]);
			}
			}

			methodInfo.writeU32(newIndex);
			methodInfo.writeU8(value_kinds[i]);
		}
        // Nuke the param names if we're not keeping debugging around
        if( (flags & METHOD_HasParamNames) != 0 && param_names != null)
        {
            for( int i = 0 ; i < param_names.length; ++i )
            {
                methodInfo.writeU32( pool.history.getIndex(poolIndex, IndexHistory.cp_string, returnType) );
            }
        }
	}

	public void metadataInfo(int index, int name, int[] keys, int[] values)
	{


        try
        {
            String s = pools[poolIndex].getString(name);
            if (removeMetadata && !keep_metadata.contains(s) )
                return;

            BytecodeBuffer b = new BytecodeBuffer(6);
            b.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_string, name));
            if (keys == null)
            {
                b.writeU32(0);
            }
            else
            {
                b.writeU32(keys.length);
            }

            for (int i = 0, keyCount = (keys == null) ? 0 : keys.length; i < keyCount; i++)
            {
                b.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_string, keys[i]));
            }

            for (int i = 0, valueCount = (values == null) ? 0 : values.length; i < valueCount; i++)
            {
                b.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_string, values[i]));
            }

            metadataInfo.addByteArray(poolIndex, index, b);
        }
        catch( DecoderException ex)
        {
            // this should never happen
            // ex.printStackTrace();
        }
	}

	public void startInstance(int name, int superName, boolean isDynamic, boolean isFinal, boolean isInterface, int[] interfaces, int iinit, int protectedNamespace)
	{
		classInfo.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, name));
		classInfo.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, superName));

		int flags = 0;
		flags = (isFinal) ? (flags | CLASS_FLAG_final) : flags;
		flags = (!isDynamic) ? (flags | CLASS_FLAG_sealed) : flags;
		flags = (isInterface) ? (flags | CLASS_FLAG_interface) : flags;
		flags = (protectedNamespace != 0) ? (flags | CLASS_FLAG_protected) : flags;
		classInfo.writeU8(flags);

		if (protectedNamespace != 0)
		{
			classInfo.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_ns, protectedNamespace));
		}
		
		if (interfaces == null)
		{
			classInfo.writeU32(0);
		}
		else
		{
			classInfo.writeU32(interfaces.length);
		}

		for (int i = 0, interfaceCount = interfaces == null ? 0 : interfaces.length; i < interfaceCount; i++)
		{
			classInfo.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, interfaces[i]));
		}

		classInfo.writeU32(methodInfo.getIndex(poolIndex, iinit));

		currentBuffer = classInfo;
	}

	public void endInstance()
	{
		currentBuffer = null;
	}

	public void startClass(int name, int cinit)
	{
		classInfo.writeU32(methodInfo.getIndex(poolIndex, cinit));

		currentBuffer = classInfo;
	}

	public void endClass()
	{
		currentBuffer = null;
	}

	public void startScript(int initID)
	{
		scriptInfo.writeU32(methodInfo.getIndex(poolIndex, initID));

		currentBuffer = scriptInfo;
	}

	public void endScript()
	{
		currentBuffer = null;
	}

	public void startMethodBody(int methodInfo, int maxStack, int maxRegs, int scopeDepth, int maxScope, int codeStart, long codeLength)
	{
		methodBodies.writeU32(this.methodInfo.getIndex(poolIndex, methodInfo));
		methodBodies.writeU32(maxStack);
		methodBodies.writeU32(maxRegs);
		methodBodies.writeU32(scopeDepth);
		methodBodies.writeU32(maxScope);

		currentBuffer = methodBodies;
		opcodePass = 1;
		exPass = 1;
	}

	public void endMethodBody()
	{
		currentBuffer = null;
		opcodes.clear();
		exceptions.clear();
	}

	public void startOpcodes(int methodInfo)
	{
	}

	public void endOpcodes()
	{
		if (opcodePass == 1)
		{
			opcodePass = 2;
		}
		else if (opcodePass == 2)
		{
			methodBodies.writeU32(opcodes.size());
			methodBodies.writeBytes(opcodes, 0, opcodes.size());
		}
	}

	public void exception(long start, long end, long target, int type, int name)
	{
		if (exPass == 2)
		{
			exceptions.writeU32(opcodes.getOffset(start));
			exceptions.writeU32(opcodes.getOffset(end));
			exceptions.writeU32(opcodes.getOffset(target));
			exceptions.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, type));
			if (minorVersion != 15)
			{
				exceptions.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, name));
			}
		}
	}

	public void startExceptions(int exceptionCount)
	{
		if (exPass == 2)
		{
			exceptions.writeU32(exceptionCount);
		}
	}

	public void endExceptions()
	{
		if (exPass == 1)
		{
			exPass++;
		}
		else if (exPass == 2)
		{
			methodBodies.writeBytes(exceptions, 0, exceptions.size());
		}
	}

	public void traitCount(int traitCount)
	{
		currentBuffer.writeU32(traitCount);
	}

//	private void encodeMetaData(int kind, int[] metadata)
//	{
//		if (((kind >> 4) & TRAIT_FLAG_metadata) != 0)
//		{
//			if (metadata == null)
//			{
//				currentBuffer.writeU32(0);
//			}
//			else
//			{
//				currentBuffer.writeU32(metadata.length);
//			}
//
//			for (int i = 0, length = metadata == null ? 0 : metadata.length; i < length; i++)
//			{
//				currentBuffer.writeU32(metadataInfo.getIndex(poolIndex, metadata[i]));
//			}
//		}
//	}

    private void encodeMetaData(int kind, IntList metadata)
    {
        if (((kind >> 4) & TRAIT_FLAG_metadata) != 0)
        {
            if (metadata == null)
            {
                currentBuffer.writeU32(0);
            }
            else
            {
                currentBuffer.writeU32(metadata.size());
            }

            for (int i = 0, length = metadata == null ? 0 : metadata.size(); i < length; i++)
            {
                currentBuffer.writeU32(metadata.get(i));
            }
        }
    }

    private IntList trimMetadata(int[] metadata)
    {
        IntList newMetadata = new IntList();
        int length = metadata != null ? metadata.length : 0;
        for( int i = 0; i < length; ++i)
        {
            int new_index = metadataInfo.getIndex(poolIndex, metadata[i]) ;
            if( new_index != -1 )
            {
                newMetadata.add(new_index);
            }
        }
        return newMetadata;
    }

	public void slotTrait(int trait_kind, int name, int slotId, int type, int value, int value_kind, int[] metadata)
	{
		currentBuffer.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, name));
        IntList new_metadata = trimMetadata(metadata);
		if ( ((trait_kind >> 4) & TRAIT_FLAG_metadata) != 0 && new_metadata.size()==0 )
		{
			trait_kind = trait_kind & ~(TRAIT_FLAG_metadata << 4);
		}
		currentBuffer.writeU8(trait_kind);

		currentBuffer.writeU32(slotId);
		currentBuffer.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, type));

		int kind = -1;

		switch(value_kind)
		{
		case CONSTANT_Utf8:
			kind = IndexHistory.cp_string;
			break;
		case CONSTANT_Integer:
			kind = IndexHistory.cp_int;
			break;
		case CONSTANT_UInteger:
			kind = IndexHistory.cp_uint;
			break;
		case CONSTANT_Double:
			kind = IndexHistory.cp_double;
			break;
		case CONSTANT_Decimal:
			kind = IndexHistory.cp_decimal;
			break;
		case CONSTANT_Namespace:
		case CONSTANT_PrivateNamespace:
        case CONSTANT_PackageNamespace:
        case CONSTANT_PackageInternalNs:
        case CONSTANT_ProtectedNamespace:
        case CONSTANT_ExplicitNamespace:
        case CONSTANT_StaticProtectedNs:			
			kind = IndexHistory.cp_ns;
			break;
		case CONSTANT_Qname:
		case CONSTANT_QnameA:
		case CONSTANT_Multiname:
		case CONSTANT_MultinameA:
        case CONSTANT_TypeName:
            kind = IndexHistory.cp_mn;
			break;
		case CONSTANT_Namespace_Set:
			kind = IndexHistory.cp_nsset;
			break;
		}

		int newIndex = 0;
		switch(value_kind)
		{
		case 0:
		case CONSTANT_False:
		case CONSTANT_True:
		case CONSTANT_Null:
			newIndex = value;
			break;
		default:
		{
			if (kind == -1)
			{
				System.out.println("writing slotTrait: don't know what constant type it is... " + value_kind + "," + value);
			}
			newIndex = pool.history.getIndex(poolIndex, kind, value);
		}
		}

		currentBuffer.writeU32(newIndex);
		if (value != 0)
		{
			currentBuffer.writeU8(value_kind);
		}

		encodeMetaData(trait_kind, new_metadata);
	}

	public void methodTrait(int trait_kind, int name, int dispId, int methodInfo, int[] metadata)
	{
		currentBuffer.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, name));
        IntList new_metadata = trimMetadata(metadata);
		if ( ((trait_kind >> 4) & TRAIT_FLAG_metadata) != 0 && new_metadata.size()==0 )
		{
			trait_kind = trait_kind & ~(TRAIT_FLAG_metadata << 4);
		}
		currentBuffer.writeU8(trait_kind);

		//currentBuffer.writeU32(0);
		currentBuffer.writeU32(dispId);
		currentBuffer.writeU32(this.methodInfo.getIndex(poolIndex, methodInfo));

		encodeMetaData(trait_kind, new_metadata);
	}

	public void classTrait(int kind, int name, int slotId, int classIndex, int[] metadata)
	{
		currentBuffer.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, name));
        IntList new_metadata = trimMetadata(metadata);
		if ( ((kind >> 4) & TRAIT_FLAG_metadata) != 0 && new_metadata.size()==0 )
		{
			kind = kind & ~(TRAIT_FLAG_metadata << 4);
		}
		currentBuffer.writeU8(kind);

		currentBuffer.writeU32(slotId);
		currentBuffer.writeU32(classInfo.getIndex(poolIndex, classIndex));

		encodeMetaData(kind, new_metadata);
	}

	public void functionTrait(int kind, int name, int slotId, int methodInfo, int[] metadata)
	{
		currentBuffer.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, name));
        IntList new_metadata = trimMetadata(metadata);
		if ( ((kind >> 4) & TRAIT_FLAG_metadata) != 0 && new_metadata.size()==0 )
		{
			kind = kind & ~(TRAIT_FLAG_metadata << 4);
		}
		currentBuffer.writeU8(kind);

		currentBuffer.writeU32(slotId);
		currentBuffer.writeU32(this.methodInfo.getIndex(poolIndex, methodInfo));

		encodeMetaData(kind, new_metadata);
	}

	static final int W = 8;
	int[] window = new int[W];
    int window_size = 0;
	int head = 0;
	boolean reachable = true;
	
	void clearWindow()
	{
		for (int i=0; i < W; i++)
			window[i] = 0;
        window_size = 0;
	}
	
	void beginop(int opcode)
	{
		window[head] = opcodes.size();
		head = (head+1) & (W-1);
        if( window_size < 8 )
            ++window_size;
		opcodes.writeU8(opcode);
	}
	
	int opat(int i)
	{
		if (peepHole)
		{
            if( i <= window_size )
            {
                int ip = window[(head-i) & (W-1)];
                if (ip < opcodes.size())
                    return opcodes.readU8(ip);
            }
		}
		return 0;
	}
	
	void setOpcodeAt(int i, int opcode)
	{
		assert peepHole;

        if( i <= window_size )
        {
            int ip = window[(head-i) & (W-1)];
            if (ip < opcodes.size())
                opcodes.writeU8(ip, opcode);
        }
	}
	
	int readByteAt(int i)
	{
        if( i <= window_size )
        {
            int ip = 1+window[(head-i) & (W-1)];
            return (byte) opcodes.readU8(ip);
        }
        return 0;
	}
	
	int readIntAt(int i)
	{
        if( i <= window_size )
        {
            int ip = 1+window[(head-i) & (W-1)];
            return (int) opcodes.readU32(ip);
        }
        return 0;
	}
	
	void rewind(int i)
	{
		int to = (head-i) & (W-1);
		int ip = window[to];
		int end = opcodes.size();
		opcodes.delete(end-ip);
		head = to;
        window_size -= i;
	}
	
	public void target(int oldPos)
	{
		if (opcodePass == 1)
		{
			opcodes.mapOffsets(oldPos);
			clearWindow();
		}
	}

	public void OP_returnvoid()
	{
		if (opcodePass == 1)
		{
			/*
			if (opat(2) == OP_getlocal0 && opat(1) == OP_pushscope)
				rewind(2);
			
			if (opat(1) == OP_returnvalue)
				return;
			*/
			beginop(OP_returnvoid);
		}
	}

	public void OP_returnvalue()
	{
		if (opcodePass == 1)
		{
			
			if (opat(1) == OP_coerce_a)
			{
				rewind(1);
			}
			
			if (opat(1) == OP_pushundefined)
			{
				rewind(1);						
				OP_returnvoid();
				return;
			}
			
			// eliminate dead code... maybe do this higher up?
			/*
			for (int i=1; i < W; i++)
			{
				if (opat(i) == OP_returnvalue) 
				{
					rewind(i);
					break;
				}
			}
			
			if (	opat(4) == OP_pushundefined &&
					opat(3) == OP_coerce_a &&
					opat(2) == OP_setlocal1 &&
					opat(1) == OP_getlocal1)
			{
				rewind(4);
				OP_returnvoid();
				return;
			}
			*/
			
			beginop(OP_returnvalue);
		}
	}

	public void OP_nop()
	{
		if (opcodePass == 1)
		{
			beginop(OP_nop);
		}
	}

	public void OP_bkpt()
	{
		if (opcodePass == 1)
		{
			beginop(OP_bkpt);
		}
	}

	public void OP_timestamp()
	{
		if (opcodePass == 1)
		{
			beginop(OP_timestamp);
		}
	}

	public void OP_debugline(int linenum)
	{
		if (opcodePass == 1)
		{
			if (!disableDebugging)
			{
				beginop(OP_debugline);
				opcodes.writeU32(linenum);
			}
		}
	}

	public void OP_bkptline()
	{
		if (opcodePass == 1)
		{
			beginop(OP_bkptline);
		}
	}

	public void OP_debug(int di_local, int index, int slot, int linenum)
	{
		if (opcodePass == 1)
		{
			if (!disableDebugging)
			{
				beginop(OP_debug);
				opcodes.writeU8(di_local);
				// FIX: is this a constant pool index? if so, we need to know the constant type...
				// opcodes.writeU32(index);
				opcodes.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_string, index));
				opcodes.writeU8(slot);
				opcodes.writeU32(linenum);
			}
		}
	}

	public void OP_debugfile(int index)
	{
		if (opcodePass == 1)
		{
			if (!disableDebugging)
			{
				beginop(OP_debugfile);
				opcodes.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_string, index));
			}
		}
	}

	public void OP_jump(int offset, int pos)
	{
		if (opcodePass == 1)
        {
			/*
			if (opat(1) == OP_jump)
			{
				// unreachable
				return;
			}
			*/

			beginop(OP_jump);
			opcodes.writeS24(offset);
			opcodes.mapOffsets(pos);
		}
		else if (opcodePass == 2)
		{
			opcodes.updateOffset(pos + offset);
		}
	}

	public void OP_pushnull()
	{
		if (opcodePass == 1)
		{
			beginop(OP_pushnull);
		}
	}

	public void OP_pushundefined()
	{
		if (opcodePass == 1)
		{
			beginop(OP_pushundefined);
		}
	}

    public void OP_pushstring(int index)
    {
	    if (opcodePass == 1)
	    {
	    	beginop(OP_pushstring);
	    	opcodes.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_string, index));
	    }
    }

    public void OP_pushnamespace(int index)
    {
	    if (opcodePass == 1)
	    {
	    	beginop(OP_pushnamespace);
	    	opcodes.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_ns, index));
	    }
    }

    public void OP_pushint(int index)
    {
	    if (opcodePass == 1)
	    {
	    	beginop(OP_pushint);
		    opcodes.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_int, index));
	    }
    }

    public void OP_pushuint(int index)
    {
	    if (opcodePass == 1)
	    {
	    	beginop(OP_pushuint);
		    opcodes.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_uint, index));
	    }
    }

    public void OP_pushdouble(int index)
    {
	    if (opcodePass == 1)
	    {
	    	beginop(OP_pushdouble);
		    opcodes.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_double, index));
	    }
    }

    public void OP_pushdecimal(int index)
    {
	    if (opcodePass == 1)
	    {
	    	beginop(OP_pushdecimal);
		    opcodes.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_decimal, index));
	    }
    }

	public void OP_getlocal(int index)
	{
		if (opcodePass == 1)
		{
			if (opat(1) == OP_setlocal && readIntAt(1) == index)
			{
				rewind(1);
				OP_dup();
				OP_setlocal(index);
				return;
			}

			beginop(OP_getlocal);
			opcodes.writeU32(index);
		}
	}

	public void OP_pushtrue()
	{
		if (opcodePass == 1)
		{
			beginop(OP_pushtrue);
		}
	}

	public void OP_pushfalse()
	{
		if (opcodePass == 1)
		{
			beginop(OP_pushfalse);
		}
	}

	public void OP_pushnan()
	{
		if (opcodePass == 1)
		{
			beginop(OP_pushnan);
		}
	}

	public void OP_pushdnan()
	{
		if (opcodePass == 1)
		{
			beginop(OP_pushdnan);
		}
	}

	public void OP_pop()
	{
		if (opcodePass == 1)
		{
			switch (opat(1))
			{
			case OP_callproperty:
				setOpcodeAt(1, OP_callpropvoid);
				return;
			case OP_callsuper:
				setOpcodeAt(1, OP_callsupervoid);
				return;
			}

			beginop(OP_pop);
		}
	}

	public void OP_dup()
	{
		if (opcodePass == 1)
		{
			beginop(OP_dup);
		}
	}

	public void OP_swap()
	{
		if (opcodePass == 1)
		{
			beginop(OP_swap);
		}
	}

	public void OP_convert_s()
	{
		if (opcodePass == 1)
		{
	        if (opat(1) == OP_coerce_a)
				rewind(1);
	        
			switch (opat(1))
			{
			case OP_coerce_s:
			case OP_convert_s:
			case OP_pushstring:
			case OP_typeof:
				// result is already string
				return;
			}
			
			if (opat(2) == OP_pushstring && opat(1) == OP_add)
			{
				// result must be string, so dont coerce after
				return;
			}

			beginop(OP_convert_s);
		}
	}

	public void OP_esc_xelem()
	{
		if (opcodePass == 1)
		{
			beginop(OP_esc_xelem);
		}
	}

    public void OP_esc_xattr()
    {
        if (opcodePass == 1)
        {
            beginop(OP_esc_xattr);
        }
    }

    public void OP_checkfilter()
    {
        if (opcodePass == 1)
        {
            beginop(OP_checkfilter);
        }
    }

	public void OP_convert_d()
	{
		if (opcodePass == 1)
		{
			beginop(OP_convert_d);
		}
	}

	public void OP_convert_m()
	{
		if (opcodePass == 1)
		{
			beginop(OP_convert_m);
		}
	}

	public void OP_convert_m_p(int param)
	{
		if (opcodePass == 1)
		{
			beginop(OP_convert_m);
			opcodes.writeU32(param);
		}
	}


	public void OP_convert_b()
	{
		if (opcodePass == 1)
		{
			switch(opat(1))
			{
			case OP_equals:
			case OP_strictequals:
			case OP_not:
			case OP_greaterthan:
			case OP_lessthan:
			case OP_greaterequals:
			case OP_lessequals:
			case OP_istype:
			case OP_istypelate:
			case OP_instanceof:
			case OP_deleteproperty:
			case OP_in:
            case OP_convert_b:
            case OP_pushtrue:
            case OP_pushfalse:
				// dont need convert
				return;
			}

			beginop(OP_convert_b);
		}
	}

	public void OP_convert_o()
	{
		if (opcodePass == 1)
		{
			beginop(OP_convert_o);
		}
	}

	public void OP_negate()
	{
		if (opcodePass == 1)
		{
			beginop(OP_negate);
		}
	}

	public void OP_negate_p(int param)
	{
		if (opcodePass == 1)
		{
			beginop(OP_negate_p);
			opcodes.writeU32(param);
		}
	}

	public void OP_negate_i()
	{
		if (opcodePass == 1)
		{
			beginop(OP_negate_i);
		}
	}

	public void OP_increment()
	{
		if (opcodePass == 1)
		{
			beginop(OP_increment);
		}
	}

	public void OP_increment_p(int param)
	{
		if (opcodePass == 1)
		{
			beginop(OP_increment_p);
			opcodes.writeU32(param);
		}
	}

	public void OP_increment_i()
	{
		if (opcodePass == 1)
		{
			beginop(OP_increment_i);
		}
	}

	public void OP_inclocal(int index)
	{
		if (opcodePass == 1)
		{
			beginop(OP_inclocal);
			opcodes.writeU32(index);
		}
	}

	public void OP_inclocal_p(int param, int index)
	{
		if (opcodePass == 1)
		{
			beginop(OP_inclocal_p);
			opcodes.writeU32(param);
			opcodes.writeU32(index);
		}
	}

	public void OP_kill(int index)
	{
		if (opcodePass == 1)
		{
			switch(opat(1))
			{
			case OP_returnvalue:
			case OP_returnvoid:
				// unreachable
				return;
			}

			beginop(OP_kill);
			opcodes.writeU32(index);
		}
	}

	public void OP_inclocal_i(int index)
	{
		if (opcodePass == 1)
		{
			beginop(OP_inclocal_i);
			opcodes.writeU32(index);
		}
	}

	public void OP_decrement()
	{
		if (opcodePass == 1)
		{
			beginop(OP_decrement);
		}
	}

	public void OP_decrement_p(int param)
	{
		if (opcodePass == 1)
		{
			beginop(OP_decrement_p);
			opcodes.writeU32(param);
		}
	}

	public void OP_decrement_i()
	{
		if (opcodePass == 1)
		{
			beginop(OP_decrement_i);
		}
	}

	public void OP_declocal(int index)
	{
		if (opcodePass == 1)
		{
			beginop(OP_declocal);
			opcodes.writeU32(index);
		}
	}

	public void OP_declocal_p(int param, int index)
	{
		if (opcodePass == 1)
		{
			beginop(OP_declocal_p);
			opcodes.writeU32(param);
			opcodes.writeU32(index);
		}
	}

	public void OP_declocal_i(int index)
	{
		if (opcodePass == 1)
		{
			beginop(OP_declocal_i);
			opcodes.writeU32(index);
		}
	}

	public void OP_typeof()
	{
		if (opcodePass == 1)
		{
			beginop(OP_typeof);
		}
	}

	public void OP_not()
	{
		if (opcodePass == 1)
		{
			beginop(OP_not);
		}
	}

	public void OP_bitnot()
	{
		if (opcodePass == 1)
		{
			beginop(OP_bitnot);
		}
	}

	public void OP_setlocal(int index)
	{
		if (opcodePass == 1)
		{
			if (opat(2) == OP_getlocal && readIntAt(2) == index &&
				opat(1) == OP_increment_i)
			{
				rewind(2);
				OP_inclocal_i(index);
				return;
			}
			
			if (opat(2) == OP_getlocal && readIntAt(2) == index &&
				opat(1) == OP_increment)
			{
				rewind(2);
				OP_inclocal(index);
				return;
			}
			
			beginop(OP_setlocal);
			opcodes.writeU32(index);
		}
	}

	public void OP_add()
	{
		if (opcodePass == 1)
		{
			if (opat(1) == OP_coerce_a)
				rewind(1);
			beginop(OP_add);
		}
	}

	public void OP_add_p(int param)
	{
		if (opcodePass == 1)
		{
			if (opat(1) == OP_coerce_a)
				rewind(1);
			beginop(OP_add_p);
			opcodes.writeU32(param);
		}
	}

	public void OP_add_i()
	{
		if (opcodePass == 1)
		{
			beginop(OP_add_i);
		}
	}

	public void OP_subtract()
	{
		if (opcodePass == 1)
		{
			if (opat(1) == OP_pushbyte && readByteAt(1) == 1)
			{
				rewind(1);
				OP_decrement();
				return;
			}
			beginop(OP_subtract);
		}
	}

	public void OP_subtract_p(int param)
	{
		if (opcodePass == 1)
		{
			if (opat(1) == OP_pushbyte && readByteAt(1) == 1)
			{
				rewind(1);
				OP_decrement_p(param);
				return;
			}
			beginop(OP_subtract_p);
			opcodes.writeU32(param);
		}
	}

	public void OP_subtract_i()
	{
		if (opcodePass == 1)
		{
			if (opat(1) == OP_pushbyte && readIntAt(1) == 1)
			{
				rewind(1);
				OP_decrement_i();
				return;
			}
			beginop(OP_subtract_i);
		}
	}

	public void OP_multiply()
	{
		if (opcodePass == 1)
		{
			beginop(OP_multiply);
		}
	}

	public void OP_multiply_p(int param)
	{
		if (opcodePass == 1)
		{
			beginop(OP_multiply_p);
			opcodes.writeU32(param);
		}
	}

	public void OP_multiply_i()
	{
		if (opcodePass == 1)
		{
			beginop(OP_multiply_i);
		}
	}

	public void OP_divide()
	{
		if (opcodePass == 1)
		{
			beginop(OP_divide);
		}
	}

	public void OP_divide_p(int param)
	{
		if (opcodePass == 1)
		{
			beginop(OP_divide_p);
			opcodes.writeU32(param);
		}
	}

	public void OP_modulo()
	{
		if (opcodePass == 1)
		{
			beginop(OP_modulo);
		}
	}

	public void OP_modulo_p(int param)
	{
		if (opcodePass == 1)
		{
			beginop(OP_modulo_p);
			opcodes.writeU32(param);
		}
	}

	public void OP_lshift()
	{
		if (opcodePass == 1)
		{
			beginop(OP_lshift);
		}
	}

	public void OP_rshift()
	{
		if (opcodePass == 1)
		{
			beginop(OP_rshift);
		}
	}

	public void OP_urshift()
	{
		if (opcodePass == 1)
		{
			beginop(OP_urshift);
		}
	}

	public void OP_bitand()
	{
		if (opcodePass == 1)
		{
			beginop(OP_bitand);
		}
	}

	public void OP_bitor()
	{
		if (opcodePass == 1)
		{
			beginop(OP_bitor);
		}
	}

	public void OP_bitxor()
	{
		if (opcodePass == 1)
		{
			beginop(OP_bitxor);
		}
	}

	public void OP_equals()
	{
		if (opcodePass == 1)
		{
			beginop(OP_equals);
		}
	}

	public void OP_strictequals()
	{
		if (opcodePass == 1)
		{
			beginop(OP_strictequals);
		}
	}

	public void OP_lookupswitch(int defaultPos, int[] casePos, int oldPos, int oldTablePos)
	{
		if (opcodePass == 1)
		{
			opcodes.mapOffsets(oldPos);
			beginop(OP_lookupswitch);
			opcodes.mapOffsets(oldPos+1);
			opcodes.writeS24(defaultPos);
			opcodes.writeU32((casePos == null || casePos.length == 0) ? 0 : casePos.length - 1);
			for (int i = 0, size = casePos.length; i < size; i++)
			{
				opcodes.mapOffsets(oldTablePos+3*i);
				opcodes.writeS24(casePos[i]);
			}
		}
		else if (opcodePass == 2)
		{
			opcodes.updateOffset(oldPos + 1, oldPos, oldPos + defaultPos);
			for (int i = 0, size = casePos.length; i < size; i++)
			{
				opcodes.updateOffset(oldTablePos + (3 * i), oldPos, oldPos + casePos[i]);
			}
		}
	}

	public void OP_iftrue(int offset, int pos)
	{
		if (opcodePass == 1)
		{
			if (opat(1) == OP_convert_b)
				rewind(1);

			if (opat(1) == OP_pushtrue)
			{
				rewind(1);
				OP_jump(offset,pos);
				return;
			}
			
			beginop(OP_iftrue);
			opcodes.writeS24(offset);
			opcodes.mapOffsets(pos);
		}
		else if (opcodePass == 2)
		{
			opcodes.updateOffset(pos + offset);
		}
	}

	public void OP_iffalse(int offset, int pos)
	{
		if (opcodePass == 1)
		{
			if (opat(1) == OP_convert_b)
			{
				rewind(1);
			}

			if (opat(2) == OP_strictequals && opat(1) == OP_not)
			{
				rewind(2);
				OP_ifstricteq(offset, pos);
				return;
			}
			
			if (opat(2) == OP_equals && opat(1) == OP_not)
			{
				rewind(2);
				OP_ifeq(offset, pos);
				return;
			}
			
			if (opat(1) == OP_not)
			{
				rewind(1);
				OP_iftrue(offset, pos);
				return;
			}
			
			if (opat(1) == OP_pushfalse)
			{
				rewind(1);
				OP_jump(offset, pos);
				return;
			}
			
			beginop(OP_iffalse);
			opcodes.writeS24(offset);
			opcodes.mapOffsets(pos);
		}
		else if (opcodePass == 2)
		{
			opcodes.updateOffset(pos + offset);
		}
	}

	public void OP_ifeq(int offset, int pos)
	{
		if (opcodePass == 1)
		{
			beginop(OP_ifeq);
			opcodes.writeS24(offset);
			opcodes.mapOffsets(pos);
		}
		else if (opcodePass == 2)
		{
			opcodes.updateOffset(pos + offset);
		}
	}

	public void OP_ifne(int offset, int pos)
	{
		if (opcodePass == 1)
		{
			beginop(OP_ifne);
			opcodes.writeS24(offset);
			opcodes.mapOffsets(pos);
		}
		else if (opcodePass == 2)
		{
			opcodes.updateOffset(pos + offset);
		}
	}

	public void OP_ifstricteq(int offset, int pos)
	{
		if (opcodePass == 1)
		{
			beginop(OP_ifstricteq);
			opcodes.writeS24(offset);
			opcodes.mapOffsets(pos);
		}
		else if (opcodePass == 2)
		{
			opcodes.updateOffset(pos + offset);
		}
	}

	public void OP_ifstrictne(int offset, int pos)
	{
		if (opcodePass == 1)
		{
			beginop(OP_ifstrictne);
			opcodes.writeS24(offset);
			opcodes.mapOffsets(pos);
		}
		else if (opcodePass == 2)
		{
			opcodes.updateOffset(pos + offset);
		}
	}

	public void OP_iflt(int offset, int pos)
	{
		if (opcodePass == 1)
		{
			beginop(OP_iflt);
			opcodes.writeS24(offset);
			opcodes.mapOffsets(pos);
		}
		else if (opcodePass == 2)
		{
			opcodes.updateOffset(pos + offset);
		}
	}

	public void OP_ifle(int offset, int pos)
	{
		if (opcodePass == 1)
		{
			beginop(OP_ifle);
			opcodes.writeS24(offset);
			opcodes.mapOffsets(pos);
		}
		else if (opcodePass == 2)
		{
			opcodes.updateOffset(pos + offset);
		}
	}

	public void OP_ifgt(int offset, int pos)
	{
		if (opcodePass == 1)
		{
			beginop(OP_ifgt);
			opcodes.writeS24(offset);
			opcodes.mapOffsets(pos);
		}
		else if (opcodePass == 2)
		{
			opcodes.updateOffset(pos + offset);
		}
	}

	public void OP_ifge(int offset, int pos)
	{
		if (opcodePass == 1)
		{
			beginop(OP_ifge);
			opcodes.writeS24(offset);
			opcodes.mapOffsets(pos);
		}
		else if (opcodePass == 2)
		{
			opcodes.updateOffset(pos + offset);
		}
	}

	public void OP_lessthan()
	{
		if (opcodePass == 1)
		{
			beginop(OP_lessthan);
		}
	}

	public void OP_lessequals()
	{
		if (opcodePass == 1)
		{
			beginop(OP_lessequals);
		}
	}

	public void OP_greaterthan()
	{
		if (opcodePass == 1)
		{
			beginop(OP_greaterthan);
		}
	}

	public void OP_greaterequals()
	{
		if (opcodePass == 1)
		{
			beginop(OP_greaterequals);
		}
	}

	public void OP_newobject(int size)
	{
		if (opcodePass == 1)
		{
			if (opat(1) == OP_coerce_a && size >= 1)
				rewind(1);

			beginop(OP_newobject);
			opcodes.writeU32(size);
		}
	}

	public void OP_newarray(int size)
	{
		if (opcodePass == 1)
		{
			beginop(OP_newarray);
			opcodes.writeU32(size);
		}
	}

	public void OP_getproperty(int index)
	{
		if (opcodePass == 1)
		{
			if (opat(1) == OP_findpropstrict && readIntAt(1) == pool.history.getIndex(poolIndex, IndexHistory.cp_mn, index))
			{
				rewind(1);
				OP_getlex(index);
				return;
			}

			beginop(OP_getproperty);
			opcodes.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, index));
		}
	}

    public void OP_setproperty(int index)
    {
        if (opcodePass == 1)
        {
            if (opat(1) == OP_coerce_a)
                rewind(1);

            beginop(OP_setproperty);
            opcodes.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, index));
        }
    }

    public void OP_initproperty(int index)
    {
        if (opcodePass == 1)
        {
            if (opat(1) == OP_coerce_a)
                rewind(1);

            beginop(OP_initproperty);
            opcodes.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, index));
        }
    }

	public void OP_getdescendants(int index)
	{
		if (opcodePass == 1)
		{
			beginop(OP_getdescendants);
			opcodes.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, index));
		}
	}

	public void OP_findpropstrict(int index)
	{
		if (opcodePass == 1)
		{
			beginop(OP_findpropstrict);
			opcodes.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, index));
		}
	}

	public void OP_findproperty(int index)
	{
		if (opcodePass == 1)
		{
			beginop(OP_findproperty);
			opcodes.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, index));
		}
	}

	public void OP_finddef(int index)
	{
		if (opcodePass == 1)
		{
			beginop(OP_finddef);
			opcodes.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, index));
		}
	}
	
	public void OP_getlex(int index)
	{
		if (opcodePass == 1)
		{
			beginop(OP_getlex);
			opcodes.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, index));
		}
	}

	public void OP_nextname()
	{
		if (opcodePass == 1)
		{
			beginop(OP_nextname);
		}
	}

	public void OP_nextvalue()
	{
		if (opcodePass == 1)
		{
			beginop(OP_nextvalue);
		}
	}

	public void OP_hasnext()
	{
		if (opcodePass == 1)
		{
			beginop(OP_hasnext);
		}
	}

	public void OP_hasnext2(int objectRegister, int indexRegister)
	{
		if (opcodePass == 1)
		{
			beginop(OP_hasnext2);
			opcodes.writeU32(objectRegister);
			opcodes.writeU32(indexRegister);			
		}
	}
	
	public void OP_deleteproperty(int index)
	{
		if (opcodePass == 1)
		{
			beginop(OP_deleteproperty);
			opcodes.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, index));
		}
	}

	public void OP_setslot(int index)
	{
		if (opcodePass == 1)
		{
			beginop(OP_setslot);
			opcodes.writeU32(index);
		}
	}

	public void OP_getslot(int index)
	{
		if (opcodePass == 1)
		{
			beginop(OP_getslot);
			opcodes.writeU32(index);
		}
	}

	public void OP_setglobalslot(int index)
	{
		if (opcodePass == 1)
		{
			if (opat(1) == OP_coerce_a)
				rewind(1);

			beginop(OP_setglobalslot);
			opcodes.writeU32(index);
		}
	}

	public void OP_getglobalslot(int index)
	{
		if (opcodePass == 1)
		{
			beginop(OP_getglobalslot);
			opcodes.writeU32(index);
		}
	}

	public void OP_call(int size)
	{
		if (opcodePass == 1)
		{
			beginop(OP_call);
			opcodes.writeU32(size);
		}
	}

	public void OP_construct(int size)
	{
		if (opcodePass == 1)
		{
			beginop(OP_construct);
			opcodes.writeU32(size);
		}
	}

    public void OP_applytype(int size)
    {
        if (opcodePass == 1)
        {
            beginop(OP_applytype);
            opcodes.writeU32(size);
        }
    }

	public void OP_newfunction(int id)
	{
		if (opcodePass == 1)
		{
			beginop(OP_newfunction);
			opcodes.writeU32(methodInfo.getIndex(poolIndex, id));
		}
	}

	public void OP_newclass(int id)
	{
		if (opcodePass == 1)
		{
			beginop(OP_newclass);
			opcodes.writeU32(classInfo.getIndex(poolIndex, id));
		}
	}

	public void OP_callstatic(int id, int argc)
	{
		if (opcodePass == 1)
		{
			beginop(OP_callstatic);
			opcodes.writeU32(methodInfo.getIndex(poolIndex, id));
			opcodes.writeU32(argc);
		}
	}

	public void OP_callmethod(int id, int argc)
	{
		if (opcodePass == 1)
		{
			beginop(OP_callmethod);
			opcodes.writeU32(methodInfo.getIndex(poolIndex, id));
			opcodes.writeU32(argc);
		}
	}

	public void OP_callproperty(int index, int argc)
	{
		if (opcodePass == 1)
		{
	        if (opat(1) == OP_coerce_a)
	        	rewind(1);
			
			beginop(OP_callproperty);
			opcodes.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, index));
			opcodes.writeU32(argc);
		}
	}

	public void OP_callproplex(int index, int argc)
	{
		if (opcodePass == 1)
		{
			beginop(OP_callproplex);
			opcodes.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, index));
			opcodes.writeU32(argc);
		}
	}

	public void OP_constructprop(int index, int argc)
	{
		if (opcodePass == 1)
		{
			beginop(OP_constructprop);
			opcodes.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, index));
			opcodes.writeU32(argc);
		}
	}

	public void OP_callsuper(int index, int argc)
	{
		if (opcodePass == 1)
		{
			beginop(OP_callsuper);
			opcodes.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, index));
			opcodes.writeU32(argc);
		}
	}

	public void OP_getsuper(int index)
	{
		if (opcodePass == 1)
		{
			beginop(OP_getsuper);
			opcodes.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, index));
		}
	}

	public void OP_setsuper(int index)
	{
		if (opcodePass == 1)
		{
			beginop(OP_setsuper);
			opcodes.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, index));
		}
	}

	public void OP_constructsuper(int argc)
	{
		if (opcodePass == 1)
		{
			beginop(OP_constructsuper);
			opcodes.writeU32(argc);
		}
	}

	public void OP_pushshort(int n)
	{
		if (opcodePass == 1)
		{
			if (peepHole && n >= -128 && n <= 127)
			{
				OP_pushbyte(n);
				return;
			}
			beginop(OP_pushshort);
			opcodes.writeU32(n);
		}
	}

	public void OP_astype(int index)
	{
		if (opcodePass == 1)
		{
			beginop(OP_astype);
			opcodes.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, index));
		}
	}

	public void OP_astypelate()
	{
		if (opcodePass == 1)
		{
			beginop(OP_astypelate);
		}
	}

	public void OP_coerce(int index)
	{
		if (opcodePass == 1)
		{
			if (opat(1) == OP_coerce && readIntAt(1) == pool.history.getIndex(poolIndex, IndexHistory.cp_mn, index))
			{
				// second coerce to same type is redundant
				return;
			}

			beginop(OP_coerce);
			opcodes.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, index));
		}
	}

	public void OP_coerce_b()
	{
		if (opcodePass == 1)
		{
			beginop(OP_coerce_b);
		}
	}

	public void OP_coerce_o()
	{
		if (opcodePass == 1)
		{
			beginop(OP_coerce_o);
		}
	}

	public void OP_coerce_a()
	{
		if (opcodePass == 1)
		{
			if (opat(1) == OP_coerce_a)
				return;
			beginop(OP_coerce_a);
		}
	}

	public void OP_coerce_i()
	{
		if (opcodePass == 1)
		{
			switch (opat(1))
			{
				case OP_coerce_i :
				case OP_convert_i :
				case OP_increment_i :
				case OP_decrement_i :
				case OP_pushbyte :
				case OP_pushshort :
				case OP_pushint :
				case OP_bitand:
				case OP_bitor:
				case OP_bitxor:
				case OP_lshift:
				case OP_rshift:
				case OP_add_i:
				case OP_subtract_i:
				case OP_multiply_i:
				case OP_bitnot:
					// coerce is redundant
					return;
			}
			beginop(OP_coerce_i);
		}
	}

	public void OP_coerce_u()
	{
		if (opcodePass == 1)
		{
			switch (opat(1))
			{
			case OP_coerce_u:
			case OP_convert_u:
			case OP_urshift:
				// redundant coerce
				return;
			}
			beginop(OP_coerce_u);
		}
	}

	public void OP_coerce_d()
	{
		if (opcodePass == 1)
		{
			switch (opat(1))
			{
				case OP_subtract :
				case OP_multiply :
				case OP_divide :
				case OP_modulo :
				case OP_increment :
				case OP_decrement :
				case OP_inclocal :
				case OP_declocal :
				case OP_coerce_d :
				case OP_convert_d :
					// coerce is redundant
					return;
			}
			beginop(OP_coerce_d);
		}
	}

	public void OP_coerce_s()
	{
		if (opcodePass == 1)
		{
			switch (opat(1))
			{
			case OP_coerce_s:
			case OP_convert_s:
			case OP_pushstring:
			case OP_typeof:
				// result is already string
				return;
			}
			
			if (opat(2) == OP_pushstring && opat(1) == OP_add)
			{
				// result must be string, so dont coerce after
				return;
			}

			beginop(OP_coerce_s);
		}
	}

	public void OP_istype(int index)
	{
		if (opcodePass == 1)
		{
			beginop(OP_istype);
			opcodes.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, index));
		}
	}

	public void OP_istypelate()
	{
		if (opcodePass == 1)
		{
			beginop(OP_istypelate);
		}
	}

	public void OP_pushbyte(int n)
	{
		if (opcodePass == 1)
		{
			if (opat(1) == OP_pushbyte && readByteAt(1) == n ||
				opat(1) == OP_dup && opat(2) == OP_pushbyte && readByteAt(2) == n )
			{
				OP_dup();
				return;
			}
			beginop(OP_pushbyte);
			opcodes.writeU8(n);
		}
	}

	public void OP_getscopeobject(int index)
	{
		if (opcodePass == 1)
		{
			beginop(OP_getscopeobject);
			opcodes.writeU8(index);
		}
	}

	public void OP_pushscope()
	{
		if (opcodePass == 1)
		{
			beginop(OP_pushscope);
		}
	}

	public void OP_popscope()
	{
		if (opcodePass == 1)
		{
			beginop(OP_popscope);
		}
	}

	public void OP_convert_i()
	{
		if (opcodePass == 1)
		{
			switch (opat(1))
			{
			case OP_convert_i:
			case OP_coerce_i:
			case OP_bitand:
			case OP_bitor:
			case OP_bitxor:
			case OP_lshift:
			case OP_rshift:
			case OP_add_i:
			case OP_subtract_i:
			case OP_increment_i:
			case OP_decrement_i:
			case OP_multiply_i:
			case OP_pushbyte:
			case OP_pushshort:
			case OP_pushint:
				return;
			}
			
			beginop(OP_convert_i);
		}
	}

	public void OP_convert_u()
	{
		if (opcodePass == 1)
		{
			beginop(OP_convert_u);
		}
	}

	public void OP_throw()
	{
		if (opcodePass == 1)
		{
			beginop(OP_throw);
		}
	}

	public void OP_instanceof()
	{
		if (opcodePass == 1)
		{
			beginop(OP_instanceof);
		}
	}

	public void OP_in()
	{
		if (opcodePass == 1)
		{
			beginop(OP_in);
		}
	}

	public void OP_dxns(int index)
	{
		if (opcodePass == 1)
		{
			beginop(OP_dxns);
			opcodes.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_string, index));
		}
	}

	public void OP_dxnslate()
	{
		if (opcodePass == 1)
		{
			beginop(OP_dxnslate);
		}
	}

	public void OP_ifnlt(int offset, int pos)
	{
		if (opcodePass == 1)
		{
			beginop(OP_ifnlt);
			opcodes.writeS24(offset);
			opcodes.mapOffsets(pos);
		}
		else if (opcodePass == 2)
		{
			opcodes.updateOffset(pos + offset);
		}
	}

	public void OP_ifnle(int offset, int pos)
	{
		if (opcodePass == 1)
		{
			beginop(OP_ifnle);
			opcodes.writeS24(offset);
			opcodes.mapOffsets(pos);
		}
		else if (opcodePass == 2)
		{
			opcodes.updateOffset(pos + offset);
		}
	}

	public void OP_ifngt(int offset, int pos)
	{
		if (opcodePass == 1)
		{
			beginop(OP_ifngt);
			opcodes.writeS24(offset);
			opcodes.mapOffsets(pos);
		}
		else if (opcodePass == 2)
		{
			opcodes.updateOffset(pos + offset);
		}
	}

	public void OP_ifnge(int offset, int pos)
	{
		if (opcodePass == 1)
		{
			beginop(OP_ifnge);
			opcodes.writeS24(offset);
			opcodes.mapOffsets(pos);
		}
		else if (opcodePass == 2)
		{
			opcodes.updateOffset(pos + offset);
		}
	}

	public void OP_pushwith()
	{
		if (opcodePass == 1)
		{
			beginop(OP_pushwith);
		}
	}

	public void OP_newactivation()
	{
		if (opcodePass == 1)
		{
			beginop(OP_newactivation);
		}
	}

	public void OP_newcatch(int index)
	{
		if (opcodePass == 1)
		{
			beginop(OP_newcatch);
			opcodes.writeU32(index);			
		}
	}

	public void OP_deldescendants()
	{
		if (opcodePass == 1)
		{
			beginop(OP_deldescendants);
		}
	}

	public void OP_getglobalscope()
	{
		if (opcodePass == 1)
		{
			beginop(OP_getglobalscope);
		}
	}

	public void OP_getlocal0()
	{
		if (opcodePass == 1)
		{
			beginop(OP_getlocal0);
		}
	}

	public void OP_getlocal1()
	{
		if (opcodePass == 1)
		{
			beginop(OP_getlocal1);
		}
	}

	public void OP_getlocal2()
	{
		if (opcodePass == 1)
		{
			beginop(OP_getlocal2);
		}
	}

	public void OP_getlocal3()
	{
		if (opcodePass == 1)
		{
			beginop(OP_getlocal3);
		}
	}

	public void OP_setlocal0()
	{
		if (opcodePass == 1)
		{
			beginop(OP_setlocal0);
		}
	}

	public void OP_setlocal1()
	{
		if (opcodePass == 1)
		{
			if (opat(2) == OP_getlocal1 && opat(1) == OP_increment_i)
			{
				rewind(2);
				OP_inclocal_i(1);
				return;
			}
			if (opat(2) == OP_getlocal1 && opat(1) == OP_increment)
			{
				rewind(2);
				OP_inclocal(1);
				return;
			}
			beginop(OP_setlocal1);
		}
	}

	public void OP_setlocal2()
	{
		if (opcodePass == 1)
		{
			if (opat(2) == OP_getlocal2 && opat(1) == OP_increment_i)
			{
				rewind(2);
				OP_inclocal_i(2);
				return;
			}
			if (opat(2) == OP_getlocal2 && opat(1) == OP_increment)
			{
				rewind(2);
				OP_inclocal(2);
				return;
			}
			beginop(OP_setlocal2);
		}
	}

	public void OP_setlocal3()
	{
		if (opcodePass == 1)
		{
			if (opat(2) == OP_getlocal3 && opat(1) == OP_increment_i)
			{
				rewind(2);
				OP_inclocal_i(3);
				return;
			}
			if (opat(2) == OP_getlocal3 && opat(1) == OP_increment)
			{
				rewind(2);
				OP_inclocal(3);
				return;
			}
			beginop(OP_setlocal3);
		}
	}
	
	public void OP_label()
	{
		if (opcodePass == 1)
		{
			beginop(OP_label);
		}
	}

	public void OP_pushconstant(int id)
	{
		if (opcodePass == 1)
		{
			beginop(OP_pushuninitialized);
			opcodes.writeU32(id);
		}
	}

	public void OP_callsupervoid(int index, int argc)
	{
		if (opcodePass == 1)
		{
			beginop(OP_callsupervoid);
			opcodes.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, index));
			opcodes.writeU32(argc);
		}
	}

	public void OP_callpropvoid(int index, int argc)
	{
		if (opcodePass == 1)
		{
			beginop(OP_callpropvoid);
			opcodes.writeU32(pool.history.getIndex(poolIndex, IndexHistory.cp_mn, index));
			opcodes.writeU32(argc);
		}
	}

    public void OP_li8()
    {
        if( opcodePass == 1 )
        {
            beginop(OP_li8);
        }
    }

    public void OP_li16()
    {
        if( opcodePass == 1 )
        {
            beginop(OP_li16);
        }
    }

    public void OP_li32()
    {
        if( opcodePass == 1 )
        {
            beginop(OP_li32);
        }
    }

    public void OP_lf32()
    {
        if( opcodePass == 1 )
        {
            beginop(OP_lf32);
        }
    }

    public void OP_lf64()
    {
        if( opcodePass == 1 )
        {
            beginop(OP_lf64);
        }
    }

    public void OP_si8()
    {
        if( opcodePass == 1 )
        {
            beginop(OP_si8);
        }
    }

    public void OP_si16()
    {
        if( opcodePass == 1 )
        {
            beginop(OP_si16);
        }
    }

    public void OP_si32()
    {
        if( opcodePass == 1 )
        {
            beginop(OP_si32);
        }
    }

    public void OP_sf32()
    {
        if( opcodePass == 1 )
        {
            beginop(OP_sf32);
        }
    }

    public void OP_sf64()
    {
        if( opcodePass == 1 )
        {
            beginop(OP_sf64);
        }
    }

    public void OP_sxi1()
    {
        if( opcodePass == 1 )
        {
            beginop(OP_sxi1);
        }
    }

    public void OP_sxi8()
    {
        if( opcodePass == 1 )
        {
            beginop(OP_sxi8);
        }
    }

    public void OP_sxi16()
    {
        if( opcodePass == 1 )
        {
            beginop(OP_sxi16);
        }
    }

    class ByteArrayPool2 extends ByteArrayPool
    {
        ByteArrayPool2(int[] sizes)
        {
            this.sizes = sizes;
            indexes = new HashMap<Integer, Integer>();
        }

        int size = 0;

        private int[] sizes;

        private Map<Integer, Integer> indexes;

        int addByteArray(int poolIndex, int oldIndex, BytecodeBuffer ba)
        {
            int index = this.contains(ba, 0, ba.size());
            if( index == -1 )
            {
                index = this.store(ba, 0, ba.size());
                size += ba.size();
            }
            // ByteArrayPool is 1 based, we want zero based for metadataInfos
            indexes.put(IntegerPool.getNumber(calcIndex(poolIndex, oldIndex)), IntegerPool.getNumber(index-1));
            return index;
        }

        private int calcIndex(int poolIndex, int oldIndex)
        {
            int newIndex = 0;
            for (int i = 0; i < poolIndex; i++)
            {
                newIndex += sizes[i];
            }
            newIndex += oldIndex;

            return newIndex;
        }

        int getIndex(int poolIndex, int oldIndex)
        {
            int newIndex = calcIndex(poolIndex, oldIndex);
            Integer i = indexes.get(IntegerPool.getNumber(newIndex));
            return i != null ? i.intValue() : -1;
        }

        int size()
        {
            return size;
        }

        void writeTo(BytecodeBuffer b)
        {
            Map sortedMap = new TreeMap();

            for (Iterator i = map.keySet().iterator(); i.hasNext();)
            {
                Object key = i.next(); // ByteArray
                Object value = map.get(key); // Integer
                sortedMap.put(value, key);
            }

            b.writeU32((sortedMap.size() == 0) ? 0 : sortedMap.size() );

            for (Iterator i = sortedMap.keySet().iterator(); i.hasNext();)
            {
                Integer index = (Integer) i.next();
                ByteArray a = (ByteArray) sortedMap.get(index);
                b.writeBytes(a.b, a.start, a.end);
            }
        }

        void writeTo(OutputStream os) throws java.io.IOException
        {
            BytecodeBuffer b = new BytecodeBuffer(size());
            writeTo(b);
            b.writeTo(os);
        }
    }

	class BytecodeBuffer2 extends BytecodeBuffer
	{
		BytecodeBuffer2(int estimatedSize, int[] sizes)
		{
			super(estimatedSize);
			this.sizes = sizes;
			this.estimatedSize = estimatedSize;
		}

		private int[] sizes;
		int estimatedSize;

		int getIndex(int poolIndex, int oldIndex)
		{
			int newIndex = 0;
			for (int i = 0; i < poolIndex; i++)
			{
				newIndex += sizes[i];
			}
			newIndex += oldIndex;

			return newIndex;
		}
	}

	class BytecodeBuffer3 extends BytecodeBuffer
	{
		BytecodeBuffer3(Decoder[] decoders, int estimatedSize)
		{
			super(estimatedSize);
			offsets = new HashMap<Integer, Integer>();
			this.decoders = decoders;
		}

		Map<Integer, Integer> offsets;
		Decoder[] decoders;

		void mapOffsets(long offset)
		{
			Integer oldPos = IntegerPool.getNumber((int) offset);
			Integer newPos = IntegerPool.getNumber(size());

			offsets.put(IntegerPool.getNumber(oldPos), IntegerPool.getNumber(newPos));
		}

		long getOffset(long offset)
		{
			Integer i = offsets.get(IntegerPool.getNumber((int) offset));
			if (i != null)
			{
				return i.intValue();
			}
			else
			{
				System.out.println("getOffset: can't match " + offset + " with a new offset ");
				System.out.println(offsets);
				return 0;
			}
		}

		void updateOffset(long offset)
		{
			Integer i = offsets.get(IntegerPool.getNumber((int) offset));
			Integer p = offsets.get(IntegerPool.getNumber(decoders[poolIndex].pos()));

			if (i != null && p != null)
			{
				writeS24(p-3, i-p);
			}
			else
			{
				/* cn: temporarily disable this warning.  The warnings that are being generated are
				 *     for jumps which are part of unreachable code, but I haven't found where those
				 *     unecessary jumps are being genreated within codeGenerator yet.  Disabling to redeuce
				 *     annoyance in the short term, 
				 *     bug #149141 remains open until the root cause is found and removed
				if (i == null)
					System.out.println("updateOffset1: can't match i " + offset + " with a new offset");
				if (p == null)
					System.out.println("updateOffset1: can't match p " + decoders[poolIndex].pos() + " with a new offset");
				System.out.println(offsets);
				*/
			}
		}

		void updateOffset(long oldOffsetPos, long oldPos, long oldTarget)
		{
			Integer i = offsets.get(IntegerPool.getNumber((int) oldTarget));
			Integer p = offsets.get(IntegerPool.getNumber((int) oldPos));
			Integer s = offsets.get(IntegerPool.getNumber((int) oldOffsetPos));

			if (i != null && p != null && s != null)
			{
				writeS24(s, i-p);
			}
			else
			{
				if (i == null)
					System.out.println("updateOffset2: can't match i " + oldTarget + " with a new offset");
				if (p == null)
					System.out.println("updateOffset2: can't match p " + oldPos + " with a new offset");
				if (s == null)
					System.out.println("updateOffset2: can't match s " + oldOffsetPos + " with a new offset");
				System.out.println(offsets);
			}
		}

		public void clear()
		{
			super.clear();
			offsets.clear();
		}
	}
}

