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

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;

import net.morilib.sed.cmd.SedCCommand;
import net.morilib.sed.cmd.SedLabel;
import net.morilib.sh.ShFileSystem;

/**
 * sedコマンドの実行エンジンです。
 *
 *
 * @author MORIGUCHI, Yuichiro 2013/03
 */
public class SedEngine {

	//
	private static Map<SedCommand, Map<SedLabel, Integer>> scopes =
		new HashMap<SedCommand, Map<SedLabel, Integer>>();

	//
	private synchronized static Map<SedLabel, Integer> getscope(
			SedCommand c) {
		Map<SedLabel, Integer> m;

		if((m = scopes.get(c)) == null) {
			m = new HashMap<SedLabel, Integer>();
			scopes.put(c, m);
		}
		return m;
	}

	//
	private static boolean match(SedLineOption m, SedSpace s,
			SedLineBuffer rd) {
		return m.matches(s.engine, s.toString(),
				rd.getLineNumber(), rd.isLastLine());
	}

	//
	private static void out(ShFileSystem fs, SedSpace s,
			PrintStream pw, boolean display) throws IOException {
		BufferedReader rd = null;
		String t, v = s.toString();

		if(s.deleted)  return;
		if(s.lineToInsert != null)  pw.println(s.lineToInsert);
		if(display && v != null)  pw.println(v);
		if(s.lineToAppend != null)  pw.println(s.lineToAppend);
		s.printFileToWrite();
		if(s.fileToRead != null) {
			try {
				rd = new BufferedReader(new InputStreamReader(
						fs.getFile(s.fileToRead).getInputStream()));
				while((t = rd.readLine()) != null)  pw.println(t);
			} finally {
				if(rd != null)  rd.close();
			}
		}
	}

	//
	private static int getip(Map<SedLabel, Integer> lb, SedLabel l,
			int ip, SedCommandLine... lines) {
		int x = 0;

		if(l == null) {
			return ip;
		} else if(!lb.containsKey(l)) {
			for(; x < lines.length; x++) {
				if(lines[x].command.equals(l))  break;
			}
			lb.put(l, x);
		} else {
			x = lb.get(l);
		}
		return x;
	}

	/**
	 * 内部的に使用されます。
	 * 
	 * @param cm コマンド
	 * @param sp sedスペース
	 * @param rd 入力
	 * @param lines コマンド行
	 * @return ラベル
	 * @throws IOException
	 */
	public static SedLabel execute(SedCommand cm,
			SedSpace sp,
			SedLineBuffer rd,
			SedCommandLine... lines) throws IOException {
		Map<SedLabel, Integer> lb = getscope(cm);
		SedCommandLine l;
		int ip = 0;

		for(; ip < lines.length; ip++) {
			l = lines[ip];
			if(match(l.matcher, sp, rd)) {
				l.command.process(sp, rd);
			}

			if(sp.deleted)  return null;
			ip = getip(lb, sp.nextLabel, ip, lines);
			if(sp.nextLabel != null && ip >= lines.length) {
				return sp.nextLabel;
			}
		}
		return null;
	}

	/**
	 * sedコマンドを実行します。
	 * 
	 * @param reader 入力
	 * @param writer 出力
	 * @param engine 正規表現エンジン
	 * @param display パターンスペースの内容を出力するときtrue
	 * @param lines コマンド
	 * @throws IOException
	 */
	public static int execute(ShFileSystem fs,
			Reader reader,
			PrintStream pw,
			SedPatternEngine engine,
			boolean display,
			SedCommandLine... lines) throws IOException {
		Map<SedLabel, Integer> lb = new HashMap<SedLabel, Integer>();
		SedLineBuffer rd = new SedLineBuffer(reader);
		boolean mt, rt = true;
		SedCommandLine l;
		SedLineOption op;
		SedCommand cm;
		SedSpace sp;
		String s;
		int ip;

		sp = new SedSpace(fs, engine, pw, display);
		try {
			outer: for(; (s = rd.readLine()) != null; sp.clear()) {
				sp.setPatternSpace(s);
				for(ip = 0; ip < lines.length; ip++) {
					l = lines[ip];
					mt = match(op = l.matcher, sp, rd);
					if((cm = l.command) instanceof SedCCommand &&
							(mt || op.isMatching()) &&
							!op.isNegate()) {
						for(; op.isMatching(); match(op, sp, rd)) {
							if((s = rd.readLine()) == null) {
								if(display) {
									pw.println(
											((SedCCommand)cm)
											.getString());
								}
								return 0;
							}
							sp.setPatternSpace(s);
						}
						sp.clear();
						sp.setPatternSpace(s);
						if(display) {
							pw.println(((SedCCommand)cm).getString());
						}
						continue outer;
					} else if(mt) {
						cm.process(sp, rd);
					}
					if(sp.deleted)  continue outer;
					ip = getip(lb, sp.nextLabel, ip, lines);
					sp.nextLabel = null;
				}
				rt = sp.isSucceedBefore() | rt;
				out(fs, sp, pw, display);
			}
			return rt ? 0 : 1;
		} catch(SedQuitException e) {
			out(fs, sp, pw, display);
			return e.exitCode;
		} finally {
			pw.flush();
			sp.destroy();
		}
	}

}
