/*
 * Copyright 2009-2010 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.array;

import java.util.Iterator;

import net.morilib.lisp.ConsIterator;
import net.morilib.lisp.Datum;
import net.morilib.lisp.Environment;
import net.morilib.lisp.LispBoolean;
import net.morilib.lisp.LispInteger;
import net.morilib.lisp.LispMessage;
import net.morilib.lisp.LispVector;
import net.morilib.lisp.Procedure;
import net.morilib.lisp.Subr;
import net.morilib.lisp.Undef;
import net.morilib.lisp.subr.BinaryArgs;
import net.morilib.lisp.subr.SubrUtils;
import net.morilib.lisp.subr.TernaryArgs;
import net.morilib.lisp.subr.UnaryArgs;
import net.morilib.util.Iterators;
import net.morilib.util.primitive.IntegerArrayVector;
import net.morilib.util.primitive.IntegerVector;

/**
 *
 *
 * @author MORIGUCHI, Yuichiro 2011/05/22
 */
public interface LispArray {

	/**
	 * 
	 *
	 *
	 * @author MORIGUCHI, Yuichiro 2011/05/22
	 */
	public static class IsArray extends UnaryArgs {

		/* (non-Javadoc)
		 * @see net.morilib.lisp.subr.UnaryArgs#execute(net.morilib.lisp.Datum, net.morilib.lisp.Environment, net.morilib.lisp.LispMessage)
		 */
		@Override
		protected Datum execute(Datum c1a, Environment env,
				LispMessage mesg) {
			return LispBoolean.getInstance(c1a instanceof LispArray);
		}

	}

	/**
	 * 
	 *
	 *
	 * @author MORIGUCHI, Yuichiro 2011/05/22
	 */
	public static class MakeArray extends Subr {

		/* (non-Javadoc)
		 * @see net.morilib.lisp.Subr#eval(net.morilib.lisp.Datum, net.morilib.lisp.Environment, net.morilib.lisp.LispMessage)
		 */
		@Override
		public Datum eval(Datum body, Environment env,
				LispMessage mesg) {
			ConsIterator itr = new ConsIterator(body);
			Datum s = SubrUtils.nextIf(itr, mesg, body);

			if(s instanceof LispArrayShape) {
				Datum o = Iterators.nextIf(itr, Undef.UNDEF);

				SubrUtils.checkTerminated(itr, body, mesg);
				return LispDefaultArray.malloc((LispArrayShape)s, o);
			} else if(s instanceof LispArrayPrototype) {
				IntegerVector bin = new IntegerArrayVector();

				while(itr.hasNext()) {
					int b = SubrUtils.nextSmallInt(itr, mesg, body);

					if(b < 0) {
						throw mesg.getError(
								"err.srfi25.arraysize.invalid",
								LispInteger.valueOf(b));
					}
					bin.add(b);
				}
				SubrUtils.checkTerminated(itr, body, mesg);
				return (Datum)((LispArrayPrototype)s).makeArray(
						bin.toIntArray());
			} else {
				throw mesg.getError("err.srfi25.require.shape", s);
			}
		}

	}

	/**
	 * 
	 *
	 *
	 * @author MORIGUCHI, Yuichiro 2011/05/22
	 */
	public static class SubrArray extends Subr {

		/* (non-Javadoc)
		 * @see net.morilib.lisp.Subr#eval(net.morilib.lisp.Datum, net.morilib.lisp.Environment, net.morilib.lisp.LispMessage)
		 */
		@Override
		public Datum eval(Datum body, Environment env,
				LispMessage mesg) {
			ConsIterator itr = new ConsIterator(body);
			Datum s = SubrUtils.nextIf(itr, mesg, body);
			LispArray a;

			if(s instanceof LispArrayShape) {
				a = LispDefaultArray.malloc((LispArrayShape)s,
						Undef.UNDEF);
				a.fill(itr);
				return (Datum)a;
			} else {
				throw mesg.getError("err.srfi25.require.shape", s);
			}
		}

	}

	/**
	 * 
	 *
	 *
	 * @author MORIGUCHI, Yuichiro 2011/05/22
	 */
	public static class ArrayRank extends UnaryArgs {

