/*
 * 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 net.morilib.sh.ShEnvironment;
import net.morilib.sh.ShFileSystem;
import net.morilib.sh.ShProcess;
import net.morilib.sh.misc.ByteRingBuffer;
import net.morilib.sh.misc.CharRingBuffer;
import net.morilib.sh.misc.RingBuffer;

public class ShTail implements ShProcess {

	private static class S1 {
		int 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 = Integer.parseInt(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 int parsenum(String s) {
		int k = 0, c;
		int l;

		for(; k < s.length(); k++) {
			if((c = s.charAt(k)) < '0' || c > '9')  break;
		}
		l = Integer.parseInt(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 tail(InputStream ins, PrintStream out, String name,
			S1 fl) throws IOException {
		RingBuffer<String> rs;
		ByteRingBuffer rb;
		CharRingBuffer rc;
		BufferedReader rd;
		BufferedWriter wr;
		String s;
		byte[] b;
		char[] a;
		int m;

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

		switch(fl.flags & 3) {
		case LINES:
			rd = new BufferedReader(new InputStreamReader(ins));
			rs = new RingBuffer<String>(fl.number);
			while((s = rd.readLine()) != null)  rs.add(s);
			for(String t : rs.toList())  out.println(t);
			break;
		case BYTES:
			b  = new byte[BUFSIZE];
			rb = new ByteRingBuffer(fl.number);
			while((m = ins.read(b)) >= 0)  rb.add(b, m);
			out.write(rb.toArray());
			break;
		case CHARS:
			rd = new BufferedReader(new InputStreamReader(ins));
			wr = new BufferedWriter(new OutputStreamWriter(out));
			a  = new char[BUFSIZE];
			rc = new CharRingBuffer(fl.number);
			while((m = rd.read(a)) >= 0)  rc.add(a, m);
			wr.write(rc.toArray());
			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 = Integer.parseInt(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 = Integer.parseInt(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 = Integer.parseInt(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) {
			tail(in, out, "standard input", f);
		} else {
			for(; k < args.length; k++) {
				if(args[k].equals("-")) {
					tail(in, out, "standard input", f);
				} else {
					try {
						ins = fs.getFile(args[k]).getInputStream();
						if(ins == null) {
							err.print("tail: ");
							err.print(args[k]);
							err.println(": file not found");
							return 2;
						}
						tail(ins, out, args[k], f);
					} finally {
						if(ins != null)  ins.close();
						ins = null;
					}
				}
				if((f.flags & VERBOSE) != 0)  out.println();
			}
		}
		return 0;
	}

}
