/*
 * RubyRuntime.java - No description
 * Created on 09. November 2001, 15:47
 *
 * Copyright (C) 2001, 2002 Jan Arne Petersen, Alan Moore, Benoit Cerrina
 * Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Alan Moore <alan_moore@gmx.net>
 * Benoit Cerrina <b.cerrina@wanadoo.fr>
 *
 * JRuby - http://jruby.sourceforge.net
 *
 * This file is part of JRuby
 *
 * JRuby is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * JRuby is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with JRuby; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */
package org.jruby.runtime;

import org.ablaf.ast.INode;
import org.ablaf.common.ISourcePosition;
import org.jruby.exceptions.*;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.*;

import java.io.*;

/**
 * @version $Revision: 1.39 $
 * @author  jpetersen
 */
public class RubyRuntime {
	private static final int TRACE_HEAD = 8;
	private static final int TRACE_TAIL = 5;
	private static final int TRACE_MAX = TRACE_HEAD + TRACE_TAIL + 5;

	private Ruby ruby;

	private RubyProc traceFunction;
	private boolean tracing = false;

	public RubyRuntime(Ruby ruby) {
		this.ruby = ruby;
	}

	/** Call the current method in the superclass of the current object
	 *
	 */
	public IRubyObject callSuper(IRubyObject[] args) {
		if (ruby.getCurrentFrame().getLastClass() == null) {
			throw new NameError(ruby, "superclass method '" + ruby.getCurrentFrame().getLastFunc() + "' must be enabled by enableSuper().");
		}

		ruby.getIterStack().push(ruby.getCurrentIter().isNot() ? Iter.ITER_NOT : Iter.ITER_PRE);

		try {
			return ruby.getCurrentFrame().getLastClass().getSuperClass().call(
						ruby.getCurrentFrame().getSelf(),
						ruby.getCurrentFrame().getLastFunc(),
						args,
						CallType.SUPER);
		} finally {
			ruby.getIterStack().pop();
		}
	}

	/** This method compiles and interprets a Ruby script.
	 *
	 *  It can be used if you want to use JRuby as a Macro language.
	 *
	 */
	public void loadScript(RubyString scriptName, RubyString source, boolean wrap) {
		IRubyObject self = ruby.getTopSelf();
		Namespace savedNamespace = ruby.getNamespace();

		ruby.pushDynamicVars();

		RubyModule wrapper = ruby.getWrapper();
		ruby.setNamespace(ruby.getTopNamespace());

		if (!wrap) {
			ruby.secure(4); /* should alter global state */
			ruby.pushClass(ruby.getClasses().getObjectClass());
			ruby.setWrapper(null);
		} else {
			/* load in anonymous module as toplevel */
			ruby.setWrapper(RubyModule.newModule(ruby));
			ruby.pushClass(ruby.getWrapper());
			self = ruby.getTopSelf().rbClone();
			self.extendObject(ruby.getRubyClass());
			ruby.setNamespace(new Namespace(ruby.getWrapper(), ruby.getNamespace()));
		}

		String last_func = ruby.getCurrentFrame().getLastFunc();

		ruby.getFrameStack().push();
		ruby.getCurrentFrame().setLastFunc(null);
		ruby.getCurrentFrame().setLastClass(null);
		ruby.getCurrentFrame().setSelf(self);
		ruby.getCurrentFrame().setNamespace(new Namespace(ruby.getRubyClass(), null));
		ruby.getScope().push();

		/* default visibility is private at loading toplevel */
		ruby.setCurrentVisibility(Visibility.PRIVATE);

		try {
			INode node = ruby.parse(source.toString(), scriptName.getValue());
			self.eval(node);

		} finally {
			ruby.getCurrentFrame().setLastFunc(last_func);
			ruby.setNamespace(savedNamespace);
			ruby.getScope().pop();
			ruby.getFrameStack().pop();
			ruby.popClass();
            ruby.popDynamicVars();
            ruby.setWrapper(wrapper);
		}
	}

	/** This method loads, compiles and interprets a Ruby file.
	 *  It is used by Kernel#require.
	 *
	 *  (matz Ruby: rb_load)
	 */
	public void loadFile(File iFile, boolean wrap) {
		if (iFile == null) {
			throw new RuntimeException("No such file to load");
		}

		try {
			StringBuffer source = new StringBuffer((int) iFile.length());
			BufferedReader br = new BufferedReader(new FileReader(iFile));
			String line;
			while ((line = br.readLine()) != null) {
				source.append(line).append('\n');
			}
			br.close();

			loadScript(new RubyString(ruby, iFile.getPath()), new RubyString(ruby, source.toString()), wrap);

		} catch (IOException ioExcptn) {
            throw IOError.fromException(ruby, ioExcptn);
		}
	}

	public void loadFile(RubyString fname, boolean wrap) {
		loadFile(new File(fname.getValue()), wrap);
	}

    /** Call the traceFunction
     *
     * MRI: eval.c - call_trace_func
     *
     */
    public synchronized void callTraceFunction(String event,
                                               ISourcePosition position,
                                               IRubyObject self,
                                               String name,
                                               IRubyObject type) {
        callTraceFunction(event, position.getFile(), position.getLine(), self, name, type);
    }

