/*******************************************************************************
* Copyright (c) 2009 Luaj.org. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
package org.luaj.vm2;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;


/**
* Class to undump compiled lua bytecode into a {@link Prototype} instances.
* <p>
* The {@link LoadState} class provides the default {@link Globals.Undumper}
* which is used to undump a string of bytes that represent a lua binary file
* using either the C-based lua compiler, or luaj's
* {@link org.luaj.vm2.compiler.LuaC} compiler.
* <p>
* The canonical method to load and execute code is done
* indirectly using the Globals:
* <pre> {@code
* Globals globals = JsePlatform.standardGlobals();
* LuaValue chunk = globasl.load("print('hello, world')", "main.lua");
* chunk.call();
* } </pre>
* This should work regardless of which {@link Globals.Compiler} or {@link Globals.Undumper}
* have been installed.
* <p>
* By default, when using {@link org.luaj.vm2.libs.jse.JsePlatform} or
* {@link org.luaj.vm2.libs.jme.JmePlatform}
* to construct globals, the {@link LoadState} default undumper is installed
* as the default {@link Globals.Undumper}.
* <p>
* 
* A lua binary file is created via the {@link org.luaj.vm2.compiler.DumpState} class
:
* <pre> {@code
* Globals globals = JsePlatform.standardGlobals();
* Prototype p = globals.compilePrototype(new StringReader("print('hello, world')"), "main.lua");
* ByteArrayOutputStream o = new ByteArrayOutputStream();
* org.luaj.vm2.compiler.DumpState.dump(p, o, false);
* byte[] lua_binary_file_bytes = o.toByteArray();
* } </pre>
* 
* The {@link LoadState}'s default undumper {@link #instance}
* may be used directly to undump these bytes:
* <pre> {@code
* Prototypep = LoadState.instance.undump(new ByteArrayInputStream(lua_binary_file_bytes), "main.lua");
* LuaClosure c = new LuaClosure(p, globals);
* c.call();
* } </pre>
* 
* 
* More commonly, the {@link Globals.Undumper} may be used to undump them:
* <pre> {@code
* Prototype p = globals.loadPrototype(new ByteArrayInputStream(lua_binary_file_bytes), "main.lua", "b");
* LuaClosure c = new LuaClosure(p, globals);
* c.call();
* } </pre>
* 
* @see Globals.Compiler
* @see Globals.Undumper
* @see LuaClosure
* @see LuaFunction
* @see org.luaj.vm2.compiler.LuaC
* @see org.luaj.vm2.luajc.LuaJC
* @see Globals#compiler
* @see Globals#load(InputStream, String, LuaValue)
*/
public class LoadState {

	/** Shared instance of Globals.Undumper to use loading prototypes from binary lua files */
	public static final Globals.Undumper instance = new GlobalsUndumper();
	
	/** format corresponding to non-number-patched lua, all numbers are floats or doubles */
	public static final int NUMBER_FORMAT_FLOATS_OR_DOUBLES    = 0;

	/** format corresponding to non-number-patched lua, all numbers are ints */
	public static final int NUMBER_FORMAT_INTS_ONLY            = 1;
	
	/** format corresponding to number-patched lua, all numbers are 32-bit (4 byte) ints */
	public static final int NUMBER_FORMAT_NUM_PATCH_INT32      = 4;
	
	// type constants
	public static final int LUA_TINT            = (-2);
	public static final int LUA_TNONE			= (-1);
	public static final int LUA_TNIL			= 0;
	public static final int LUA_TBOOLEAN		= 1;
	public static final int LUA_TLIGHTUSERDATA	= 2;
	public static final int LUA_TNUMBER			= 3;
	public static final int LUA_TSTRING			= 4;
	public static final int LUA_TTABLE			= 5;
	public static final int LUA_TFUNCTION		= 6;
	public static final int LUA_TUSERDATA		= 7;
	public static final int LUA_TTHREAD			= 8;
	public static final int LUA_TVALUE          = 9;
	
