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

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;

import net.morilib.sh.ShEnvironment;
import net.morilib.sh.ShFileSystem;
import net.morilib.sh.ShProcess;

public class ShWc implements ShProcess {

	private static final int BYTES = 1;
	private static final int CHARS = 2;
	private static final int WORDS = 4;
	private static final int LINES = 8;

	private int analyzeopt(String s, int[] f) {
		for(int i = 1; i < s.length(); i++) {
			switch(s.charAt(i)) {
			case 'c':  f[0] |= BYTES;  break;
			case 'm':  f[0] |= CHARS;  break;
			case 'w':  f[0] |= WORDS;  break;
			case 'l':  f[0] |= LINES;  break;
			default:  return s.charAt(i);
			}
		}
		return -1;
	}

	private void prt(PrintStream out, int f, String name, long l,
			long w, long c, long b) {
		String d = "";

		if(f == 0) {
			if(name == null) {
				out.format("%7d %7d %7d\n", l, w, b);
			} else {
				out.format("%7d %7d %7d %s\n", l, w, b, name);
			}
		} else {
			if((f & LINES) != 0) {
				out.format("%s%7d", d, l);  d = " ";
			}
			if((f & WORDS) != 0) {
				out.format("%s%7d", d, w);  d = " ";
			}
			if((f & CHARS) != 0) {
				out.format("%s%7d", d, c);  d = " ";
			}
			if((f & BYTES) != 0) {
				out.format("%s%7d", d, b);  d = " ";
			}
			if(name != null) {
				out.format("%s%s", d, name);
			}
			out.println();
		}
	}

	private void wc(InputStream ins, PrintStream out, String name,
			int f, long[] sum) throws IOException {
		CharsetDecoder cd = Charset.defaultCharset().newDecoder();
		boolean wd = false, cr = false, lf = true;
		ByteBuffer bb = ByteBuffer.allocate(8);
		CharBuffer cb = CharBuffer.allocate(8);
		byte[] a = new byte[8];
		long b, c, w, l;
		int x, k, y;

		b = c = w = l = k = y = 0;
		for(; (x = ins.read()) >= 0; b++) {
			// count characters
			a[k] = (byte)x;
			bb.put(a, 0, k + 1).flip();
			if(cd.decode(bb, cb, true).isUnderflow()) {
				cd.flush(cb);
				if(cb.flip().hasRemaining()) {
					y = cb.get();
					c++;
				}
				cb.clear();  cd.reset();
				k = 0;
			} else {
				k++;
			}
			bb.clear();

			// count words
			if(wd) {
				wd = !Character.isWhitespace(y);
			} else if(wd = !Character.isWhitespace(y)) {
				w++;
			}

			// count lines
			l += (lf = y == '\n') || cr ? 1 : 0;
			cr = y == '\r';
		}
		l += lf ? 0 : 1;

		prt(out, f, name, l, w, c, b);
		if(sum != null) {
			sum[0] += l;
			sum[1] += w;
			sum[2] += c;
			sum[3] += b;
		}
	}

	public int main(ShEnvironment env, ShFileSystem fs, InputStream in,
			PrintStream out, PrintStream err,
			String... args) throws IOException {
		InputStream ins = null;
		int[] f = new int[1];
		int k = 1, z, l = 0;
		long[] s;

		for(; k < args.length; k++) {
			if(args[k].equals("--bytes")) {
				f[0] |= BYTES;
			} else if(args[k].equals("--chars")) {
				f[0] |= CHARS;
			} else if(args[k].equals("--words")) {
				f[0] |= WORDS;
			} else if(args[k].equals("--lines")) {
				f[0] |= LINES;
			} else if(args[k].equals("--")) {
				k++;
				break;
			} else if(args[k].equals("-")) {
				break;
			} else if(!args[k].startsWith("-")) {
				break;
			} else if((z = analyzeopt(args[k], f)) >= 0) {
				err.println("wc: invalid option: " + (char)z);
				return 2;
			}
		}

		if(k >= args.length) {
			wc(in, out, null, f[0], null);
		} else {
			s = new long[4];
			s[0] = s[1] = s[2] = s[3] = 0;
			for(; k < args.length; k++, l++) {
				if(args[k].equals("-")) {
					wc(in, out, "(standard input)", f[0], s);
				} else {
					try {
						ins = fs.getFile(args[k]).getInputStream();
						if(ins == null) {
							err.print("wc: ");
							err.print(args[k]);
							err.println(": file not found");
							return 2;
						}
						wc(ins, out, args[k], f[0], s);
					} finally {
						if(ins != null)  ins.close();
						ins = null;
					}
				}
			}

			if(l > 1) {
				prt(out, f[0], "(total)", s[0], s[1], s[2], s[3]);
			}
		}
		return 0;
	}

}
