/*
 * Copyright 2009 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.lisp.nano;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import net.morilib.lisp.nano.exlib.DisplayObject;
import net.morilib.lisp.nano.exlib.StringBuf;
import net.morilib.lisp.nano.exlib.WriteObject;
import net.morilib.lisp.nano.subr.Add;
import net.morilib.lisp.nano.subr.Car;
import net.morilib.lisp.nano.subr.Cdr;
import net.morilib.lisp.nano.subr.IsEq;
import net.morilib.lisp.nano.subr.IsEqv;
import net.morilib.lisp.nano.subr.IsNull;
import net.morilib.lisp.nano.subr.IsPair;
import net.morilib.lisp.nano.subr.IsVector;
import net.morilib.lisp.nano.subr.MakeCons;
import net.morilib.lisp.nano.subr.VectorLength;
import net.morilib.lisp.nano.subr.VectorRef;

/**
 * 
 *
 *
 * @author MORIGUCHI, Yuichiro 2009
 */
public final class LispUtils {

	//
	private static final String DI2 =
			"/net/morilib/lisp/nano/display.scm";
	static final Scheme DISP;

	//
	private static void _bind(Environment e, String s, Datum d) {
		e.bindDatum(Symbol.getSymbol(s), d);
	}

	//
	static {
		Environment e;

		e = new Environment();
		_bind(e, "if",             new SynIf());
		_bind(e, "lambda",         new SynLambda());
		_bind(e, "define",         new SynDefine());
		_bind(e, "quote",          new SynQuote());
		_bind(e, "begin",          new SynBegin());
		_bind(e, "cons",           new MakeCons());
		_bind(e, "car",            new Car());
		_bind(e, "cdr",            new Cdr());
		_bind(e, "null?",          new IsNull());
		_bind(e, "pair?",          new IsPair());
		_bind(e, "eq?",            new IsEq());
		_bind(e, "eqv?",           new IsEqv());
		_bind(e, "+",              new Add());
		_bind(e, "vector?",        new IsVector());
		_bind(e, "vector-ref",     new VectorRef());
		_bind(e, "vector-length",  new VectorLength());
		_bind(e, "display-object", new DisplayObject());
		_bind(e, "write-object",   new WriteObject());
		DISP = new Scheme(e, LispMessage.getInstance());

		try {
			NanoParser.read(DISP,
					LispUtils.class.getResourceAsStream(DI2));
		} catch (IOException e1) {
			throw new RuntimeException(e1);
		}
	}

	//
	private LispUtils() {
		// do nothing
	}

	/**
	 * 
	 * @param d
	 * @return
	 */
	public static String toDisplay(Datum d) {
		StringBuf b = new StringBuf();

		try {
			DISP.call("display-all", b, d);
		} catch(NoSuchMethodException e) {
			throw new RuntimeException(e);
		}
		return b.toDisplay();
	}

	/**
	 * 
	 * @param d
	 * @return
	 */
	public static String toWrite(Datum d) {
		StringBuf b = new StringBuf();

		try {
			DISP.call("write-all", b, d);
		} catch(NoSuchMethodException e) {
			throw new RuntimeException(e);
		}
		return b.toDisplay();
	}

	/**
	 * 
	 * @param d
	 * @return
	 */
	public static String toWriteWithoutSS(Datum d) {
		StringBuf b = new StringBuf();

		try {
			DISP.call("write-all", b, d);
		} catch(NoSuchMethodException e) {
			throw new RuntimeException(e);
		}
		return b.toDisplay();
	}

	/**
	 * 
	 * @param d
	 * @param dot
	 * @return
	 */
	public static Datum listToCons(
			Collection<? extends Datum> d, Datum dot) {
		Cons res = null;
		Cons c2 = null;

		for(Datum x : d) {
			if(res == null) {
				res = c2 = new Cons();
				c2.setCar(x);
			} else {
				Cons c3 = new Cons(x, Nil.NIL);
				c2.setCdr(c3);
				c2 = c3;
			}
		}

		if(res == null) {
			return dot;
		} else {
			c2.setCdr(dot);
			return res;
		}
	}

	/**
	 * 
	 * @param d
	 * @return
	 */
	public static Datum listToCons(Collection<? extends Datum> d) {
		return listToCons(d, Nil.NIL);
	}

	/**
	 * 
	 * @param d
	 * @param mesg
	 * @return
	 */
	public static List<Datum> consToList(Datum d, LispMessage mesg) {
		List<Datum> res = new ArrayList<Datum>();
		Datum dd = d;

		while(dd != Nil.NIL) {
			if(dd instanceof Cons) {
				res.add(((Cons)dd).getCar());
				dd = ((Cons)dd).getCdr();
			} else {
				throw mesg.getError("err.list");
			}
		}
		return res;
	}