		/* (non-Javadoc)
		 * @see net.morilib.lisp.subr.UnaryArgs#execute(net.morilib.lisp.Datum, net.morilib.lisp.Environment, net.morilib.lisp.LispMessage)
		 */
		@Override
		protected Datum execute(Datum c1a, Environment env,
				LispMessage mesg) {
			if(c1a instanceof LispArray) {
				return LispInteger.valueOf(((LispArray)c1a).rank());
			} else {
				throw mesg.getError("err.srfi25.require.array", c1a);
			}
		}

	}

	/**
	 * 
	 *
	 *
	 * @author MORIGUCHI, Yuichiro 2011/05/22
	 */
	public static class ArrayStart extends BinaryArgs {

		/* (non-Javadoc)
		 * @see net.morilib.lisp.subr.BinaryArgs#execute(net.morilib.lisp.Datum, net.morilib.lisp.Datum, net.morilib.lisp.Environment, net.morilib.lisp.LispMessage)
		 */
		@Override
		protected Datum execute(Datum c1a, Datum c2a, Environment env,
				LispMessage mesg) {
			int dim = SubrUtils.getSmallIntegerExact(c2a, mesg);

			if(c1a instanceof LispArray) {
				try {
					return LispInteger.valueOf(
							((LispArray)c1a).startIndex(dim));
				} catch(IndexOutOfBoundsException e) {
					throw mesg.getError("err.srfi25.dimension.invalid",
							c2a);
				}
			} else {
				throw mesg.getError("err.srfi25.require.array", c1a);
			}
		}

	}

	/**
	 * 
	 *
	 *
	 * @author MORIGUCHI, Yuichiro 2011/05/22
	 */
	public static class ArrayEnd extends BinaryArgs {

		/* (non-Javadoc)
		 * @see net.morilib.lisp.subr.BinaryArgs#execute(net.morilib.lisp.Datum, net.morilib.lisp.Datum, net.morilib.lisp.Environment, net.morilib.lisp.LispMessage)
		 */
		@Override
		protected Datum execute(Datum c1a, Datum c2a, Environment env,
				LispMessage mesg) {
			int dim = SubrUtils.getSmallIntegerExact(c2a, mesg);

			if(c1a instanceof LispArray) {
				try {
					return LispInteger.valueOf(
							((LispArray)c1a).endIndex(dim));
				} catch(IndexOutOfBoundsException e) {
					throw mesg.getError("err.srfi25.dimension.invalid",
							c2a);
				}
			} else {
				throw mesg.getError("err.srfi25.require.array", c1a);
			}
		}

	}

	/**
	 * 
	 *
	 *
	 * @author MORIGUCHI, Yuichiro 2011/05/22
	 */
	public static class ArrayRef extends Subr {

		/* (non-Javadoc)
		 * @see net.morilib.lisp.Subr#eval(net.morilib.lisp.Datum, net.morilib.lisp.Environment, net.morilib.lisp.LispMessage)
		 */
		@Override
		public Datum eval(Datum body, Environment env,
				LispMessage mesg) {
			ConsIterator itr = new ConsIterator(body);
			Datum a = SubrUtils.nextIf(itr, mesg, body);
			Datum d = Iterators.nextIf(itr);
			int[] i;

			if(!(a instanceof LispArray)) {
				throw mesg.getError("err.srfi25.require.array", a);
			} else if(d == null) {
				return ((LispArray)a).getFromArray();
			} else if(d instanceof LispArray) {
				LispArray b = (LispArray)d;
				LispVector v;

				if(b.rank() != 1) {
					throw mesg.getError("err.srfi25.dimension.invalid",
							d);
				}

				v = b.toVector();
				i = new int[v.size()];
				for(int j = 0; j < v.size(); j++) {
					i[j] = SubrUtils.getSmallIntegerExact(v.get(j),
							mesg);
				}
			} else {
				IntegerVector iv = new IntegerArrayVector();

				do {
					iv.add(SubrUtils.getSmallIntegerExact(d, mesg));
				} while((d = Iterators.nextIf(itr)) != null);
				i = iv.toIntArray();
			}

			SubrUtils.checkTerminated(itr, body, mesg);
			try {
				return ((LispArray)a).getFromArray(i);
			} catch(InvalidDimensionException e) {
				throw mesg.getError("err.srfi25.dimension.invalid");
			} catch(IndexOutOfBoundsException e) {
				throw mesg.getError("err.range.invalid");
			}
		}

	}

