/*
 * 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.subr;

import java.math.BigInteger;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import net.morilib.lisp.Cons;
import net.morilib.lisp.ConsIterator;
import net.morilib.lisp.Datum;
import net.morilib.lisp.Keyword;
import net.morilib.lisp.LispCharacter;
import net.morilib.lisp.LispMessage;
import net.morilib.lisp.LispReal;
import net.morilib.lisp.LispSmallInt;
import net.morilib.lisp.LispString;
import net.morilib.lisp.Symbol;
import net.morilib.util.Iterators;

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

	//
	private SubrUtils() {}

	/**
	 * 
	 * @param n
	 * @param d
	 * @return
	 */
	public static BigInteger ceil(BigInteger n, BigInteger d) {
		BigInteger v = n;

		if(v.compareTo(BigInteger.ZERO) > 0) {
			v = v.add(d);
		}
		return v.divide(d);
	}

	/**
	 * 
	 * @param n
	 * @param d
	 * @return
	 */
	public static BigInteger floor(BigInteger n, BigInteger d) {
		BigInteger v = n;

		if(v.compareTo(BigInteger.ZERO) < 0) {
			v = v.subtract(d);
		}
		return v.divide(d);
	}

	/**
	 * 
	 * @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 d
	 * @return
	 */
	public static int getSmallIntegerExact(Datum d, LispMessage mesg) {
		/*if(d instanceof LispInteger) {
			LispInteger li = (LispInteger)d;
			Integer i = SubrUtils.toIntExact(li.bigIntegerValue());

			if(i == null) {
				throw new LispException("small integer required");
			}
			return i.intValue();
		} else {
			throw new LispException("small integer required");
		}*/
		if(d instanceof LispSmallInt) {
			LispSmallInt li = (LispSmallInt)d;

			return li.getExactSmallInt();
		} else {
//			throw new LispException("small integer required");
			throw mesg.getError("err.require.smallint", d);
		}
	}

	/**
	 * 
	 * @param d
	 * @return
	 */
	public static char getCharacter(Datum d, LispMessage mesg) {
		if(d instanceof LispCharacter) {
			return ((LispCharacter)d).getCharacter();
		} else {
//			throw new LispException("character required");
			throw mesg.getError("err.require.char", d);
		}
	}

	/**
	 * 
	 * @param d
	 * @return
	 */
	public static String getString(Datum d, LispMessage mesg) {
		if(d instanceof LispString) {
			return ((LispString)d).getString();
		} else {
			throw mesg.getError("err.require.string", d);
		}
	}

	/**
	 * 
	 * @param d
	 * @return
	 */
	public static double getDouble(Datum d, LispMessage mesg) {
		if(d instanceof LispReal) {
			return d.getRealDouble();
		} else {
			throw mesg.getError("err.require.real", d);
		}
	}

	/**
	 * 
	 * @param d
	 * @param mesg
	 * @return
	 */
	public static String getSymbolName(Datum d, LispMessage mesg) {
		if(d instanceof Symbol) {
			return ((Symbol)d).getName();
		} else {
			throw mesg.getError("err.require.symbol", d);
		}
	}

	/**
	 * 
	 * @param itr
	 * @param body
	 * @param mesg
	 */
	public static void checkTerminated(
			ConsIterator itr, Datum body, LispMessage mesg) {
		if(itr.hasNext()) {
			throw mesg.getError("err.argument", body);
		} else if(!itr.getTerminal().isNil()) {
			throw mesg.getError("err.list", body);
		}
	}

	/**
	 * 
	 * @param itr
	 * @param mesg
	 * @param errcd
	 * @param ed
	 * @return
	 */
	public static Datum nextIf(
			Iterator<Datum> itr, LispMessage mesg, String errcd) {
		return Iterators.nextIf(itr, mesg.getError(errcd));
	}

	/**
	 * 
	 * @param itr
	 * @param mesg
	 * @param errcd
	 * @param ed
	 * @return
	 */
	public static Datum nextIf(
			Iterator<Datum> itr,
			LispMessage mesg, String errcd, String ed) {
		return Iterators.nextIf(itr, mesg.getError(errcd, ed));
	}

	/**
	 * 
	 * @param itr
	 * @param mesg
	 * @param errcd
	 * @param ed
	 * @return
	 */
	public static Datum nextIf(
			Iterator<Datum> itr,
			LispMessage mesg, String errcd, Datum ed) {
		return Iterators.nextIf(itr, mesg.getError(errcd, ed));
	}

	/**
	 * 
	 * @param itr
	 * @param mesg
	 * @param body
	 * @return
	 */
	public static Datum nextIf(
			Iterator<Datum> itr, LispMessage mesg, Datum body) {
		return Iterators.nextIf(itr,
				mesg.getError("err.argument", body));
	}

	/**
	 * 
	 * @param itr
	 * @param s
	 * @param mesg
	 * @return
	 */
	public static String nextString(
			Iterator<Datum> itr, String s, LispMessage mesg) {
		Datum d = Iterators.nextIf(itr, (Datum)null);

		if(d == null) {
			return s;
		} else if(d instanceof LispString) {
			return d.getString();
		} else {
			throw mesg.getError("err.require.string", d);
		}
	}

	/**
	 * 
	 * @param itr
	 * @param mesg
	 * @param body
	 * @return
	 */
	public static String nextString(
			Iterator<Datum> itr, LispMessage mesg, Datum body) {
		Datum d = Iterators.nextIf(itr, (Datum)null);

		if(d == null) {
			throw mesg.getError("err.argument", body);
		} else if(d instanceof LispString) {
			return d.getString();
		} else {
			throw mesg.getError("err.require.string", d);
		}
	}

	/**
	 * 
	 * @param itr
	 * @param s
	 * @param mesg
	 * @return
	 */
	public static String nextSymbolName(
			Iterator<Datum> itr, String s, LispMessage mesg) {
		Datum d = Iterators.nextIf(itr, (Datum)null);

		if(d == null) {
			return s;
		} else if(d instanceof Symbol) {
			return ((Symbol)d).getName();
		} else {
			throw mesg.getError("err.require.symbol", d);
		}
	}

	/**
	 * 
	 * @param itr
	 * @param mesg
	 * @param body
	 * @return
	 */
	public static String nextSymbolName(
			Iterator<Datum> itr, LispMessage mesg, Datum body) {
		Datum d = Iterators.nextIf(itr, (Datum)null);

		if(d == null) {
			throw mesg.getError("err.argument", body);
		} else if(d instanceof Symbol) {
			return ((Symbol)d).getName();
		} else {
			throw mesg.getError("err.require.symbol", d);
		}
	}

	/**
	 * 
	 * @param itr
	 * @param s
	 * @param mesg
	 * @return
	 */
	public static int nextSmallInt(
			Iterator<Datum> itr, int s, LispMessage mesg) {
		Datum d = Iterators.nextIf(itr, (Datum)null);

		if(d == null) {
			return s;
		} else if(d instanceof LispSmallInt) {
			return d.getInt();
		} else {
			throw mesg.getError("err.require.smallint", d);
		}
	}

	/**
	 * 
	 * @param itr
	 * @param mesg
	 * @param body
	 * @return
	 */
	public static int nextSmallInt(
			Iterator<Datum> itr, LispMessage mesg, Datum body) {
		Datum d = Iterators.nextIf(itr, (Datum)null);

		if(d == null) {
			throw mesg.getError("err.argument", body);
		} else if(d instanceof LispSmallInt) {
			return d.getInt();
		} else {
			throw mesg.getError("err.require.smallint", d);
		}
	}

	/**
	 * 
	 * @param itr
	 * @param mesg
	 * @param body
	 * @return
	 */
	public static String nextKeywordName(
			Iterator<Datum> itr, LispMessage mesg, Datum body) {
		Datum d = Iterators.nextIf(itr, (Datum)null);

		if(d == null) {
			throw mesg.getError("err.argument", body);
		} else if(d instanceof Keyword) {
			return ((Keyword)d).getName();
		} else {
			throw mesg.getError("err.require.keyword", d);
		}
	}

	/**
	 * 
	 * @param xx
	 * @param d
	 * @return
	 */
	public static Datum cxxr(String xx, Datum d) {
		Datum p = d;

		for(int i = xx.length() - 1; i >= 0; i--) {
			switch(xx.charAt(i)) {
			case 'a':
				if(p instanceof Cons) {
					p = ((Cons)p).getCar();
				} else {
					return null;
				}
				break;
			case 'd':
				if(p instanceof Cons) {
					p = ((Cons)p).getCdr();
				} else {
					return null;
				}
				break;
			default:
				throw new IllegalArgumentException(xx);
			}
		}
		return p;
	}

	/**
	 * 
	 * @param d
	 * @param mesg
	 * @param msg
	 * @return
	 */
	public static Datum unwrap(Datum d, LispMessage mesg, String m) {
		if(d instanceof Cons) {
			Cons c1 = (Cons)d;

			if(c1.getCdr().isNil()) {
				return c1.getCar();
			} else {
				throw mesg.getError(m, d);
			}
		} else {
			throw mesg.getError(m, d);
		}
	}

	/**
	 * @param d
	 * @return
	 */
	public static Map<Keyword, Datum> keywordArgsToMap(
			Datum d, Datum body, LispMessage mesg) {
		Map<Keyword, Datum> res = new HashMap<Keyword, Datum>();
		ConsIterator itr = new ConsIterator(d);

		while(itr.hasNext()) {
			Datum d1;

			if(!((d1 = itr.next()) instanceof Keyword)) {
				throw mesg.getError("err.require.keyword", d1);
			} else if(!itr.hasNext()) {
				throw mesg.getError("err.argument", body);
			} else {
				res.put((Keyword)d1, itr.next());
			}
		}

		if(!itr.getTerminal().isNil()) {
			throw mesg.getError("err.list", body);
		}
		return res;
	}

	/**
	 * @param c1a
	 * @param class1
	 * @param mesg
	 * @param string
	 */
	public static void checkType(Datum c1a, Class<?> class1,
			LispMessage mesg, String string) {
		if(!class1.isInstance(c1a)) {
			throw mesg.getError(string, c1a);
		}
	}

}