    public synchronized void callTraceFunction(String event,
                                               String file,
                                               int line,
                                               IRubyObject self,
                                               String name,
                                               IRubyObject type) {
        if (!tracing && traceFunction != null) {
            tracing = true;

            // XXX

            if (file == null) {
                file = "(ruby)";
            }
            if (type == null)
                type = ruby.getFalse();

            ruby.getFrameStack().push();
            try {
                traceFunction
                    .call(new IRubyObject[] {
                        RubyString.newString(ruby, event),
                        RubyString.newString(ruby, file),
                        RubyFixnum.newFixnum(ruby, line),
                        RubySymbol.newSymbol(ruby, name),
                        self,
                    // XXX
                    type });
            } finally {
                ruby.getFrameStack().pop();
                tracing = false;

                // XXX
            }
        }
    }

	/** Prints an error with backtrace to the error stream.
	 *
	 * MRI: eval.c - error_print()
	 *
	 */
	public void printError(RubyException excp) {
		if (excp == null || excp.isNil()) {
			return;
		}

		RubyArray backtrace = (RubyArray) excp.callMethod("backtrace");

		if (backtrace.isNil()) {
			if (ruby.getSourceFile() != null) {
				getErrorStream().print(ruby.getSourceFile() + ':' + ruby.getSourceLine());
			} else {
				getErrorStream().print(ruby.getSourceLine());
			}
		} else if (backtrace.getLength() == 0) {
			printErrorPos();
		} else {
			IRubyObject mesg = backtrace.entry(0);

			if (mesg.isNil()) {
				printErrorPos();
			} else {
				getErrorStream().print(mesg);
			}
		}

		RubyClass type = excp.getInternalClass();
		String info = excp.toString();

		if (type == ruby.getExceptions().getRuntimeError() && (info == null || info.length() == 0)) {
			getErrorStream().print(": unhandled exception\n");
		} else {
			String path = type.getClassPath().toString();

			if (info.length() == 0) {
				getErrorStream().print(": " + path + '\n');
			} else {
				if (path.startsWith("#")) {
					path = null;
				}

				String tail = null;
				if (info.indexOf("\n") != -1) {
					tail = info.substring(info.indexOf("\n") + 1);
					info = info.substring(0, info.indexOf("\n"));
				}

				getErrorStream().print(": " + info);

				if (path != null) {
					getErrorStream().print(" (" + path + ")\n");
				}

				if (tail != null) {
					getErrorStream().print(tail + '\n');
				}
			}
		}

		if (!backtrace.isNil()) {
			IRubyObject[] elements = backtrace.toJavaArray();

			for (int i = 0; i < elements.length; i++) {
				if (elements[i] instanceof RubyString) {
					getErrorStream().print("\tfrom " + elements[i] + '\n');
				}

				if (i == TRACE_HEAD && elements.length > TRACE_MAX) {
					getErrorStream().print("\t ... " + (elements.length - TRACE_HEAD - TRACE_TAIL) + "levels...\n");
					i = elements.length - TRACE_TAIL;
				}
			}
		}
	}

	private void printErrorPos() {
		if (ruby.getSourceFile() != null) {
			if (ruby.getCurrentFrame().getLastFunc() != null) {
				getErrorStream().print(ruby.getSourceFile() + ':' + ruby.getSourceLine());
				getErrorStream().print(":in '" + ruby.getCurrentFrame().getLastFunc() + '\'');
			} else if (ruby.getSourceLine() != 0) {
				getErrorStream().print(ruby.getSourceFile() + ':' + ruby.getSourceLine());
			} else {
				getErrorStream().print(ruby.getSourceFile());
			}
		}
	}

	/**
	 * Gets the errorStream
	 * @return Returns a PrintStream
	 */
	public PrintStream getErrorStream() {
		return new PrintStream(((RubyIO) ruby.getGlobalVar("$stderr")).getOutStream());
	}

	/**
	 * Sets the errorStream
	 * @param errorStream The errorStream to set
	 */
	public void setErrorStream(PrintStream errorStream) {
		ruby.setGlobalVar("$stderr", RubyIO.stderr(ruby, ruby.getClasses().getIoClass(), errorStream));
	}

	/**
	 * Gets the inputStream
	 * @return Returns a InputStream
	 */
	public InputStream getInputStream() {
		return ((RubyIO) ruby.getGlobalVar("$stdin")).getInStream();
	}

	/**
	 * Sets the inputStream
	 * @param inputStream The inputStream to set
	 */
	public void setInputStream(InputStream inputStream) {
		ruby.setGlobalVar("$stdin", RubyIO.stdin(ruby, ruby.getClasses().getIoClass(), inputStream));
	}

	/**
	 * Gets the outputStream
	 * @return Returns a PrintStream
	 */
	public PrintStream getOutputStream() {
		return new PrintStream(((RubyIO) ruby.getGlobalVar("$stdout")).getOutStream());
	}

	/**
	 * Sets the outputStream
	 * @param outStream The outputStream to set
	 */
	public void setOutputStream(PrintStream outStream) {
		IRubyObject stdout = RubyIO.stdout(ruby, ruby.getClasses().getIoClass(), outStream);
		if (ruby.getGlobalVar("$stdout") == ruby.getGlobalVar("$>")) {
			ruby.setGlobalVar("$>", stdout);
		}
		ruby.setGlobalVar("$stdout", stdout);
	}

	/**
	 * Gets the traceFunction.
	 * @return Returns a RubyProc
	 */
	public RubyProc getTraceFunction() {
		return traceFunction;
	}

	/**
	 * Sets the traceFunction.
	 * @param traceFunction The traceFunction to set
	 */
	public void setTraceFunction(RubyProc traceFunction) {
		this.traceFunction = traceFunction;
	}
}