	/**
	 * 
	 *
	 *
	 * @author MORIGUCHI, Yuichiro 2011/05/22
	 */
	public static class ArraySetS extends Subr {

		/* (non-Javadoc)
		 * @see net.morilib.lisp.Subr#eval(net.morilib.lisp.Datum, net.morilib.lisp.Environment, net.morilib.lisp.LispMessage)
		 */
		@Override
		public Datum eval(Datum body, Environment env,
				LispMessage mesg) {
			ConsIterator itr = new ConsIterator(body);
			Datum a = SubrUtils.nextIf(itr, mesg, body);
			Datum d = SubrUtils.nextIf(itr, mesg, body);
			int[] i;

			if(!(a instanceof LispArray)) {
				throw mesg.getError("err.srfi25.require.array", a);
			} else if(d instanceof LispArray) {
				LispArray b = (LispArray)d;
				LispVector v;

				if(b.rank() != 1) {
					throw mesg.getError("err.srfi25.dimension.invalid",
							d);
				}

				v = b.toVector();
				i = new int[v.size()];
				for(int j = 0; j < v.size(); j++) {
					i[j] = SubrUtils.getSmallIntegerExact(v.get(j),
							mesg);
				}
				d = SubrUtils.nextIf(itr, mesg, body);
			} else {
				IntegerVector iv = new IntegerArrayVector();

				do {
					if(!itr.hasNext()) {
						break;
					}
					iv.add(SubrUtils.getSmallIntegerExact(d, mesg));
				} while((d = Iterators.nextIf(itr)) != null);
				i = iv.toIntArray();
			}

			SubrUtils.checkTerminated(itr, body, mesg);
			try {
				((LispArray)a).setToArray(d, i);
				return Undef.UNDEF;
			} catch(InvalidDimensionException e) {
				throw mesg.getError("err.srfi25.dimension.invalid");
			} catch(IndexOutOfBoundsException e) {
				throw mesg.getError("err.range.invalid");
			} catch(ClassCastException e) {
				throw mesg.getError("err.srfi25.typemismatch");
			} catch(ValueOutOfBoundsException e) {
				throw mesg.getError("err.srfi47.valueoutofrange",
						e.getMessage());
			}
		}

	}

	/**
	 * 
	 *
	 *
	 * @author MORIGUCHI, Yuichiro 2011/05/22
	 */
	public static class ShareArray extends TernaryArgs {

		/* (non-Javadoc)
		 * @see net.morilib.lisp.subr.TernaryArgs#execute(net.morilib.lisp.Datum, net.morilib.lisp.Datum, net.morilib.lisp.Datum, net.morilib.lisp.Environment, net.morilib.lisp.LispMessage)
		 */
		@Override
		protected Datum execute(Datum c1a, Datum c2a, Datum c3a,
				Environment env, LispMessage mesg) {
			if(!(c1a instanceof LispArray)) {
				throw mesg.getError("err.srfi25.require.array", c1a);
			} else if(!(c2a instanceof LispArrayShape)) {
				throw mesg.getError("err.srfi25.require.shape", c2a);
			} else if(!(c3a instanceof Procedure)) {
				throw mesg.getError("err.require.procedure", c3a);
			} else {
				return new LispSharedArray((LispArray)c1a,
						(LispArrayShape)c2a, (Procedure)c3a, env,
						mesg);
			}
		}

	}

	/**
	 * @return
	 */
	public int rank();

	/**
	 * @param dim
	 * @return
	 */
	public int startIndex(int dim);

	/**
	 * @param dim
	 * @return
	 */
	public int endIndex(int dim);

	/**
	 * @return
	 */
	public LispVector toVector();

	/**
	 * @param i
	 * @return
	 */
	public Datum getFromArray(int... is);

	/**
	 * @param d
	 * @param i
	 * @return
	 */
	public void setToArray(Datum d, int... is);

	/**
	 * 
	 * @param a
	 * @return
	 */
	public boolean isIndexEqualTo(LispArray a);

	/**
	 * 
	 * @param a
	 * @return
	 */
	public boolean isEqualTo(LispArray a);

	/**
	 * 
	 * @return
	 */
	public String getTypeSpecifier();

	/**
	 * 
	 * @param itr
	 */
	public void fill(Iterator<Datum> itr);

}
