/*
 * 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.Closeable;
import java.io.IOException;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;

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

/**
 * sedのパターンスペースやホールドスペースを管理するクラスです。
 *
 *
 * @author MORIGUCHI, Yuichiro 2013/03
 */
public class SedSpace {

	//
	private StringBuffer patn, hold = null;
	private Map<String, PrintStream> writers =
		new HashMap<String, PrintStream>();
	SedPatternEngine engine;
	private PrintStream out;
	private ShFileSystem fs;

	//
	String lineToAppend = null, lineToInsert = null;
	String fileToRead = null, fileToWrite = null;
	boolean deleted = false, display;
	SedLabel nextLabel = null;

	//
	private boolean succeedBefore = true, succeed = true;

	//
	SedSpace(ShFileSystem fs, SedPatternEngine engine, PrintStream out,
			boolean disp) {
		this.engine  = engine;
		this.out     = out;
		this.display = disp;
	}

	/**
	 * パターンスペースに文字列をセットします。
	 * 
	 * @param s 文字列
	 */
	public void setPatternSpace(String s) {
		patn = new StringBuffer(s);
	}

	/**
	 * パターンスペースに文字列を追加します。
	 * 
	 * @param s 文字列
	 */
	public void appendPatternSpace(String s) {
		patn.append('\n').append(s);
	}

	/**
	 * ホールドスペースの内容をパターンスペースにセットします。
	 */
	public void appendPatternSpace() {
		patn.append('\n').append(hold);
	}

	/**
	 * パターンスペースの内容をホールドスペースに置き換えます。
	 */
	public void replacePatternSpace() {
		patn = new StringBuffer(hold);
	}

	/**
	 * パターンスペースの内容をホールドスペースにセットします。
	 */
	public void appendHoldSpace() {
		hold = (hold == null) ? new StringBuffer() : hold.append('\n');
		hold.append(patn);
	}

	/**
	 * ホールドスペースの内容をパターンスペースに置き換えます。
	 */
	public void replaceHoldSpace() {
		hold = new StringBuffer(patn);
	}

	/**
	 * パターンスペースとホールドスペースを入れ替えます。
	 */
	public void exchange() {
		StringBuffer b;

		b = hold;  hold = patn;  patn = b;
	}

	/**
	 * 行を追加します(aコマンド)。
	 * 
	 * @param string 行
	 */
	public void appendLines(String string) {
		lineToAppend = string;
	}

	/**
	 * 行を挿入します(iコマンド)。
	 * 
	 * @param string 行
	 */
	public void insertLines(String string) {
		lineToInsert = string;
	}

	/**
	 * パターンスペースを削除します。
	 */
	public void deleteLine() {
		deleted = true;
	}

	/**
	 * 行をプリントします。
	 */
	public void printImmediate() {
		if(display)  out.println(toString());
	}

	/**
	 * 与えられた行をプリントします。
	 * 
	 * @param s 行
	 */
	public void printImmediate(String s) {
		if(display)  out.println(s);
	}

	//
	void clear() {
		fileToRead = fileToWrite = lineToAppend = lineToInsert = null;
		succeedBefore = succeed;
		deleted = false;
		succeed = true;
	}

	/**
	 * 次の行き先のラベルをセットします。
	 * 
	 * @param l ラベル
	 */
	public void setNextLabel(SedLabel l) {
		nextLabel = l;
	}

	/**
	 * 外部から読み込むファイルを指定します。
	 * 
	 * @param filename ファイル名
	 */
	public void setFileToRead(String filename) {
		fileToRead = filename;
	}

	/**
	 * 外部から書き込むファイルを指定します。
	 * 
	 * @param filename ファイル名
	 */
	public void setFileToWrite(String filename) {
		fileToWrite = filename;
	}

	//
	void printFileToWrite(String s, String p) throws IOException {
		PrintStream w;

		if(s == null)  return;
		if((w = writers.get(s)) == null) {
			w = fs.getFile(s).getPrintStream(false);
			writers.put(s, w);
		}
		w.print(p);
	}

	//
	void printFileToWrite() throws IOException {
		printFileToWrite(fileToWrite, toString());
	}

	//
	void destroy() throws IOException {
		for(Closeable w : writers.values())  w.close();
	}

	/**
	 * 前回の置換が成功したか調べます。
	 * 
	 * @return 前回の置換が成功したときtrue
	 */
	public boolean isSucceedBefore() {
		return succeedBefore;
	}

	/**
	 * 置換が成功したか調べます。
	 * 
	 * @return 置換が成功したときtrue
	 */
	public boolean isSucceed() {
		return succeed;
	}

	/**
	 * パターンスペースを置換します。
	 * 
	 * @param pattern 正規表現パターン
	 * @param replace 置き換える文字列
	 * @param flags フラグ
	 * @throws IOException 
	 */
	public void substitute(String pattern, String replace,
			SedSubstituteFlags flags) throws IOException {
		String s = toString();

		if((s = engine.replace(pattern, s, replace, flags)) != null) {
			patn = new StringBuffer(s);
			succeed = true;
		} else {
			succeed = false;
		}

		if(flags.isPrint()) {
			out.println(toString());
		} else if(flags.getFileToWrite() != null) {
			printFileToWrite(flags.getFileToWrite(), toString());
		}
	}

	/**
	 * パターンスペースの内容をプリントします。
	 */
	public void print() {
		out.println(toString());
	}

	/**
	 * パターンスペースの内容を見える形でプリントします。<br />
	 * 非表示文字はエスケープして表示されます。
	 */
	public void printVisible() {
		String s = toString();
		int c;

		for(int i = 0; i < s.length(); i += c > 0xffff ? 2 : 1) {
			if(Character.isISOControl(c = s.codePointAt(i))) {
				out.print('\\');
				switch(c) {
				case 0x07:  out.print('a');  break;
				case 0x08:  out.print('b');  break;
				case 0x09:  out.print('t');  break;
				case 0x0a:  out.print('n');  break;
				case 0x0b:  out.print('v');  break;
				case 0x0c:  out.print('f');  break;
				case 0x0d:  out.print('r');  break;
				default:
					out.print(Integer.toString((c / 64) % 8));
					out.print(Integer.toString((c /  8) % 8));
					out.print(Integer.toString(c % 8));
					break;
				}
			} else if(c == '\\') {
				out.print("\\");
			} else {
				out.print((char)c);
			}
		}
		out.println("$");
	}

	/**
	 * パターンスペースの最初の行をプリントします。
	 */
	public void printFirstLine() {
		String s = toString();
		char c;

		for(int i = 0; i < s.length(); i++) {
			if((c = s.charAt(i)) == '\n')  break;
			out.print(c);
		}
		out.println();
	}

	/**
	 * 与えられた行をプリントします。
	 * 
	 * @param s 行
	 */
	public void print(String s) {
		out.println(s);
	}

	/**
	 * 与えられた数をプリントします。
	 * 
	 * @param value 数
	 */
	public void print(int value) {
		out.println(value);
	}

	/**
	 * 与えられた数をプリントします。
	 * 
	 * @param value 数
	 */
	public void print(long value) {
		out.println(value);
	}

	/**
	 * パターンスペースの最初の行を削除します。
	 */
	public void deleteFirstLine() {
		String s = toString();
		int i = 0;

		while(i < s.length()) {
			if(s.charAt(i++) == '\n')  break;
		}

		if(i >= s.length()) {
			deleteLine();
		} else {
			patn = patn.delete(0, i);
		}
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	public String toString() {
		return patn == null ? "" : patn.toString();
	}

}
