/*******************************************************************************
 * Copyright (c) 2012 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.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.Reader;

import org.luaj.vm2.libs.BaseLib;
import org.luaj.vm2.libs.DebugLib;
import org.luaj.vm2.libs.IoLib;
import org.luaj.vm2.libs.PackageLib;
import org.luaj.vm2.libs.ResourceFinder;

/**
 * Global environment used by luaj.  Contains global variables referenced by executing lua.
 * <p>
 * 
 * <h3>Constructing and Initializing Instances</h3>
 * Typically, this is constructed indirectly by a call to 
 * {@link org.luaj.vm2.libs.jse.JsePlatform#standardGlobals()} or
 * {@link org.luaj.vm2.libs.jme.JmePlatform#standardGlobals()},
 * and then used to load lua scripts for execution as in the following example. 
 * <pre> {@code
 * Globals globals = JsePlatform.standardGlobals();
 * globals.load( new StringReader("print 'hello'"), "main.lua" ).call(); 
 * } </pre>
 * The creates a complete global environment with the standard libraries loaded.
 * <p>
 * For specialized circumstances, the Globals may be constructed directly and loaded
 * with only those libraries that are needed, for example.
 * <pre> {@code
 * Globals globals = new Globals();
 * globals.load( new BaseLib() ); 
 * } </pre>
 * 
 * <h3>Loading and Executing Lua Code</h3>
 * Globals contains convenience functions to load and execute lua source code given a Reader. 
 * A simple example is:
 * <pre> {@code
 * globals.load( new StringReader("print 'hello'"), "main.lua" ).call(); 
 * } </pre>
 * 
 * <h3>Fine-Grained Control of Compiling and Loading Lua</h3>
 * Executable LuaFunctions are created from lua code in several steps
 * <ul>
 * <li>find the resource using the platform's {@link ResourceFinder}
 * <li>compile lua to lua bytecode using {@link Compiler}
 * <li>load lua bytecode to a {@link Prototype} using {@link Undumper}
 * <li>construct {@link LuaClosure} from {@link Prototype} with {@link Globals} using {@link Loader}
 * </ul>
 * <p>
 * There are alternate flows when the direct lua-to-Java bytecode compiling {@link org.luaj.vm2.luajc.LuaJC} is used.
 * <ul>
 * <li>compile lua to lua bytecode using {@link Compiler} or load precompiled code using {@link Undumper}
 * <li>convert lua bytecode to equivalent Java bytecode using {@link org.luaj.vm2.luajc.LuaJC} that implements {@link Loader} directly
 * </ul>
 * 
 * <h3>Java Field</h3>
 * Certain public fields are provided that contain the current values of important global state:
 * <ul>
 * <li>{@link #STDIN} Current value for standard input in the laaded {@link IoLib}, if any.
 * <li>{@link #STDOUT} Current value for standard output in the loaded {@link IoLib}, if any.
 * <li>{@link #STDERR} Current value for standard error in the loaded {@link IoLib}, if any.
 * <li>{@link #finder} Current loaded {@link ResourceFinder}, if any.
 * <li>{@link #compiler} Current loaded {@link Compiler}, if any.
 * <li>{@link #undumper} Current loaded {@link Undumper}, if any.
 * <li>{@link #loader} Current loaded {@link Loader}, if any.
 * </ul>
 * 
 * <h3>Lua Environment Variables</h3>
 * When using {@link org.luaj.vm2.libs.jse.JsePlatform} or {@link org.luaj.vm2.libs.jme.JmePlatform},
 * these environment variables are created within the Globals.
 * <ul>
 * <li>"_G" Pointer to this Globals.
 * <li>"_VERSION" String containing the version of luaj.
 * </ul>
 * 
 * <h3>Use in Multithreaded Environments</h3>
 * In a multi-threaded server environment, each server thread should create one Globals instance, 
 * which will be logically distinct and not interfere with each other, but share certain 
 * static immutable resources such as class data and string data.
 * <p>
 * 
 * @see org.luaj.vm2.libs.jse.JsePlatform
 * @see org.luaj.vm2.libs.jme.JmePlatform
 * @see LuaValue
 * @see Compiler
 * @see Loader
 * @see Undumper
 * @see ResourceFinder
 * @see org.luaj.vm2.compiler.LuaC
 * @see org.luaj.vm2.luajc.LuaJC
 */
