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

import net.morilib.lisp.nano.subr.SubrUtils;
import net.morilib.lisp.nano.subr.TernaryArgs;

/**
 * 
 *
 *
 * @author MORIGUCHI, Yuichiro 2009
 */
public final class LispString extends Atom
implements java.io.Serializable {

	//
	private static final long serialVersionUID = -2492989855710883745L;

	/**
	 * 
	 *
	 *
	 * @author MORIGUCHI, Yuichiro 2011/04/10
	 */
	public static class StringSetS 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) {
			char[] cs = SubrUtils.getString(c1a, mesg).toCharArray();
			int i = SubrUtils.getSmallInt(c2a, mesg);
			char ch = SubrUtils.getCharacter(c3a, mesg);

			if(i < 0 || i >= cs.length) {
				throw mesg.getError("err.string.outofrange", c2a);
			}
			cs[i] = ch;
			((LispString)c1a).value = new String(cs);
			return Undef.UNDEF;
		}

	}

	/**
	 * 
	 *
	 *
	 * @author MORIGUCHI, Yuichiro 2011/04/10
	 */
	public static class StringCopyS extends Subr {

		/**
		 * 
		 * @param c1a
		 * @param c2a
		 * @param c3a
		 * @param env
		 * @param mesg
		 * @return
		 */
		protected Datum execute(
				Datum c1a, Datum c2a, Datum c3a,
				Environment env, LispMessage mesg) {
			String s = SubrUtils.getString(c3a, mesg);

			return execute(c1a, c2a, s, 0, s.length(), mesg);
		}

		/**
		 * 
		 * @param c1a
		 * @param c2a
		 * @param s
		 * @param b
		 * @param e
		 * @return
		 */
		protected Datum execute(
				Datum c1a, Datum c2a,
				String str, int b, int e, LispMessage mesg) {
			char[] t = SubrUtils.getString(c1a, mesg).toCharArray();
			char[] s = str.toCharArray();
			int ts = SubrUtils.getSmallInt(c2a, mesg);

			if(ts >= t.length) {
				throw mesg.getError("err.string.outofrange", c1a);
			} else if(e - b > t.length - ts) {
				throw mesg.getError("err.string.outofrange");
			}
			System.arraycopy(s, b, t, ts, e - b);
			((LispString)c1a).value = new String(t);
			return Undef.UNDEF;
		}

		/* (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) {
			List<Datum> l = LispUtils.consToList(body, mesg);
			String s;
			int b, e;

			if(l.size() == 3) {
				return execute(
						l.get(0), l.get(1), l.get(2), env, mesg);
			} else if(l.size() == 4) {
				s = SubrUtils.getString(l.get(2), mesg);
				b = SubrUtils.getSmallInt(l.get(3), mesg);
				e = s.length();
				if(b >= s.length()) {
					throw mesg.getError("err.string.outofrange", l.get(3));
				}
			} else if(l.size() == 5) {
				s = SubrUtils.getString(l.get(2), mesg);
				b = SubrUtils.getSmallInt(l.get(3), mesg);
				e = SubrUtils.getSmallInt(l.get(4), mesg);
				if(b >= s.length()) {
					throw mesg.getError("err.string.outofrange", l.get(3));
				} else if(e > s.length()) {
					throw mesg.getError("err.string.outofrange", l.get(4));
				} else if(b > e) {
					throw mesg.getError("err.range.invalid");
				}
			} else {
				throw mesg.getError("err.argument", body);
			}
			return execute(l.get(0), l.get(1), s, b, e, mesg);
		}

	}

	/**
	 * 
	 */
	public static final LispString EMPTY = new LispString("");

	//
	private String value;

	/**
	 * 
	 * @param value
	 */
	public LispString(String value) {
		if(value == null) {
			throw new NullPointerException("string is null");
		}
		this.value = value;
	}

	/**
	 * 
	 * @param str
	 */
	public LispString(LispString str) {
		if(str == null) {
			throw new NullPointerException();
		}
		value = str.value;
	}

	/**
	 * 
	 * @param s
	 * @return
	 */
	public static LispString valueOf(String s) {
		return new LispString(s);
	}

	/**
	 * 
	 * @param a
	 * @return
	 */
	public boolean isEqualTo(LispString a) {
		return value.equals(a.value);
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.Datum#getString()
	 */
	public String getString() {
		return value;
	}

	/**
	 * 
	 * @param value
	 */
	public void setString(String value) {
		this.value = value;
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.Atom#toLispString()
	 */
	public LispString toLispString() {
		return this;
	}

	/*
	 * (non-Javadoc)
	 * @see net.morilib.lisp.Atom#print()
	 */
	public String toDisplay() {
		return value;
	}

	/*
	 * (non-Javadoc)
	 * @see net.morilib.lisp.Atom#getResult()
	 */
	public String toWrite() {
		StringBuilder b = new StringBuilder();

		for(int i = 0; i < value.length(); i++) {
			char c = value.charAt(i);

			if(c == '\\') {
				b.append("\\\\");
			} else if(c == '\"') {
				b.append("\\\"");
			} else {
				b.append(c);
			}
		}

		return "\"" + b.toString() + "\"";
	}

	/*
	 * (non-Javadoc)
	 * @see net.morilib.lisp.Datum#isTypeString()
	 */
	public boolean isTypeString() {
		return true;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if(obj instanceof LispString) {
			return value.equals(((LispString)obj).value);
		}
		return false;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		return value.hashCode();
	}

	/*
	 * (non-Javadoc)
	 * @see net.morilib.lisp.Datum#toString()
	 */
	public String toString() {
		return "\"" + value + "\"";
	}

}
