/*
 * 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.text.SimpleDateFormat;
import java.util.Comparator;
import java.util.Iterator;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;

import net.morilib.sh.ShEnvironment;
import net.morilib.sh.ShFile;
import net.morilib.sh.ShFileSystem;
import net.morilib.sh.ShProcess;
import net.morilib.sh.ShStat;
import net.morilib.unix.misc.OptionIterator;

public class ShD implements ShProcess {

	private static final SimpleDateFormat FMT =
		new SimpleDateFormat("yyyy/MM/dd  HH:mm:ss");

	private static final int CLASSIFY = 1;
	private static final int RECURSIVE = 2;
	private static final int ALL = 4;
	private static final int DIRECTORY = 8;
	private static final int HIDE_CONTROL_CHARS = 16;
	private static final int REVERSE = 32;
	private static final int SINGLE_COLUMN = 64;

	private static final Comparator<ShStat>
	MT = new Comparator<ShStat>() {

		public int compare(ShStat a, ShStat b) {
			return a.getMtime() < b.getMtime() ?
					-1 : a.getMtime() > b.getMtime() ? 1 : 0;
		}

	};

	private static final Comparator<ShStat>
	NM = new Comparator<ShStat>() {

		public int compare(ShStat a, ShStat b) {
			return a.getFilename().compareTo(b.getFilename());
		}

	};

	private static final Comparator<ShStat>
	RM = new Comparator<ShStat>() {

		public int compare(ShStat a, ShStat b) {
			return a.getMtime() < b.getMtime() ?
					1 : a.getMtime() > b.getMtime() ? -1 : 0;
		}

	};

	private static final Comparator<ShStat>
	RN = new Comparator<ShStat>() {

		public int compare(ShStat a, ShStat b) {
			return -a.getFilename().compareTo(b.getFilename());
		}

	};

	private static void q(SortedMap<String, SortedSet<ShStat>> m,
			SortedSet<ShStat> r, Comparator<ShStat> c,
			int g, ShFileSystem fs, ShFile f) {
		SortedSet<ShStat> z;

		if(f == null) {
			for(ShFile x : fs.getCurrentDirectory().getFiles()) {
				if((g & ALL) != 0 || !x.isHidden()) {
					q(m, r, c, g, fs, x);
				}
			}
		} else if((g & RECURSIVE) != 0 && f.isDirectory()) {
			z = new TreeSet<ShStat>(c);
			for(ShFile x : f.getFiles()) {
				q(m, z, c, g, fs, x);
			}
			m.put(f.toString(), z);
			r.add(f.getStat());
		} else {
			r.add(f.getStat());
		}
	}

	private String _l(int f, String s) {
//		if((f & HIDE_CONTROL_CHARS) != 0) {
//			return s.replaceAll("\\p{Cntrl}", "?");
//		} else {
//			return s;
//		}
		return s;
	}

	private void _pr1(int f, PrintStream out, SortedSet<ShStat> s) {
		for(ShStat x : s) {
			out.println(_l(f, x.getFilename()));
		}
	}

	private void _prs(int f, PrintStream out, SortedSet<ShStat> s) {
		char[] a = new char[4];
		int d = 0, g;
		String t;

		for(ShStat x : s) {
			g = Long.toString(x.getSize()).length();
			d = d < g ? g : d;
		}

		for(ShStat x : s) {
			g    = x.getFlags();
			a[0] = (g & ShStat.DIRECTORY)  != 0 ? 'd' : '-'; 
			a[1] = (g & ShStat.READABLE)   != 0 ? 'r' : '-'; 
			a[2] = (g & ShStat.WRITABLE)   != 0 ? 'w' : '-'; 
			a[3] = (g & ShStat.EXECUTABLE) != 0 ? 'x' : '-'; 

			t = _l(f, x.getFilename());
			if((f & CLASSIFY) == 0) {
				// do nothing
			} else if(a[0] == 'd') {
				t += "/";
			} else if(a[3] == 'x') {
				t += "*";
			}
			out.format("%s %s %" + d + "d %s\n",
					new String(a),
					FMT.format(new java.util.Date(x.getMtime())),
					x.getSize(),
					t);
		}
	}

	public int main(ShEnvironment env, ShFileSystem fs, InputStream in,
			PrintStream out, PrintStream err,
			String... args) throws IOException {
		String[] a = new String[args.length - 1];
		SortedMap<String, SortedSet<ShStat>> m;
		Comparator<ShStat> c = NM;
		SortedSet<ShStat> s, w;
		Iterator<String> t;
		OptionIterator o;
		int f = 0, r = 0;
		String v, d = "";
		ShFile z;

		System.arraycopy(args, 1, a, 0, a.length);
		o = new OptionIterator("FRadqrt1", a);
		while(o.hasNext()) {
			switch(o.nextChar()) {
			case 'F':  f |= CLASSIFY;            break;
			case 'R':  f |= RECURSIVE;           break;
			case 'a':  f |= ALL;                 break;
			case 'd':  f |= DIRECTORY;           break;
			case 'q':  f |= HIDE_CONTROL_CHARS;  break;
			case 'r':  f |= REVERSE;             break;
			case '1':  f |= SINGLE_COLUMN;       break;
			case 't':  c  = MT;                  break;
			default:
				err.print("d: unrecognized option: ");
				err.println((char)o.getErrorOption());
				return 2;
			}
		}

		// reverse condition
		if((f & REVERSE) == 0) {
			// do nothing
		} else if(c == MT) {
			c = RM;
		} else {
			c = RN;
		}

		// query files
		t = o.filenameIterator();
		s = new TreeSet<ShStat>(c);
		m = new TreeMap<String, SortedSet<ShStat>>();
		if(t.hasNext()) {
			while(t.hasNext()) {
				if(!(z = fs.getFile(v = t.next())).isExist()) {
					err.print("d: file `");
					err.print(v);
					err.println("' not found.");
					r = 2;
				} else if((f & DIRECTORY) == 0 && z.isDirectory()) {
					w = new TreeSet<ShStat>(c);
					for(ShFile x : z.getFiles()) {
						q(m, w, c, f, fs, x);
					}
					m.put(z.toString(), w);
				} else {
					q(m, s, c, f, fs, z);
				}
			}
		} else {
			q(m, s, c, f, fs, null);
		}

		// display files
		if((f & SINGLE_COLUMN) != 0) {
			_pr1(f, out, s);
			if(!s.isEmpty())  out.println();
			for(String b : m.keySet()) {
				out.print(d);  d = "\n";
				out.print(b);
				out.println(':');
				_pr1(f, out, m.get(b));
			}
		} else {
			_prs(f, out, s);
			if(!s.isEmpty())  out.println();
			for(String b : m.keySet()) {
				out.print(d);  d = "\n";
				out.print(b);
				out.println(':');
				_prs(f, out, m.get(b));
			}
		}
		return r;
	}

}