public class Globals extends LuaTable {

	/** The current default input stream. */
	public InputStream STDIN  = null;

	/** The current default output stream. */
	public PrintStream STDOUT = System.out;

	/** The current default error stream. */
	public PrintStream STDERR = System.err;

	/** The installed ResourceFinder for looking files by name. */
	public ResourceFinder finder;
	
	/** The currently running thread.  Should not be changed by non-library code. */
	public LuaThread running = new LuaThread(this);

	/** The BaseLib instance loaded into this Globals */
	public BaseLib baselib;
	
	/** The PackageLib instance loaded into this Globals */
	public PackageLib package_;
	
	/** The DebugLib instance loaded into this Globals, or null if debugging is not enabled */
	public DebugLib debuglib;

	/** Interface for module that converts a Prototype into a LuaFunction with an environment. */
	public interface Loader {
		/** Convert the prototype into a LuaFunction with the supplied environment. */
		LuaFunction load(Prototype prototype, String chunkname, LuaValue env) throws IOException;
	}

	/** Interface for module that converts lua source text into a prototype. */
	public interface Compiler {
		/** Compile lua source into a Prototype. The InputStream is assumed to be in UTF-8. */
		Prototype compile(InputStream stream, String chunkname) throws IOException;
	}

	/** Interface for module that loads lua binary chunk into a prototype. */
	public interface Undumper {
		/** Load the supplied input stream into a prototype. */
		Prototype undump(InputStream stream, String chunkname) throws IOException;
	}
	
	/** Check that this object is a Globals object, and return it, otherwise throw an error. */
	public Globals checkglobals() {
		return this;
	}
	
	/** The installed loader. 
	 * @see Loader */
	public Loader loader;

	/** The installed compiler.
	 * @see Compiler */
	public Compiler compiler;

	/** The installed undumper.
	 * @see Undumper */
	public Undumper undumper;

	/** Convenience function for loading a file that is either binary lua or lua source.
	 * @param filename Name of the file to load.
	 * @return LuaValue that can be call()'ed or invoke()'ed.
	 * @throws LuaError if the file could not be loaded.
	 */
	public LuaValue loadfile(String filename) {
		InputStream is = null;
		try {
			is = finder.findResource(filename);
			if (is == null) {
				return error("cannot open " + filename + ": No such file or directory");
			}
			return load(is, "@"+filename, "bt", this);
		} catch (Exception e) {
			return error("load "+filename+": "+e);
		} finally {
			if (is != null) {
				try {
					is.close();
				} catch (Exception ignored) {
				}
			}
		}
	}

	/** Convenience function to load a string value as a script.  Must be lua source.
	 * @param script Contents of a lua script, such as "print 'hello, world.'"
	 * @param chunkname Name that will be used within the chunk as the source.
	 * @return LuaValue that may be executed via .call(), .invoke(), or .method() calls.
	 * @throws LuaError if the script could not be compiled.
	 */
	public LuaValue load(String script, String chunkname) {
		return load(new StrReader(script), chunkname);
	}
	
	/** Convenience function to load a string value as a script.  Must be lua source.
	 * @param script Contents of a lua script, such as "print 'hello, world.'"
	 * @return LuaValue that may be executed via .call(), .invoke(), or .method() calls.
	 * @throws LuaError if the script could not be compiled.
	 */
	public LuaValue load(String script) {
		return load(new StrReader(script), script);
	}

	/** Convenience function to load a string value as a script with a custom environment.
	 * Must be lua source.
	 * @param script Contents of a lua script, such as "print 'hello, world.'"
	 * @param chunkname Name that will be used within the chunk as the source.
	 * @param environment LuaTable to be used as the environment for the loaded function.
	 * @return LuaValue that may be executed via .call(), .invoke(), or .method() calls.
	 * @throws LuaError if the script could not be compiled.
	 */
	public LuaValue load(String script, String chunkname, LuaTable environment) {
		return load(new StrReader(script), chunkname, environment);
	}