	/** The character encoding to use for file encoding.  Null means the default encoding */
	public static String encoding = null;
	
	/** Signature byte indicating the file is a compiled binary chunk */
	public static final byte[] LUA_SIGNATURE	= { '\033', 'L', 'u', 'a' };

	/** Data to catch conversion errors */
	public static final byte[] LUAC_TAIL = { (byte) 0x19, (byte) 0x93, '\r', '\n', (byte) 0x1a, '\n', };
	

	/** Name for compiled chunks */
	public static final String SOURCE_BINARY_STRING = "binary string";


	/** for header of binary files -- this is Lua 5.5 */
	public static final int LUAC_VERSION		= 0x55;
	public static final int LUAC_VERSION_54	= 0x54;
	public static final int LUAC_VERSION_53	= 0x53;
	public static final int LUAC_VERSION_52	= 0x52;

	/** for header of binary files -- this is the official format */
	public static final int LUAC_FORMAT		= 0;

	/** size of header of binary files */
	public static final int LUAC_HEADERSIZE		= 12;

	public static final int LUA_TNUMFLT = LUA_TNUMBER;
	public static final int LUA_TSHRSTR = LUA_TSTRING;
	public static final int LUA_TNUMINT = LUA_TNUMBER | (1 << 4);
	public static final int LUA_TLNGSTR = LUA_TSTRING | (1 << 4);
	public static final long LUAC_INT = 0x5678;
	public static final double LUAC_NUM = 370.5;

	// values read from the header
	private int     luacVersion;
	private int     luacFormat;
	private boolean luacLittleEndian;
	private int     luacSizeofInt;
	private int     luacSizeofSizeT;
	private int     luacSizeofInstruction;
	private int     luacSizeofLuaInteger;
	private int     luacSizeofLuaNumber;
	private int 	luacNumberFormat;

	/** input stream from which we are loading */
	public final DataInputStream is;

	/** Name of what is being loaded? */
	String name;

	private static final LuaValue[]     NOVALUES    = {};
	private static final Prototype[] NOPROTOS    = {};
	private static final LocVars[]   NOLOCVARS   = {};
	private static final Upvaldesc[]  NOUPVALDESCS = {};
	private static final int[]       NOINTS      = {};
	
	/** Read buffer */
	private byte[] buf = new byte[512];

	/** Install this class as the standard Globals.Undumper for the supplied Globals */
	public static void install(Globals globals) {
		globals.undumper = instance;
	}

	private boolean isModernVersion() {
		return luacVersion == LUAC_VERSION || luacVersion == LUAC_VERSION_54 || luacVersion == LUAC_VERSION_53;
	}
	
	/** Load a 4-byte int value from the input stream
	 * @return the int value laoded.
	 **/
	int loadInt() throws IOException {
		is.readFully(buf,0,4);
		return luacLittleEndian?
				(buf[3] << 24) | ((0xff & buf[2]) << 16) | ((0xff & buf[1]) << 8) | (0xff & buf[0]):
				(buf[0] << 24) | ((0xff & buf[1]) << 16) | ((0xff & buf[2]) << 8) | (0xff & buf[3]);
	}
	
	/** Load an array of int values from the input stream
	 * @return the array of int values laoded.
	 **/
	int[] loadIntArray() throws IOException {
		int n = loadInt();
		if ( n == 0 )
			return NOINTS;
		
		// read all data at once
		int m = n << 2;
		if ( buf.length < m )
			buf = new byte[m];
		is.readFully(buf,0,m);
		int[] array = new int[n];
		for ( int i=0, j=0; i<n; ++i, j+=4 )
			array[i] = luacLittleEndian?
					(buf[j+3] << 24) | ((0xff & buf[j+2]) << 16) | ((0xff & buf[j+1]) << 8) | (0xff & buf[j+0]):
					(buf[j+0] << 24) | ((0xff & buf[j+1]) << 16) | ((0xff & buf[j+2]) << 8) | (0xff & buf[j+3]);

		return array;
	}
	
