/*******************************************************************************
 * 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.vm3;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.Reader;

import org.luaj.vm3.lib.BaseLib;
import org.luaj.vm3.lib.DebugLib;
import org.luaj.vm3.lib.PackageLib;
import org.luaj.vm3.lib.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 JsePlatform.standardGlobasl()} or {@link 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 LuaPrototpye} 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 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 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 IoLib, if any.
 * <li>{@link STDOUT} Current value for standard output in the loaded IoLib, if any.
 * <li>{@link STDERR} Current value for standard error in the loaded 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 JsePlatform} or {@link 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.vm3.lib.jse.JsePlatform
 * @see org.luaj.vm3.lib.jme.JmePlatform
 * @see LuaValue
 * @see Compiler
 * @see Loader
 * @see Undumper
 * @see ResourceFinder
 * @see LuaC
 * @see 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) {
		try {
			return load(finder.findResource(filename), "@"+filename, "bt", this);
		} catch (Exception e) {
			return error("load "+filename+": "+e);
		}
	}

	/** 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);
	}
	
	/** 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.  */
	public LuaValue load(Reader reader, String chunkname) {
		return load(new UTF8Stream(reader), chunkname, "t", this);
	}

	/** Load the content form an input stream as a binary chunk or text file. */
	public LuaValue load(InputStream is, String chunkname, String mode, LuaValue env) {
		try {
			Prototype p = loadPrototype(is, chunkname, mode);
			return loader.load(p, chunkname, env);
		} 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.  
	 */
	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 MarkStream(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, n;
		StrReader(String s) {
			this.s = s;
			n = s.length();
		}
		public void close() throws IOException {
			i = n;
		}
		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;
		}
	}

	/**  Simple converter from Reader to InputStream using UTF8 encoding that will work
	 * on both JME and JSE.
	 */
	static class UTF8Stream extends InputStream {
		final char[] c = new char[32];
		final byte[] b = new byte[96];
		int i = 0, j = 0;
		final Reader r;
		UTF8Stream(Reader r) {
			this.r = r;
		}
		public int read() throws IOException {
			if (i < j)
				return c[i++];
			int n = r.read(c);
			if (n < 0)
				return -1;
			j = LuaString.encodeToUtf8(c, n, b, i = 0);
			return b[i++];
		}
	}
	
	/** Simple 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.
	 */
	static class MarkStream extends InputStream {
		private int[] b;
		private int i = 0, j = 0;
		private final InputStream s;
		MarkStream(InputStream s) {
			this.s = s;
		}
		public int read() throws IOException {
			if (i < j)
				return b[i++];
			final int c = s.read();
			if (c < 0)
				return -1;
			if (j < b.length) {
				b[j++] = c;
				i = j;
			}
			return c;
		}
		public synchronized void mark(int n) {
			b = new int[n];
			i = j = 0;
		}
		public boolean markSupported() {
			return true;
		}
		public synchronized void reset() throws IOException {
			i = 0;
		}
	}

}