	/** Load the content form a reader as a text file.  Must be lua source. 
	 * The source is converted to UTF-8, so any characters appearing in quoted literals 
	 * above the range 128 will be converted into multiple bytes.  
	 * @param reader Reader containing text of a lua script, such as "print 'hello, world.'"
	 * @param chunkname Name that will be used within the chunk as the source.
	 * @return LuaValue that may be executed via .call(), .invoke(), or .method() calls.
	 * @throws LuaError if the script could not be compiled.
	 */ 
	public LuaValue load(Reader reader, String chunkname) {
		return load(new UTF8Stream(reader), chunkname, "t", this);
	}

	/** Load the content form a reader as a text file, supplying a custom environment.  
	 * Must be lua source. The source is converted to UTF-8, so any characters 
	 * appearing in quoted literals above the range 128 will be converted into
	 * multiple bytes. 
	 * @param reader Reader containing text of a lua script, such as "print 'hello, world.'"
	 * @param chunkname Name that will be used within the chunk as the source.
	 * @param environment LuaTable to be used as the environment for the loaded function.
	 * @return LuaValue that may be executed via .call(), .invoke(), or .method() calls.
	 * @throws LuaError if the script could not be compiled.
	 */ 
	public LuaValue load(Reader reader, String chunkname, LuaTable environment) {
		return load(new UTF8Stream(reader), chunkname, "t", environment);
	}	

	/** Load the content form an input stream as a binary chunk or text file. 
	 * @param is InputStream containing a lua script or compiled lua"
	 * @param chunkname Name that will be used within the chunk as the source.
	 * @param mode String containing 'b' or 't' or both to control loading as binary or text or either.
	 * @param environment LuaTable to be used as the environment for the loaded function.
	 * */
	public LuaValue load(InputStream is, String chunkname, String mode, LuaValue environment) {
		try {
			Prototype p = loadPrototype(is, chunkname, mode);
			return loader.load(p, chunkname, environment);
		} catch (LuaError l) {
			throw l;
		} catch (Exception e) {
			return error("load "+chunkname+": "+e);
		}
	}

	/** Load lua source or lua binary from an input stream into a Prototype. 
	 * The InputStream is either a binary lua chunk starting with the lua binary chunk signature, 
	 * or a text input file.  If it is a text input file, it is interpreted as a UTF-8 byte sequence.  
	 * @param is Input stream containing a lua script or compiled lua"
	 * @param chunkname Name that will be used within the chunk as the source.
	 * @param mode String containing 'b' or 't' or both to control loading as binary or text or either.
	 */
	public Prototype loadPrototype(InputStream is, String chunkname, String mode) throws IOException {
		if (mode.indexOf('b') >= 0) {
			if (undumper == null)
				error("No undumper.");
			if (!is.markSupported())
				is = new BufferedStream(is);
			is.mark(4);
			final Prototype p = undumper.undump(is, chunkname);
			if (p != null)
				return p;
			is.reset();
		}
		if (mode.indexOf('t') >= 0) {
			return compilePrototype(is, chunkname);
		}
		error("Failed to load prototype "+chunkname+" using mode '"+mode+"'");
		return null;
	}
	
	/** Compile lua source from a Reader into a Prototype. The characters in the reader 
	 * are converted to bytes using the UTF-8 encoding, so a string literal containing 
	 * characters with codepoints 128 or above will be converted into multiple bytes. 
	 */
	public Prototype compilePrototype(Reader reader, String chunkname) throws IOException {
		return compilePrototype(new UTF8Stream(reader), chunkname);
	}
	
	/** Compile lua source from an InputStream into a Prototype. 
	 * The input is assumed to be UTf-8, but since bytes in the range 128-255 are passed along as 
	 * literal bytes, any ASCII-compatible encoding such as ISO 8859-1 may also be used.  
	 */
	public Prototype compilePrototype(InputStream stream, String chunkname) throws IOException {
		if (compiler == null)
			error("No compiler.");
		return compiler.compile(stream, chunkname);
	}