	/** Load a long  value from the input stream
	 * @return the long value laoded.
	 **/
	long loadInt64() throws IOException {
		int a,b;
		if ( this.luacLittleEndian ) {
			a = loadInt();
			b = loadInt();
		} else {
			b = loadInt();
			a = loadInt();
		}
		return (((long)b)<<32) | (((long)a)&0xffffffffL);
	}

	/** Load a lua strin gvalue from the input stream
	 * @return the {@link LuaString} value laoded.
	 **/
	LuaString loadString() throws IOException {
		if (isModernVersion()) {
			long size = is.readUnsignedByte();
			if (size == 0) {
				return null;
			}
			if (size == 0xFFL) {
				size = this.luacSizeofSizeT == 8 ? loadInt64() : (loadInt() & 0xffffffffL);
			}
			int len = (int) size - 1;
			byte[] bytes = new byte[len];
			is.readFully(bytes, 0, len);
			return LuaString.valueUsing(bytes);
		}
		int size = this.luacSizeofSizeT == 8? (int) loadInt64(): loadInt();
		if ( size == 0 )
			return null;
		byte[] bytes = new byte[size];
		is.readFully( bytes, 0, size );
		return LuaString.valueUsing( bytes, 0, bytes.length - 1 );
	}
	
	/**
	 * Convert bits in a long value to a {@link LuaValue}.
	 * @param bits long value containing the bits
	 * @return {@link LuaInteger} or {@link LuaDouble} whose value corresponds to the bits provided.
	 */
	public static LuaValue longBitsToLuaNumber( long bits ) {
		if ( ( bits & ( ( 1L << 63 ) - 1 ) ) == 0L ) {
			return LuaValue.ZERO;
		}
		
		int e = (int)((bits >> 52) & 0x7ffL) - 1023;
		
		if ( e >= 0 && e < 63 ) {
			long f = bits & 0xFFFFFFFFFFFFFL;
			int shift = 52 - e;
			long intPrecMask = shift > 0 ? ( 1L << shift ) - 1 : 0;
			if ( shift <= 52 && ( f & intPrecMask ) == 0 ) {
				long intValue = shift >= 0 ? (f >> shift) | (1L << e) : (f << (-shift)) | (1L << e);
				return LuaInteger.valueOf( ( ( bits >> 63 ) != 0 ) ? -intValue : intValue );
			}
		}
		
		return LuaValue.valueOf( Double.longBitsToDouble(bits) );
	}
	
	/**
	 * Load a number from a binary chunk
	 * @return the {@link LuaValue} loaded
	 * @throws IOException if an i/o exception occurs
	 */
	LuaValue loadNumber() throws IOException {
		if (isModernVersion()) {
			return LuaValue.valueOf(Double.longBitsToDouble(loadInt64()));
		}
		if ( luacNumberFormat == NUMBER_FORMAT_INTS_ONLY ) {
			return LuaInteger.valueOf( loadInt() );
		} else {
			return longBitsToLuaNumber( loadInt64() );
		}
	}

	LuaValue loadInteger() throws IOException {
		return LuaInteger.valueOf(luacSizeofLuaInteger == 8 ? loadInt64() : loadInt());
	}

	/**
	 * Load a list of constants from a binary chunk
	 * @param f the function prototype
	 * @throws IOException if an i/o exception occurs
	 */
	void loadConstants(Prototype f) throws IOException {
		int n = loadInt();
		LuaValue[] values = n>0? new LuaValue[n]: NOVALUES;
		for ( int i=0; i<n; i++ ) {
			int type = is.readByte();
			switch ( type ) {
			case LUA_TNIL:
				values[i] = LuaValue.NIL;
				break;
			case LUA_TBOOLEAN:
				values[i] = (0 != is.readUnsignedByte()? LuaValue.TRUE: LuaValue.FALSE);
				break;
			case LUA_TINT:
				values[i] = LuaInteger.valueOf( loadInt() );
				break;
			case LUA_TNUMINT:
				values[i] = loadInteger();
				break;
			case LUA_TNUMBER:
				values[i] = loadNumber();
				break;
			case LUA_TSTRING:
			case LUA_TLNGSTR:
				values[i] = loadString();
				break;
			default:
				throw new IllegalStateException("bad constant");
			}
		}
		f.k = values;
	}

