/*
 * Copyright 2013-2014 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.nina.cmd;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;

import net.morilib.deculture.misc.DecultureException;
import net.morilib.nina.NinaCompoundException;
import net.morilib.nina.NinaException;
import net.morilib.nina.Quadro;
import net.morilib.nina.translate.NinaTranslator;

/**
 *
 *
 * @author MORIGUCHI, Yuichiro 2013/10/23
 */
public class Ninac {

	private static void process(NinatOptions opts, NinaTranslator tr,
			String s, Map<String, String> prms, boolean only,
			Reader rd0, PrintStream pr) {
		InputStream ins = null;
		Reader rd = null;

		try {
			if(s != null) {
				ins = new FileInputStream(s);
				rd  = new InputStreamReader(ins);
			} else if(rd0 != null) {
				rd = rd0;
				s  = opts.getFilename();
			} else {
				throw new NullPointerException();
			}

			if(only) {
				tr.compile(s, rd, opts, opts.getLibraryList(), prms);
			} else {
				tr.translate(s, rd, opts, opts.getLibraryList(), prms,
						pr);
			}

			if(opts.getReport() != null && opts.getReport() != tr) {
				process(opts, opts.getReport(), s, prms, only, rd0,
						null);
			}

			if(opts.isOptionDefined("statistics")) {
				tr.reportStatistics(System.out);
			}
		} catch(FileNotFoundException e) {
			opts.perror("filenotfound", s);
			System.exit(2);
		} catch(IOException e) {
			opts.perror("ioerror");
			if(opts.isOptionDefined("exception")) {
				e.printStackTrace(System.err);
			}
			System.exit(2);
		} catch(NinaCompoundException e) {
			for(NinaException e1 : e.getExceptions()) {
				opts.pmessage(e1.getMessage());
			}

			try {
				if(opts.isOptionDefined("exception")) {
					e.getCells().putBadfile(opts.getFilename() + ".bad");
				} else if(opts.isOptionDefined("trace")) {
					e.getCells().putBadfile(opts.getFilename() + ".bad");
				}
			} catch(IOException e1) {
				opts.perror("ioerror");
			}
			System.exit(2);
		} catch(NinaException e) {
			opts.pmessage(e.getMessage());
			if(opts.isOptionDefined("exception")) {
				e.printTrace(System.err);
				e.printStackTrace(System.err);
			} else if(opts.isOptionDefined("trace")) {
				e.printTrace(System.err);
			}
			System.exit(2);
		} catch(DecultureException e) {
			opts.pmessage("LR parser error: " + e.getMessage());
			if(opts.isOptionDefined("exception")) {
				e.printStackTrace(System.err);
			}
			System.exit(2);
		} finally {
			if(ins != null) {
				try {
					ins.close();
				} catch (IOException e) {
					throw new RuntimeException(e);
				}
			}
		}
	}

	private static void process(NinatOptions opts, String arg,
			Map<String, String> prms, boolean only, PrintStream pr) {
		String s, t;
		int x;

		if(arg.endsWith("/")) {
			opts.perror("filenotfound", arg);
			return;
		} else if((x = arg.lastIndexOf('.')) < 0) {
			s = arg + ".nina";
			t = arg;
		} else {
			s = arg;
			t = arg.substring(0, x);
		}

		if((x = t.lastIndexOf('/')) < 0) {
			opts.filename = t;
		} else {
			opts.filename = t.substring(x + 1);
		}

		if(opts.getTranslator() == null) {
			opts.perror("languagenotsupport");
			System.exit(2);
		}
		process(opts, opts.getTranslator(), s, prms, only, null, pr);
	}

	//
	private static void process(NinatOptions opts, Reader rd,
			Map<String, String> prms, boolean only, PrintStream pr) {
		if(opts.getTranslator() == null) {
			opts.perror("languagenotsupport");
			System.exit(2);
		} else if(opts.getOption("outfile") == null) {
			opts.perror("requireoutput");
			System.exit(2);
		}
		opts.filename = opts.getOption("outfile");
		process(opts, opts.getTranslator(), null, prms, only, rd,
				null);
	}

	//
	private static int javacompile(NinatOptions opts) {
		JavaCompiler cp;
		int x;

		if(opts.isJavaCompiler()) {
			try {
				cp = ToolProvider.getSystemJavaCompiler();
				x  = cp.run(null, null, null,
						opts.getJavaFile().toString());
				return x;
			} catch(Exception e) {
				opts.perror("javacompiler");
				return 4;
			}
		} else {
			return 0;
		}
	}

	private static void interpret(NinatOptions opts, String s,
			Map<String, String> z) {
		ByteArrayOutputStream ot;
		PrintStream pt;
		ScriptEngineManager mn;
		ScriptEngine en;

		try {
			ot = new ByteArrayOutputStream();
			pt = new PrintStream(ot, true);
			if(s != null) {
				process(opts, s, z, false, pt);
			} else {
				process(opts, new InputStreamReader(System.in), z,
						false, pt);
			}
			mn = new ScriptEngineManager();
			en = mn.getEngineByName("javascript");
			en.eval(ot.toString());
			System.exit(0);
		} catch(ScriptException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		NinatOptions opts = new NinatOptions();
		Map<String, String> z;
		int k;

		z = new HashMap<String, String>();
		opts = new NinatOptions();
		if(args.length == 0) {
			opts.usage();
			System.exit(2);
		} else if((k = opts.parseOptions(args, z)) == args.length) {
			if(!opts.isStandardInput()) {
				opts.perror("nofiles");
				System.exit(2);
			} else if(opts.isOptionDefined("interpret")) {
				interpret(opts, null, z);
			} else {
				process(opts, new InputStreamReader(System.in), z,
						false, System.out);
				System.exit(0);
			}
		} else if(opts.isOptionDefined("interpret")) {
			interpret(opts, args[k], z);
		} else if(opts.isOptionDefined("only")) {
			for(; k < args.length; k++) {
				process(opts, args[k], z, true, null);
			}
			System.exit(Quadro.isWarning() ? 1 : 0);
		} else {
			for(; k < args.length; k++) {
				process(opts, args[k], z, false, null);
			}
			System.exit(javacompile(opts));
		}
	}

}
