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

import net.morilib.sh.ShEnvironment;
import net.morilib.sh.ShFileSystem;
import net.morilib.sh.ShProcess;
import net.morilib.unix.charset.IntInterval;
import net.morilib.unix.charset.IntRange;
import net.morilib.unix.charset.IntRangeAdder;

public class ShCut implements ShProcess {

	private static enum S1 { INI, BGN, END }

	private static IntRange _parse(String s, int k,
			PrintStream err) {
		IntRangeAdder l = new IntRangeAdder();
		int c, a = 0, b = 0, x = 0, y = 0;
		S1 stat = S1.INI;

		for(int p = k; true; p++) {
			c = p < s.length() ? s.charAt(p) : -1;
			switch(stat) {
			case INI:
				if(c >= '0' && c <= '9') {
					a = p;
					b = p + 1;
					stat = S1.BGN;
				} else if(c == '-') {
					a = p + 1;
					x = 0;
					stat = S1.END;
				} else {
					err.println("cut: invalid list");
					return null;
				}
				break;
			case BGN:
				if(c >= '0' && c <= '9') {
					b = p + 1;
				} else if(c == '-') {
					x = Integer.parseInt(s.substring(a, b)) - 1;
					a = b = p + 1;
					stat = S1.END;
				} else if(c == ',' || c < 0) {
					x = Integer.parseInt(s.substring(a, b)) - 1;
					l.add(new IntInterval(x, x + 1));
					if(c < 0)  return l.toOptimizedRange();
					stat = S1.INI;
				} else {
					err.println("cut: invalid list");
					return null;
				}
				break;
			case END:
				if(c >= '0' && c <= '9') {
					b = p + 1;
				} else if(c == ',' || c < 0) {
					if(a == b) {
						y = Integer.MAX_VALUE;
					} else {
						y = Integer.parseInt(s.substring(a, b));
					}

					if(x > y) {
						err.println("cut: invalid range");
						return null;
					}
					l.add(new IntInterval(x, y));
					if(c < 0)  return l.toOptimizedRange();
					stat = S1.INI;
				} else {
					err.println("cut: invalid list");
					return null;
				}
				break;
			}
		}
	}

	private static byte[] readlineb(
			InputStream in) throws IOException {
		ByteArrayOutputStream b = new ByteArrayOutputStream();
		int c;

		while(true) {
			if((c = in.read()) < 0) {
				if(b.size() == 0)  return null;
				return b.toByteArray();
			} else if(c == '\n') {
				b = new ByteArrayOutputStream();
			} else if(c != '\r') {
				b.write(c);
			} else if((c = in.read()) < 0) {
				if(b.size() == 0)  return null;
				return b.toByteArray();
			} else if(c == '\n') {
				return b.toByteArray();
			} else {
				b.write(c);
			}
		}
	}

	private static String cut(int t, InputStream in, PrintStream out,
			IntRange l, boolean s, String dlm,
			String w, Charset cset) throws IOException {
		BufferedReader rd;
		String[] a;
		String v;
		byte[] b;
		int y, z;

		switch(t) {
		case 'b':
			while((b = readlineb(in)) != null) {
				for(IntInterval x : l.intervals()) {
					y = x.minimum() < 0 ? 0 : x.minimum();
					if(x.minimum() > b.length) {
						v = "";
					} else if(x.maximum() >= b.length) {
						v = new String(b, y, b.length - y,
								"ISO-8859-1");
					} else {
						v = new String(b, y, x.maximum() - y,
								"ISO-8859-1");
					}
					out.print(v);
				}
				out.println();
			}
			break;
		case 'c':
			rd = new BufferedReader(new InputStreamReader(in, cset));
			while((v = rd.readLine()) != null) {
				for(IntInterval x : l.intervals()) {
					y = x.minimum() < 0 ? 0 : x.minimum();
					if(x.minimum() > v.length()) {
						// do nothing
					} else if(x.maximum() >= v.length()) {
						out.print(v.substring(y, v.length()));
					} else {
						out.print(v.substring(y, x.maximum()));
					}
				}
				out.println();
			}
			break;
		case 'f':
			rd = new BufferedReader(new InputStreamReader(in, cset));
			while((v = rd.readLine()) != null) {
				if(v.indexOf(dlm) < 0)  continue;
				a = v.split(dlm);
				for(IntInterval x : l.intervals()) {
					y = x.minimum() < 0 ? 0 : x.minimum();
					if(x.minimum() > a.length) {
						z = -1;
					} else if(x.maximum() >= a.length) {
						z = a.length;
					} else {
						z = x.maximum();
					}
					for(int i = y; i < z; i++) {
						out.print(w);
						out.print(a[i]);
						w = dlm;
					}
				}
				out.println();
			}
			break;
		default:  throw new RuntimeException();
		}
		return w;
	}