	void loadProtos(Prototype f) throws IOException {
		int n = loadInt();
		Prototype[] protos = n>0? new Prototype[n]: NOPROTOS;
		for ( int i=0; i<n; i++ )
			protos[i] = loadFunction(f.source);
		f.p = protos;
	}


	void loadUpvalues(Prototype f) throws IOException {
		int n = loadInt();
		f.upvalues = n>0? new Upvaldesc[n]: NOUPVALDESCS;
		for (int i=0; i<n; i++) {
			boolean instack = is.readByte() != 0;
			int idx = ((int) is.readByte()) & 0xff;
			f.upvalues[i] = new Upvaldesc(null, instack, idx, false);
		}
	}

	/**
	 * Load the debug info for a function prototype
	 * @param f the function Prototype
	 * @throws IOException if there is an i/o exception
	 */
	void loadDebug( Prototype f ) throws IOException {
		if (!isModernVersion()) {
			f.source = loadString();
		}
		f.lineinfo = loadIntArray();
		int n = loadInt();
		f.locvars = n>0? new LocVars[n]: NOLOCVARS;
		for ( int i=0; i<n; i++ ) {
			LuaString varname = loadString();
			int startpc = loadInt();
			int endpc = loadInt();
			if (luacVersion == LUAC_VERSION || luacVersion == LUAC_VERSION_54) {
				int slot = loadInt();
				boolean toclose = is.readByte() != 0;
				boolean isconst = is.readByte() != 0;
				f.locvars[i] = new LocVars(varname, startpc, endpc, slot, toclose, isconst);
			} else {
				f.locvars[i] = new LocVars(varname, startpc, endpc);
			}
		}
		
		n = loadInt();
		for ( int i=0; i<n; i++ )
			f.upvalues[i].name = loadString();
	}

	/**
	 * Load a function prototype from the input stream
	 * @param p name of the source
	 * @return {@link Prototype} instance that was loaded
	 * @throws IOException
	 */
	public Prototype loadFunction(LuaString p) throws IOException {
		Prototype f = new Prototype();
		if (isModernVersion()) {
			f.source = loadString();
			if (f.source == null)
				f.source = p;
			f.linedefined = loadInt();
			f.lastlinedefined = loadInt();
		} else {
			f.linedefined = loadInt();
			f.lastlinedefined = loadInt();
		}
		f.numparams = is.readUnsignedByte();
		f.is_vararg = is.readUnsignedByte();
		f.maxstacksize = is.readUnsignedByte();
		if (luacVersion == LUAC_VERSION) {
			f.varargname = loadString();
		}
		f.code = loadIntArray();
		if (isModernVersion()) {
			loadConstants(f);
			loadUpvalues(f);
			loadProtos(f);
			loadDebug(f);
		} else {
			loadConstants(f);
			loadUpvalues(f);
			loadDebug(f);
		}
		
		// TODO: add check here, for debugging purposes, I believe
		// see ldebug.c
//		 IF (!luaG_checkcode(f), "bad code");
		
//		 this.L.pop();
		 return f;
	}

