/*
 * Copyright 2013 Yuichiro Moriguchi
 *
 * Licensed 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 net.morilib.sh;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.PushbackReader;
import java.io.UnsupportedEncodingException;
import java.util.Iterator;

import net.morilib.sh.builtin.ShBuiltInCommands;
import net.morilib.sh.file.ShFileSystemFactory;
import net.morilib.sh.misc.ConsoleInputStream;
import net.morilib.sh.misc.XtraceStream;
import net.morilib.unix.misc.OptionIterator;

public final class Sh {

	/**
	 * 
	 */
	public static final String NAME = "jsh";

	/**
	 * 
	 */
	public static final ShErrorHandler
	INTERACTIVE = new ShErrorHandler() {

		public void handle(ShError e) {
			e.printStackTrace();
			System.err.println("Syntax error");
		}

	};

	private static void exechandle(ShErrorHandler h, ShError e) {
		if(h == null) {
			throw new RuntimeException((Throwable)e);
		}
		h.handle(e);
	}

	/**
	 * 
	 * @param env
	 * @param fs
	 * @param cmds
	 * @param run
	 * @param stdin
	 * @param stdout
	 * @param stderr
	 * @param prompt
	 * @return
	 * @throws IOException
	 */
	public static int interactive(ShEnvironment env,
			ShFileSystem fs,
			ShBuiltInCommands cmds,
			ShRuntime run,
			InputStream stdin,
			PrintStream stdout,
			PrintStream stderr,
			XtraceStream prompt,
			ShPromptReader p) throws IOException {
		ShLexer r;
		ShToken t;
		ShTree x;
		String z;
		int a = 0;

		r = new ShLexer(env, p, prompt);
		while(true) {
			try {
				while((t = r.nextToken()) == ShToken.NEWLINE);
				x = ShParser.parseCommand(r, t);
				a = x.eval(env, fs, cmds, run, stdin, stdout, stderr,
						prompt);
				if(r.isEof())  return a;
				r.resetPrompt();
			} catch(IOException e) {
				if((z = env.getTrap(ShSignal.IO)) != null) {
					try {
						ShRuntime.getInstance().eval(env, fs, stdin,
								stdout, stderr, prompt, z);
					} catch(ShCommandNotFoundException e1) {
						stderr.print(NAME);
						stderr.print(": command not found: ");
						stderr.println(e1.getName());
					} catch(IOException e1) {
						//e.printStackTrace(System.err);
						stderr.print(NAME);
						stderr.println(": IO error");
					} catch(ShSyntaxException e1) {
						exechandle(INTERACTIVE, e1);
					}
				}
				//e.printStackTrace(System.err);
				stderr.print(NAME);
				stderr.println(": IO error");
			} catch(ShCommandNotFoundException e) {
				stderr.print(NAME);
				stderr.print(": command not found: ");
				stderr.println(e.getName());
			} catch(ShSyntaxException e) {
				exechandle(INTERACTIVE, e);
			} catch(ShRuntimeException e) {
				exechandle(INTERACTIVE, e);
			}
		}
	}

	/**
	 * 
	 * @param env
	 * @param fs
	 * @param cmds
	 * @param run
	 * @param stdin
	 * @param stdout
	 * @param stderr
	 * @param prompt
	 * @return
	 * @throws IOException
	 */
	public static int interactive(ShEnvironment env,
			ShFileSystem fs,
			ShBuiltInCommands cmds,
			ShRuntime run,
			InputStream stdin,
			PrintStream stdout,
			PrintStream stderr,
			XtraceStream prompt) throws IOException {
		return interactive(env, fs, cmds, run, stdin, stdout, stderr,
				prompt, new ShPromptReader(env, prompt));
	}

	/**
	 * 
	 * @param env
	 * @param fs
	 * @param cmds
	 * @param run
	 * @param lex
	 * @param prompt
	 * @param errhandle
	 * @param in
	 * @param out
	 * @param err
	 * @return
	 */
	public static int interactive(ShEnvironment env, ShFileSystem fs,
			ShBuiltInCommands cmds, ShRuntime run,
			InputStream lex, ShErrorHandler errhandle,
			InputStream in, PrintStream out, PrintStream err,
			XtraceStream q) {
		try {
			execute(env, fs, cmds, run, fs.getProfile(), in, out,
					err, q);
			return interactive(env, fs, cmds, run, in, out, err, q);
		} catch(ShExitException e) {
			return e.getCode();
		} catch(IOException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 
	 * @param env
	 * @param fs
	 * @param cmds
	 * @param run
	 * @param script
	 * @param in
	 * @param out
	 * @param err
	 * @param prompt
	 * @return
	 */
	public static int execute(ShEnvironment env,
			ShFileSystem fs, ShBuiltInCommands cmds, ShRuntime run,
			InputStream script, InputStream in,
			PrintStream out, PrintStream err, XtraceStream prompt) {
		PushbackReader f = null;
		ShTree x;
		int z = 0;

		try {
			if(script == null)  return 0;
			x = ShParser.parse(script, env.getCharset());
			z = x.eval(env, fs, cmds, run, in, out, err, prompt);
		} catch(IOException e) {
			err.print(NAME);
			err.println(": IO Error");
		} catch(ShCommandNotFoundException e) {
			err.print(NAME);
			err.print(": command not found: ");
			err.println(e.getName());
		} catch(ShSyntaxException e) {
			exechandle(INTERACTIVE, e);
		} catch(ShRuntimeException e) {
			exechandle(INTERACTIVE, e);
		} finally {
			if(f != null) {
				try {
					f.close();
				} catch (IOException e1) {
					throw new RuntimeException(e1);
				}
			}
		}
		return z;
	}

	/**
	 * 
	 * @param env
	 * @param fs
	 * @param cmds
	 * @param run
	 * @param script
	 * @param in
	 * @param out
	 * @param err
	 * @param prompt
	 * @return
	 */
	public static int executeScript(ShEnvironment env,
			ShFileSystem fs, ShBuiltInCommands cmds, ShRuntime run,
			InputStream script, InputStream in,
			PrintStream out, PrintStream err,
			XtraceStream prompt
			) throws IOException, ShSyntaxException {
		ShTree x;
		int z = 0;

		if(script == null)  return 0;
		x = ShParser.parse(script, env.getCharset());
		z = x.eval(env, fs, cmds, run, in, out, err, prompt);
		return z;
	}

	private static void _setarg(ShEnvironment n, Iterator<String> a) {
		for(int i = 1; a.hasNext(); i++) {
			n.put(i + "", a.next());
		}
	}

	/**
	 * 
	 * @param args
	 */
	@SuppressWarnings("resource")
	public static int invoke(String[] args) {
		ShBuiltInCommands b = ShBuiltInCommands.getInstance();
		ShFileSystem f = ShFileSystemFactory.getInstance();
//		ShFileSystem f = new ShNullFileSystem();
		ShRuntime m = ShRuntime.getInstance();
		InputStream ins = null;
		PrintStream out, err;
		Iterator<String> a;
		OptionIterator o;
		ShEnvironment v;
		XtraceStream q;
		boolean fc, fs;
		int z = 0;
		String s;

		// initialize
		v = ShFileSystemFactory.getSystemEnvironment();
		v = new ShRootShellEnvironment(v);
		v.bind("PS1", "$ ");
		v.bind("PS2", "> ");
		f.setCurrentDirectory(f.getHome());
		q = new XtraceStream(System.out);

		fc = fs = false;
		o  = new OptionIterator("Cfnuvxo:c:s", true, args);
		while(o.hasNext()) {
			switch(o.nextChar()) {
			case 'C':  v.set("noclobber", !o.isPlus());  break;
			case 'f':  v.set("noglob", !o.isPlus());  break;
			case 'n':  v.set("noexec", !o.isPlus());  break;
			case 'u':  v.set("nounset", !o.isPlus());  break;
			case 'v':  v.set("verbose", !o.isPlus());  break;
			case 'x':  v.set("xtrace", !o.isPlus());  break;
			case 'c':  fc = true;  break;
			case 's':  fs = true;  break;
			}
		}

		// set outputs
		try {
			out = new PrintStream(
					System.out, true, v.getCharset().toString());
			err = new PrintStream(
					System.err, true, v.getCharset().toString());
		} catch (UnsupportedEncodingException e2) {
			throw new RuntimeException(e2);
		}

		// execute .jsh_profile file
		execute(v, f, b, m, f.getProfile(), System.in, out, err, q);

		try {
			// execute
			a = o.filenameIterator();
			if(fc) {
				s = o.getArgument();
				a = o.filenameIterator();
				_setarg(v, a);
				z = m.eval(v, f, System.in, out, err, q, s);
			} else if(fs) {
				s = o.getArgument();
				a = o.filenameIterator();
				_setarg(v, a);
				z = interactive(v, f, b, m,
						ConsoleInputStream.console(), INTERACTIVE,
						System.in, out, err, q);
			} else if(a.hasNext()) {
				try {
					s   = a.next();
					ins = new FileInputStream(s);
					_setarg(v, a);
					z = execute(v, f, b, m, ins, System.in, out, err,
							q);
				} catch (FileNotFoundException e) {
					throw new RuntimeException(e);
				}
			} else {
				z = interactive(v, f, b, m,
						ConsoleInputStream.console(), INTERACTIVE,
						System.in, out, err, q);
			}
			return z;
		} catch(IOException e) {
			// execute IO trap
			if((s = v.getTrap(ShSignal.IO)) != null) {
				try {
					ShRuntime.getInstance().eval(v, f, System.in,
							out, err, q, s);
				} catch(ShCommandNotFoundException e1) {
					err.print(NAME);
					err.print(": command not found: ");
					err.println(e1.getName());
				} catch(IOException e1) {
					throw new RuntimeException(e);
				} catch(ShSyntaxException e1) {
					throw new RuntimeException(e);
				}
			}
			throw new RuntimeException(e);
		} catch(ShSyntaxException e) {
			throw new RuntimeException(e);
		} finally {
			// execute EXIT trap
			if((s = v.getTrap(ShSignal.EXIT)) != null) {
				try {
					m.eval(v, f, System.in, out, err, q, s);
				} catch (IOException e) {
					throw new RuntimeException(e);
				} catch (ShSyntaxException e) {
					throw new RuntimeException(e);
				}
			}
		}
	}

}