	/**
	 * 
	 * @param d
	 * @return
	 */
	public static List<Datum> consToListIgnoreDot(Datum d) {
		List<Datum> res = new ArrayList<Datum>();
		Datum dd = d;

		while(dd != Nil.NIL) {
			if(dd instanceof Cons) {
				res.add(((Cons)dd).getCar());
				dd = ((Cons)dd).getCdr();
			} else {
				break;
			}
		}
		return res;
	}

	/**
	 * 
	 * @param arr
	 * @param cdr
	 * @return
	 */
	public static Datum toConsList(Object[] arr, Object cdr) {
		ConsListBuilder lst = new ConsListBuilder();

		for(Object o : arr) {
			lst.append(toDatum(o));
		}
		return lst.get(toDatum(cdr));
	}

	/**
	 * 
	 * @param arr
	 * @return
	 */
	public static Datum toConsList(Object[] arr) {
		return toConsList(arr, Nil.NIL);
	}

	/**
	 * 
	 * @param v
	 * @return
	 */
	public static LispExactReal bigDecimalToRational(
			BigDecimal v) {
		BigInteger n = v.unscaledValue();

		if(v.scale() > 0) {
			BigInteger d = BigInteger.TEN.pow(v.scale());

			return LispRational.newRational(n, d);
		} else if(v.scale() < 0) {
			BigInteger d = BigInteger.TEN.pow(-v.scale());

			return LispInteger.valueOf(n.multiply(d));
		} else {
			return LispInteger.valueOf(n);
		}
	}

	/**
	 * 
	 * @param o
	 * @return
	 */
	public static Datum toDatum(Object o) {
		Class<?> cl = (o == null) ? null : o.getClass();

		if(o instanceof Datum) {
			return (Datum)o;
		} else if(o instanceof Byte) {
			return LispInteger.valueOf(((Byte)o).intValue());
		} else if(o instanceof Short) {
			return LispInteger.valueOf(((Short)o).intValue());
		} else if(o instanceof Integer) {
			return LispInteger.valueOf(((Integer)o).intValue());
		} else if(o instanceof Long) {
			return LispInteger.valueOf(((Long)o).longValue());
		} else if(o instanceof BigInteger) {
			return LispInteger.valueOf((BigInteger)o);
		} else if(o instanceof BigDecimal) {
			return bigDecimalToRational((BigDecimal)o);
		} else if(o instanceof Float) {
			return new LispDouble(((Float)o).doubleValue());
		} else if(o instanceof Double) {
			return new LispDouble(((Double)o).doubleValue());
		} else if(o instanceof String) {
			return new LispString((String)o);
		} else if(o instanceof Character) {
			return new LispCharacter(((Character)o).charValue());
		} else if(o instanceof Boolean) {
			return LispBoolean.getInstance(((Boolean)o).booleanValue());
		} else if(cl.isArray()) {
			ConsListBuilder bld = new ConsListBuilder();
			int len = Array.getLength(o);

			for(int i = 0; i < len; i++) {
				bld.append(toDatum(Array.get(o, i)));
			}
			return bld.get();
		} else {
			throw new ClassCastException();
		}
	}

	/**
	 * 
	 * @param d
	 * @param lst
	 * @return
	 */
	public static Datum listDot(Datum d, Datum... lst) {
		ConsListBuilder b = new ConsListBuilder();

		for(Datum o : lst) {
			b.append(o);
		}
		return b.get(d);
	}

	/**
	 * 
	 * @param lst
	 * @return
	 */
	public static Datum list(Datum... lst) {
		return listDot(Nil.NIL, lst);
	}

	/**
	 * @param charAt
	 * @return
	 */
	public static boolean isReservedCharacter(char c) {
		return (c == '(' || c == '[' ||
				c == ')' || c == ']' || c == '#' ||
				Character.isWhitespace(c));
	}

	/**
	 * 
	 * @param number
	 * @return
	 */
	public static BigInteger toIntegerExact(double number) {
		try {
			BigDecimal dec;
	
			if(Double.isInfinite(number) || Double.isNaN(number)) {
				return null;
			}
			dec = new BigDecimal(number);
			return dec.toBigIntegerExact();
		} catch(ArithmeticException e) {
			return null;
		}
	}

	/**
	 * 
	 * @param i
	 * @return
	 */
	public static Integer toIntExact(BigInteger i) {
		int r = i.intValue();
	
		if(i.equals(BigInteger.valueOf(r))) {
			return new Integer(r);
		} else {
			return null;
		}
	}

	/**
	 * 
	 * @param env
	 * @param s
	 * @return
	 * @throws IOException
	 */
	public static File getFile(
			Environment env, String s) throws IOException {
		File  f = new File(s.replaceFirst("^~/",
				System.getProperty("user.home") + "/"));
	
		if(f.isAbsolute()) {
			return f;
		} else {
			return new File(
					System.getProperty("user.dir"),
					s).getCanonicalFile();
		}
	}

}