	/**
	 * Load the lua chunk header values.
	 * @throws IOException if an i/o exception occurs.
	 */
	public void loadHeader() throws IOException {
		luacVersion = is.readByte();
		luacFormat = is.readByte();
		if (isModernVersion()) {
			for (int i = 0; i < LUAC_TAIL.length; ++i)
				if (is.readByte() != LUAC_TAIL[i])
					throw new LuaError("unexpected byte in luac data, index=" + i);
			luacSizeofInt = is.readUnsignedByte();
			luacSizeofSizeT = is.readUnsignedByte();
			luacSizeofInstruction = is.readUnsignedByte();
			luacSizeofLuaInteger = is.readUnsignedByte();
			luacSizeofLuaNumber = is.readUnsignedByte();
			luacLittleEndian = true;
			long luacInt = luacSizeofLuaInteger == 8 ? loadInt64() : loadInt();
			double luacNum = Double.longBitsToDouble(loadInt64());
			if (luacInt == Long.reverseBytes(LUAC_INT) || Double.doubleToLongBits(luacNum) == Long.reverseBytes(Double.doubleToLongBits(LUAC_NUM))) {
				luacLittleEndian = false;
			} else if (luacInt != LUAC_INT || luacNum != LUAC_NUM) {
				throw new LuaError("incompatible binary chunk");
			}
			luacNumberFormat = NUMBER_FORMAT_FLOATS_OR_DOUBLES;
		} else {
			luacLittleEndian = (0 != is.readByte());
			luacSizeofInt = is.readByte();
			luacSizeofSizeT = is.readByte();
			luacSizeofInstruction = is.readByte();
			luacSizeofLuaNumber = is.readByte();
			luacNumberFormat = is.readByte();
			luacSizeofLuaInteger = luacSizeofLuaNumber;
			for (int i=0; i < LUAC_TAIL.length; ++i)
				if (is.readByte() != LUAC_TAIL[i])
					throw new LuaError("Unexpeted byte in luac tail of header, index="+i);
		}
	}

	/**
	 * Load input stream as a lua binary chunk if the first 4 bytes are the lua binary signature.
	 * @param stream InputStream to read, after having read the first byte already
	 * @param chunkname Name to apply to the loaded chunk
	 * @return {@link Prototype} that was loaded, or null if the first 4 bytes were not the lua signature.
	 * @throws IOException if an IOException occurs
	 */
	public static Prototype undump(InputStream stream, String chunkname) throws IOException {
		// check rest of signature
		if ( stream.read() != LUA_SIGNATURE[0]
		   || stream.read() != LUA_SIGNATURE[1]
	       || stream.read() != LUA_SIGNATURE[2]
		   || stream.read() != LUA_SIGNATURE[3] )
			return null;
		
		// load file as a compiled chunk
		String sname = getSourceName(chunkname);
		LoadState s = new LoadState( stream, sname );
		s.loadHeader();

		// check format
		if (s.isModernVersion()) {
			s.is.readUnsignedByte();
		} else {
			switch ( s.luacNumberFormat ) {
			case NUMBER_FORMAT_FLOATS_OR_DOUBLES:
			case NUMBER_FORMAT_INTS_ONLY:
			case NUMBER_FORMAT_NUM_PATCH_INT32:
				break;
			default:
				throw new LuaError("unsupported int size");
			}
		}
		return s.loadFunction( LuaString.valueOf(sname) );
	}
	
	/**
	 * Construct a source name from a supplied chunk name
	 * @param name String name that appears in the chunk
	 * @return source file name
	 */
    public static String getSourceName(String name) {
        String sname = name;
        if ( name.startsWith("@") || name.startsWith("=") )
			sname = name.substring(1);
		else if ( name.startsWith("\033") )
			sname = SOURCE_BINARY_STRING;
        return sname;
    }

	/** Private constructor for create a load state */
	private LoadState( InputStream stream, String name ) {
		this.name = name;
		this.is = new DataInputStream( stream );
	}
	
	private static final class GlobalsUndumper implements Globals.Undumper {
		public Prototype undump(InputStream stream, String chunkname)
				throws IOException {
			return LoadState.undump(stream,  chunkname);
		}
	}
}
