/*
 * 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.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.nio.charset.Charset;

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

public class ShHead implements ShProcess {

	private static class S1 {
		long number = 10;
		int  flags = 0;
	}

	private static final int LINES = 0;
	private static final int BYTES = 1;
	private static final int CHARS = 3;
	private static final int VERBOSE = 4;
	private static final int BUFSIZE = 1024;

	private int analyzeopt1(String s, S1 f) {
		int k = 1, c;

		if(s.charAt(1) < '0' || s.charAt(1) > '9') {
			return analyzeopt(s, f);
		} else {
			for(; k < s.length(); k++) {
				if((c = s.charAt(k)) < '0' || c > '9')  break;
			}
			f.number = Long.parseLong(s.substring(1, k));

			for(; k < s.length(); k++) {
				switch(s.charAt(k)) {
				case 'q':  f.flags &= ~VERBOSE;  break;
				case 'v':  f.flags |=  VERBOSE;  break;
				case 'k':  f.number *= 1024;  break;
				case 'm':  f.number *= 1048576;  break;
				case 'c':  f.flags  = (f.flags & ~3) | BYTES;  break;
				case 'n':  f.flags  = (f.flags & ~3) | LINES;  break;
				case 'C':  f.flags  = (f.flags & ~3) | CHARS;  break;
				default:  return s.charAt(k);
				}
			}
		}
		return -1;
	}

	private int analyzeopt(String s, S1 f) {
		for(int i = 1; i < s.length(); i++) {
			switch(s.charAt(i)) {
			case 'q':  f.flags &= ~VERBOSE;  break;
			case 'v':  f.flags |=  VERBOSE;  break;
			default:  return s.charAt(i);
			}
		}
		return -1;
	}

	private long parsenum(String s) {
		int k = 0, c;
		long l;

		for(; k < s.length(); k++) {
			if((c = s.charAt(k)) < '0' || c > '9')  break;
		}
		l = Long.parseLong(s.substring(0, k));

		if(k < s.length() && s.charAt(k) == 'k') {
			l *= 1024;
		} else if(k < s.length() && s.charAt(k) == 'm') {
			l *= 1048576;
		}
		return l;
	}

	private void head(InputStream ins, PrintStream out, String name,
			S1 fl, Charset cset) throws IOException {
		long n = fl.number;
		BufferedReader rd;
		BufferedWriter wr;
		int m = 0;
		String s;
		byte[] b;
		char[] a;

		if((fl.flags & VERBOSE) != 0) {
			out.print("==> ");
			out.print(name);
			out.println(" <==");
		}

		switch(fl.flags & 3) {
		case LINES:
			rd = new BufferedReader(new InputStreamReader(ins, cset));
			for(int i = 0; i < fl.number; i++) {
				if((s = rd.readLine()) == null)  break;
				out.println(s);
			}
			break;
		case BYTES:
			b = new byte[BUFSIZE];
			while((n -= m) > 0) {
				if((m = ins.read(b, 0,
						n < b.length ? (int)n : b.length)) < 0)  break;
				out.write(b, 0, m);
			}
			break;
		case CHARS:
			rd = new BufferedReader(new InputStreamReader(ins, cset));
			wr = new BufferedWriter(new OutputStreamWriter(out, cset));
			a = new char[BUFSIZE];
			while((n -= m) > 0) {
				if((m = rd.read(a, 0,
						n < a.length ? (int)n : a.length)) < 0)  break;
				wr.write(a, 0, m);
			}
			wr.flush();
			break;
		default:  throw new RuntimeException();
		}
	}

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

		for(; k < args.length; k++) {
			if(args[k].matches("-[vq]*vn") &&
					k < args.length - 1 &&
					args[k + 1].length() > 0) {
				f.flags |= VERBOSE;
				f.flags  = (f.flags & ~3) | LINES;
				try {
					f.number = Long.parseLong(args[++k]);
				} catch(NumberFormatException e) {
					err.println("head: invalid option");
					return 2;
				}
			} else if(args[k].matches("-[vq]*q?n") &&
					k < args.length - 1 &&
					args[k + 1].length() > 0) {
				f.flags  = (f.flags & ~3) | LINES;
				try {
					f.number = Long.parseLong(args[++k]);
				} catch(NumberFormatException e) {
					err.println("head: invalid option");
					return 2;
				}
			} else if(args[k].startsWith("--lines=") &&
					args[k].length() > 8) {
				f.flags  = (f.flags & ~3) | LINES;
				try {
					f.number = Long.parseLong(args[k].substring(8));
				} catch(NumberFormatException e) {
					err.println("head: invalid option");
					return 2;
				}
			} else if(args[k].matches("-[vq]*vc") &&
					k < args.length - 1 &&
					args[k + 1].length() > 0) {
				f.flags |= VERBOSE;
				f.flags  = (f.flags & ~3) | BYTES;
				if((f.number = parsenum(args[++k])) < 0) {
					err.println("head: invalid option");
					return 2;
				}
			} else if(args[k].matches("-[vq]*q?c") &&
					k < args.length - 1 &&
					args[k + 1].length() > 0) {
				f.flags  = (f.flags & ~3) | BYTES;
				if((f.number = parsenum(args[++k])) < 0) {
					err.println("head: invalid option");
					return 2;
				}
			} else if(args[k].startsWith("--bytes=") &&
					args[k].length() > 8) {
				f.flags  = (f.flags & ~3) | BYTES;
				if((f.number = parsenum(args[k].substring(8))) < 0) {
					err.println("head: invalid option");
					return 2;
				}
			} else if(args[k].matches("-[vq]*vC") &&
					k < args.length - 1 &&
					args[k + 1].length() > 0) {
				f.flags |= VERBOSE;
				f.flags  = (f.flags & ~3) | CHARS;
				if((f.number = parsenum(args[++k])) < 0) {
					err.println("head: invalid option");
					return 2;
				}
			} else if(args[k].matches("-[vq]*q?C") &&
					k < args.length - 1 &&
					args[k + 1].length() > 0) {
				f.flags  = (f.flags & ~3) | CHARS;
				if((f.number = parsenum(args[++k])) < 0) {
					err.println("head: invalid option");
					return 2;
				}
			} else if(args[k].startsWith("--characters=") &&
					args[k].length() > 13) {
				f.flags  = (f.flags & ~3) | CHARS;
				if((f.number = parsenum(args[k].substring(13))) < 0) {
					err.println("head: invalid option");
					return 2;
				}
			} else if(args[k].equals("--quiet") ||
					args[k].equals("--silent")) {
				f.flags &= ~VERBOSE;
			} else if(args[k].equals("--verbose")) {
				f.flags |= VERBOSE;
			} else if(args[k].equals("--")) {
				k++;
				break;
			} else if(args[k].equals("-")) {
				break;
			} else if(args[k].startsWith("-")) {
				if(k == 1 && (z = analyzeopt1(args[k], f)) < 0) {
				} else if((z = analyzeopt(args[k], f)) < 0) {
				} else {
					err.println("head: invalid option: " + (char)z);
					return 2;
				}
			} else {
				break;
			}
		}

		if(k >= args.length) {
			head(in, out, "standard input", f, env.getCharset());
		} else {
			for(; k < args.length; k++) {
				if(args[k].equals("-")) {
					head(in, out, "standard input", f,
							env.getCharset());
				} else {
					try {
						ins = fs.getFile(args[k]).getInputStream();
						if(ins == null) {
							err.print("head: ");
							err.print(args[k]);
							err.println(": file not found");
							return 2;
						}
						head(ins, out, args[k], f, env.getCharset());
					} finally {
						if(ins != null)  ins.close();
						ins = null;
					}
				}
				if((f.flags & VERBOSE) != 0)  out.println();
			}
		}
		return 0;
	}

}
