package ch.wlkl.shell;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

/**
 * implements the current environment. 
 * The environment consists of
 * <ul>
 * <li>standard output: {@link #out()}, {@link #write(Object)}
 * <li>standard input: {@link #in()}, {@link #read()}, {@link #read(Object)}
 * <li> external FD: may be allocated on creation {@link #Env(Object[])} and deallocated by {@link #close()}
 * <li>variable pool: {@link #get(Object)}, {@link #via(Object)}, {@link #isDefined(Object)}, {@link #put(Object, Object)}, {@link #remove(Object)}
 * </ul>
 * @author wlkl
 *
 * @param <R>
 * @param <W>
 */
public class Env<R, W> extends Top implements Read<R>, Write<W> {
	public static final Map<Object, Object> variables = new HashMap<Object, Object>();
	Read<R> in = null;
	Write<W> out = null;
	private LazyOut<W> lazyOut = null;
	ArrayList<OpenClose> fdClose = new ArrayList<OpenClose>();

	public Env() {
	}

	public Env(Object... args) {
		reset(args);
	}

	public static <U, V> Env<U, V> make(Env<U, V> type, Object... args) {
		return new Env<U, V>(args);
	}
	public void reset(Object... args) {
		close();
		addRedirs(args);
	}

	@SuppressWarnings("unchecked")
	public <P, Q>Env<R, W>link(Env<P, Q> e) {
		if (in == null) 
			in =(Read<R>) (e.in());
		if (out == null)
			out = (Write<W>) (e.out());
		return this;
	}

	public Read<R> in() {
		return in;
	}
	
	public Write<W> out () {
		return out;
	}

	@SuppressWarnings("unchecked")
	public Env<R,W> addRedirs(Object... objs) {
		String opt = null;
		Object obj = null;
		Cat cat = null;
		String catOpt = null;
		OpenClose oc = null;
		boolean cont;
		for (int ox=0; ox < objs.length; ox+=2) {
			opt = (String) objs[ox];
			obj = ox+1 < objs.length ? objs[ox+1] : null;
			if ( cont = opt.indexOf(Cat.eContinue) >= 0) 
				opt = opt.replace(String.valueOf(Cat.eContinue), "");
			if (cat != null) 
				cat.addIo(Option.defMark + opt, obj);
			else 
				oc = Cat.make(opt, obj);
			if (cont) {
				if (cat == null) {
					cat = new Cat(Option.defMark + opt.charAt(0) + Cat.eObject, oc);
					catOpt = opt.substring(0, 1);
				}
			} else {
				if (cat != null) {
					opt = catOpt;
					oc = cat;
					cat = null;
				}
				if (opt.charAt(0) == '<') {
					if (in != null)
						fail("duplicate std in");
					in = (Read<R>) oc;
				} else if (opt.charAt(0) == '>') {
					if (out != null)
						fail("duplicate std in");
					out = (Write<W>) oc;
				} else if (opt.charAt(0) == Cat.eExtFD) {
				} else {
					fail("bad opt " + opt);					
				}
				if (opt.indexOf(Cat.eNoOpenClose) < 0) {
					Cat.open(opt, oc);
					fdClose.add(oc);
				}
			}
		}
		if (cat != null) 
			fail("end of addRedirs with open concatenation");
		return this;
	}

	public R read() {
		return in.read();
	}

	public void open(String opt) {
		fail("open not allowed, use link instead");
	}

	public void close() {
		for(OpenClose o: fdClose)
			o.close();
		fdClose.clear();
	}
	
//	public Read<W> lazyOut() {
//		return lazyOut;
//	}

	public Read<W> lazyOrClose() {
		if (lazyOut != null && lazyOut.isLazy())
			return lazyOut;
		close();
		return (out instanceof Read) ? (Read<W>)out : null;
	}

	public void lazyReader(Read<W> lo) {
		if (lazyOut == null)
			lazyOut = LazyOut.make(this);
		lazyOut.add(lo);
	}
	
	@SuppressWarnings("unchecked")
	public void readWrite() {
		Ut.write(out, (Read<W>) in);
	}
	
	public void write(W arg) {
		out.write(arg);		
	}

	public Object get(Object key) {
		Object res = variables.get(key);
		if (res == null && ! variables.containsKey(key))
			fail("get on undefined key: " + key);
		return res;		
	}
	
	@SuppressWarnings("unchecked")
	public Object via(Object keyObj) {
		if (! (keyObj instanceof String))
			return get(keyObj);
		String key = (String) keyObj;
		int cx = key.indexOf('.');
		Object o = get(cx < 0 ? key : key.substring(0, cx));
		while (cx >= 0) {
			int dx = key.indexOf('.', cx + 1);
			String k1 = dx > cx ? key.substring(cx+1, dx) : key.substring(cx+1);
			cx = dx;
			try {
				Method m = o.getClass().getMethod(k1, (Class[]) null);
				o = m.invoke(o, (Object[]) null); 
			} catch (Exception em) {
				try {
					Field f = o.getClass().getField(k1);
					o = f.get(o);
				} catch (Exception ef) {
					fail("no method/field " + k1 + " of " + key + " for class " + o.getClass() + " instance " + o
							+ "\nmethod: " + em + "\nfield: " + ef);
				}
			}
		}
		return o;
	}

//	public String getString(Object key) {
//		return String.valueOf(via(key));
//	}
//

//	public <T> T get(Object key, Class<T> cl) {
//		if (! variables.containsKey(key))
//			fail("get on undefined key: " + key);
//		return cl.cast(variables.get(key));
//	}
//	
	public boolean isDefined(Object key) {
		return variables.containsKey(key); 
	}
	
	public boolean isDefined(Object key, Class<?> cl) {
		return variables.containsKey(key) && cl.isInstance(variables.get(key));
	}

	public <T> T put(Object key, T val) {
		variables.put(key, val);
		return val;
	}

	public <T, V extends T> T put(Object key, T type, V val) {
		variables.put(key, val);
		return val;
	}

	public void remove(Object key) {
		variables.remove(key);
	}

	public boolean read(Object key) {
		R val = read();
		if (val == null)
			remove(key);
		else
			put(key, val);
		return val != null;
	}

}


