/*
 * 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.BufferedWriter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;

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


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(SedSpace s, PrintWriter 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(
						new FileInputStream(s.fileToRead)));
				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
	 * @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;
	}

	/**
	 * 
	 * @param reader
	 * @param writer
	 * @param engine
	 * @param isdisplay
	 * @param lines
	 * @throws IOException
	 */
	public static int execute(Reader reader,
			Writer writer,
			SedPatternEngine engine,
			boolean display,
			SedCommandLine... lines) throws IOException {
		Map<SedLabel, Integer> lb = new HashMap<SedLabel, Integer>();
		PrintWriter pw = new PrintWriter(new BufferedWriter(writer));
		SedLineBuffer rd = new SedLineBuffer(reader);
		SedCommandLine l;
		SedLineOption op;
		SedCommand cm;
		SedSpace sp;
		boolean mt;
		String s;
		int ip;

		sp = new SedSpace(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;
				}
				out(sp, pw, display);
			}
			return sp.isSucceedBefore() ? 0 : 1;
		} catch(SedQuitException e) {
			out(sp, pw, display);
			return e.exitCode;
		} finally {
			pw.flush();
			sp.destroy();
		}
	}

}