	/** Function which yields the current thread. 
	 * @param args  Arguments to supply as return values in the resume function of the resuming thread.
	 * @return Values supplied as arguments to the resume() call that reactivates this thread.
	 */
	public Varargs yield(Varargs args) {
		if (running == null || running.isMainThread())
			throw new LuaError("cannot yield main thread");
		final LuaThread.State s = running.state;
		return s.lua_yield(args);
	}

	/** Reader implementation to read chars from a String in JME or JSE. */
	static class StrReader extends Reader {
		final String s;
		int i = 0;
		final int n;
		StrReader(String s) {
			this.s = s;
			n = s.length();
		}
		public void close() throws IOException {
			i = n;
		}
		public int read() throws IOException {
			return i < n ? s.charAt(i++) : -1;
		}
		public int read(char[] cbuf, int off, int len) throws IOException {
			int j = 0;
			for (; j < len && i < n; ++j, ++i)
				cbuf[off+j] = s.charAt(i);
			return j > 0 || len == 0 ? j : -1;
		}
	}

	/* Abstract base class to provide basic buffered input storage and delivery.
	 * This class may be moved to its own package in the future.
	 */
	abstract static class AbstractBufferedStream extends InputStream {
		protected byte[] b;
		protected int i = 0, j = 0;
		protected AbstractBufferedStream(int buflen) {
			this.b = new byte[buflen];
		}
		abstract protected int avail() throws IOException;
		public int read() throws IOException {
			int a = avail();
			return (a <= 0 ? -1 : 0xff & b[i++]);
		}
		public int read(byte[] b) throws IOException {
			return read(b, 0, b.length);
		}
		public int read(byte[] b, int i0, int n) throws IOException {
			int a = avail();
			if (a <= 0) return -1;
			final int n_read = Math.min(a, n);
			System.arraycopy(this.b,  i,  b,  i0,  n_read);
			i += n_read;
			return n_read;
		}
		public long skip(long n) throws IOException {
			final long k = Math.min(n, j - i);
			i += k;
			return k;
		}		
		public int available() throws IOException {
			return j - i;
		}
	}

	/**  Simple converter from Reader to InputStream using UTF8 encoding that will work
	 * on both JME and JSE.
	 * This class may be moved to its own package in the future.
	 */
	static class UTF8Stream extends AbstractBufferedStream {
		private final char[] c = new char[32];
		private final Reader r;
		UTF8Stream(Reader r) {
			super(96);
			this.r = r;
		}
		protected int avail() throws IOException {
			if (i < j) return j - i;
			int n = r.read(c);
			if (n < 0)
				return -1;
			if (n == 0) {
				int u = r.read();
				if (u < 0)
					return -1;
				c[0] = (char) u;
				n = 1;
			}
			j = LuaString.encodeToUtf8(c, n, b, i = 0);
			return j;
		}
		public void close() throws IOException {
			r.close();
		}
	}
	
	/** Simple buffered InputStream that supports mark.
	 * Used to examine an InputStream for a 4-byte binary lua signature, 
	 * and fall back to text input when the signature is not found,
	 * as well as speed up normal compilation and reading of lua scripts.
	 * This class may be moved to its own package in the future.
	 */
	static class BufferedStream extends AbstractBufferedStream {
		private final InputStream s;
		public BufferedStream(InputStream s) {
			this(128, s);
		}
		BufferedStream(int buflen, InputStream s) {
			super(buflen);
			this.s = s;
		}
		protected int avail() throws IOException {
			if (i < j) return j - i;
			if (j >= b.length) i = j = 0;
			// leave previous bytes in place to implement mark()/reset().
			int n = s.read(b, j, b.length - j);
			if (n < 0)
				return -1;
			if (n == 0) {
				int u = s.read();
				if (u < 0)
					return -1;
				b[j] = (byte) u;
				n = 1;
			}
			j += n;
			return n;
		}
		public void close() throws IOException {
			s.close();
		}
		public synchronized void mark(int n) {
			if (i > 0 || n > b.length) {
				byte[] dest = n > b.length ? new byte[n] : b;
				System.arraycopy(b, i, dest, 0, j - i);
				j -= i;
				i = 0;
				b = dest;
			}
		}
		public boolean markSupported() {
			return true;
		}
		public synchronized void reset() throws IOException {
			i = 0;
		}
	}
}