	public int main(ShEnvironment env, ShFileSystem fs, InputStream in,
			PrintStream out, PrintStream err,
			String... args) throws IOException {
		InputStream ins = null;
		char[] d = new char[1];
		IntRange l = null;
		boolean s = false;
		int k = 1, t = 0;
		String w = "";

		d[0] = '\t';
		for(; k < args.length; k++) {
			if(args[k].equals("-s") ||
					args[k].equals("--only-delimited")) {
				s = true;
			} else if(args[k].equals("-d") &&
					k < args.length - 1 &&
					args[k + 1].length() > 0) {
				d[0] = args[++k].charAt(0);
			} else if(args[k].startsWith("--delimiter=") &&
					args[k].length() > 12) {
				d[0] = args[k].charAt(12);
			} else if(args[k].startsWith("-d") &&
					args[k].length() > 2) {
				d[0] = args[k].charAt(2);
			} else if(args[k].equals("--")) {
				k++;
				break;
			} else if(args[k].equals("-") ||
					!args[k].startsWith("-")) {
				break;
			} else if(l != null) {
				err.println("cut: invaild arguments");
				return 2;
			} else if(args[k].equals("-b") &&
					k < args.length - 1 &&
					args[k + 1].length() > 0) {
				t = 'b';
				if((l = _parse(args[++k], 0, err)) == null) {
					return 2;
				}
			} else if(args[k].startsWith("--bytes=") &&
					args[k].length() > 8) {
				t = 'b';
				if((l = _parse(args[k], 8, err)) == null) {
					return 2;
				}
			} else if(args[k].equals("-c") &&
					k < args.length - 1 &&
					args[k + 1].length() > 0) {
				t = 'c';
				if((l = _parse(args[++k], 0, err)) == null) {
					return 2;
				}
			} else if(args[k].startsWith("--characters=") &&
					args[k].length() > 13) {
				t = 'c';
				if((l = _parse(args[k], 13, err)) == null) {
					return 2;
				}
			} else if(args[k].equals("-f") &&
					k < args.length - 1 &&
					args[k + 1].length() > 0) {
				t = 'f';
				if((l = _parse(args[++k], 0, err)) == null) {
					return 2;
				}
			} else if(args[k].startsWith("--fields=") &&
					args[k].length() > 9) {
				t = 'f';
				if((l = _parse(args[k], 9, err)) == null) {
					return 2;
				}
			} else {
				err.println("cut: invaild arguments");
				return 2;
			}
		}

		if(l == null) {
			err.println("cut: list is not specified");
			return 2;
		}

		if(k >= args.length) {
			cut(t, in, out, l, s, new String(d), "", env.getCharset());
		} else {
			for(; k < args.length; k++) {
				if(args[k].equals("-")) {
					w = cut(t, in, out, l, s, new String(d), w,
							env.getCharset());
				} else {
					try {
						ins = fs.getFile(args[k]).getInputStream();
						if(ins == null) {
							err.print("cut: ");
							err.print(args[k]);
							err.println(": file not found");
							return 2;
						}
						w = cut(t, ins, out, l, s, new String(d), w,
								env.getCharset());
					} finally {
						if(ins != null)  ins.close();
						ins = null;
					}
				}
			}
		}
		return 0;
	}

}
