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

import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;

import net.morilib.awk.misc.System2;
import net.morilib.sh.ShFileSystem;

/**
 * awkiumで使用されているファイルを管理するクラスです。
 * 
 * @author MORIGUCHI, Yuichiro 2013/03/09
 */
public class AwkFiles {

	private static final String PIPE_ID = "\u0007\u0002";

	private Map<String, RecordReader> readers;
	private Map<String, LineWriter>   writers;
	private Map<String, OutputStream> binouts;
	private AwkReader stdin;
	private LineWriter stdout, stderr;
	private ShFileSystem filesystem;

	/**
	 * 標準入力・標準出力・標準エラー出力を指定のものを使用して
	 * インスタンスを生成します。
	 * 
	 * @param stdin  標準入力
	 * @param stdout 標準出力
	 * @param stderr 標準エラー出力
	 */
	public AwkFiles(ShFileSystem filesystem, InputStream stdin,
			Writer stdout, Writer stderr) {
		readers     = new HashMap<String, RecordReader>();
		writers     = new HashMap<String, LineWriter>();
		binouts     = new HashMap<String, OutputStream>();
		this.stdin  = new AwkReader(stdin);
		this.stdout = new PrintLineWriter(new PrintWriter(stdout));
		this.stderr = new PrintLineWriter(new PrintWriter(stderr));
		this.filesystem = filesystem;
	}

	/**
	 * ファイルを読み出し専用で開きます。<br />
	 * ファイルが開かれているときは開かれたものを得ます。
	 * 
	 * @param filename 開くファイル名
	 * @return 開かれたファイルのReader
	 * @throws IOException IOエラー
	 */
	public RecordReader getReader(String filename) throws IOException {
		RecordReader r;

		if(filename == null) {
			r = stdin;
		} else if((r = AwkVirtualFile.in(filename, this)) == null &&
				(r = readers.get(filename)) == null) {
//			r = new AwkReader(new FileInputStream(filename));
			r = new AwkReader(
					filesystem.getFile(filename).getInputStream());
			readers.put(filename, r);
		}
		return r;
	}

	/**
	 * コマンドの出力結果を得ます。<br />
	 * コマンドは1回のみ実行されます。
	 * 
	 * @param command 実行するコマンド
	 * @return 開かれた「パイプ」のReader
	 * @throws IOException IOエラー
	 */
	public RecordReader getReadPipe(
			String command) throws IOException {
		BufferedOutputStream o;
		RecordReader r;
		java.io.File t;

		if((r = readers.get(PIPE_ID + command)) == null) {
			t = java.io.File.createTempFile("awkium", "pipe");
			t.deleteOnExit();
			o = new BufferedOutputStream(
					new java.io.FileOutputStream(t));
			System2.system(command, null, o);
			o.close();
			r = new AwkReader(new java.io.FileInputStream(t));
			readers.put(PIPE_ID + command, r);
		}
		return r;
	}

	/**
	 * ファイルを書き込み専用で開きます。<br />
	 * ファイルが開かれているときは開かれたものを得ます。
	 * 
	 * @param filename 開くファイル名
	 * @return 開かれたファイルのWriter
	 * @throws IOException IOエラー
	 */
	public LineWriter openWriter(String filename) throws IOException {
		LineWriter r;

		if(filename == null) {
			r = stdout;
		} else if((r = AwkVirtualFile.out(filename, this)) == null &&
				(r = writers.get(filename)) == null) {
			r = new PrintLineWriter(new PrintWriter(
					new OutputStreamWriter(
							filesystem.getFile(
									filename).getPrintStream(false))));
			writers.put(filename, r);
		}
		return r;
	}

	/**
	 * ファイルを書き込み専用で開きます。<br />
	 * ファイルが開かれているときはいったん閉じてから
	 * 再び開きます。
	 * 
	 * @param filename 開くファイル名
	 * @return 開かれたファイルのWriter
	 * @throws IOException IOエラー
	 */
	public LineWriter getWriter(String filename) throws IOException {
		LineWriter r;

		if(filename == null) {
			r = stdout;
		} else if((r = AwkVirtualFile.out(filename, this)) == null &&
				(r = writers.get(filename)) == null) {
			r = new PrintLineWriter(new PrintWriter(
					new OutputStreamWriter(
							filesystem.getFile(
									filename).getPrintStream(true))));
			writers.put(filename, r);
		}
		return r;
	}

	/**
	 * 標準出力を得ます。
	 */
	public LineWriter getStdout() {
		return stdout;
	}

	/**
	 * 標準エラー出力を得ます。
	 */
	public LineWriter getStderr() {
		return stderr;
	}

	/**
	 * 標準入力を得ます。
	 */
	public RecordReader getStdin() {
		return stdin;
	}

	/**
	 * ファイルを閉じます。
	 * 
	 * @param filename ファイル名
	 * @throws IOException IOエラー
	 */
	public void close(String filename) throws IOException {
		Closeable c;

		if((c = readers.get(filename)) != null) {
			c.close();
		} else if((c = writers.get(filename)) != null) {
			c.close();
		} else if((c = binouts.get(filename)) != null) {
			c.close();
		}
	}

	/**
	 * バイナリファイルを書き込み専用で開きます。<br />
	 * ファイルが開かれているときは開かれたものを得ます。
	 * 
	 * @param filename 開くファイル名
	 * @return 開かれたファイルのOutputStream
	 * @throws IOException IOエラー
	 */
	public OutputStream openOutputStream(
			String filename) throws IOException {
		OutputStream r;

		if(filename == null || filename.equals("")) {
			throw new IOException("empty filename");
		} else if((r = AwkVirtualFile.outBinary(
				filename, this)) == null) {
			if((r = binouts.get(filename)) != null)  r.close();
//			r = new FileOutputStream(filename);
			r = filesystem.getFile(filename).getPrintStream(false);
			binouts.put(filename, r);
		}
		return r;
	}

	//
	private static void closeq(Closeable c) {
		try {
			if(c != null)  c.close();
		} catch(IOException e) {
			// ignore
		}
	}

	/**
	 * 開かれたすべてのファイルを閉じます。
	 */
	public void closeAll() {
		for(Closeable r : readers.values())  closeq(r);
		for(Closeable r : writers.values())  closeq(r);
		for(Closeable r : binouts.values())  closeq(r);
	}

}
