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

import java.util.HashMap;
import java.util.Map;

import net.morilib.lisp.sos.LispType;

/**
 * 
 *
 *
 * @author MORIGUCHI, Yuichiro 2011/05/24
 */
public final class Symbol extends Atom implements SymbolName {

	//
	/*package*/ static class Namespace {

		//
		private Map<String, Symbol> flyweight =
			new HashMap<String, Symbol>();

		//
		/*package*/ Namespace() {
			// do nothing
		}

		/**
		 * 
		 * @param name
		 * @return
		 */
		public Symbol getSymbol(String name) {
			Symbol res;

			if(name == null) {
				throw new NullPointerException("Symbol");
			}

			synchronized(this) {
				res = flyweight.get(name);
				if(res == null) {
					res = new Symbol(name);
					flyweight.put(name, res);
				}
			}
			return res;
		}

	}

	//
	/*package*/ static final Namespace DEFAULT_NAMESPACE =
		new Namespace();

	//
	public static final Symbol QUOTE =
		DEFAULT_NAMESPACE.getSymbol("quote");
	public static final Symbol QUASIQUOTE =
		DEFAULT_NAMESPACE.getSymbol("quasiquote");
	public static final Symbol UNQUOTE =
		DEFAULT_NAMESPACE.getSymbol("unquote");
	public static final Symbol UNQUOTE_SPLICING =
		DEFAULT_NAMESPACE.getSymbol("unquote-splicing");

	// ----------------------------------------------------
	//private static long genIdSeq = 1;
	//private static Map<String, Symbol> flyweight;
	private static Namespace namespace = DEFAULT_NAMESPACE;

	//
	private String name;
	//private long genId = 0;
	private Symbol replace = null;
	private boolean replaced = false;
	private Symbol macroSymbol = null;

	//
	private Symbol(String name) {
		//if(name == null) {
		//	throw new NullPointerException("Symbol");
		//}
		this.name = name;
	}

	/**
	 * 
	 * @param name
	 * @return
	 */
	public static Symbol getSymbol(String name) {
		/*Symbol res;

		if(name == null) {
			throw new NullPointerException("Symbol");
		}

		synchronized(Symbol.class) {
			if(flyweight == null) {
				//flyweight = new HashMap<String, Symbol>();
				flyweight = new WeakHashMap<String, Symbol>();
			}

			res = flyweight.get(name);
			if(res == null) {
				res = new Symbol(name);
				flyweight.put(name, res);
			}
		}
		return res;*/
		return namespace.getSymbol(name);
	}

	//
	/*package*/ static Symbol newAndMark(
			Map<Symbol, Symbol> box, Symbol sym, boolean mark) {
		Symbol res;

		if(sym == null) {
			throw new NullPointerException();
		}

		if(mark) {
			res = box.get(sym);

			if(res == null) {
				//res = new Symbol(sym.name);
				res = new Symbol(null);
				//res.genId = sym.genId;
				res.replace = sym;
				res.replaced = true;
				res.macroSymbol = sym;
				box.put(sym, res);
			}
		} else {
			if(!sym.replaced) {
				res = sym;
			} else if((res = sym.replace) == null) {
				throw new NullPointerException();
			}
		}
		return res;
	}

	/**
	 * 
	 * @return
	 */
	public static Symbol gensym() {
		Symbol res = new Symbol(null);

		//res.genId = genIdSeq++;
		return res;
	}

	/**
	 * 
	 * @param sym
	 * @return
	 */
	public static Symbol encloseSymbol(Symbol sym) {
		Symbol res = new Symbol(null);

		//res.genId = genIdSeq++;
		res.macroSymbol = sym.macroSymbol;
		return res;
	}

	/**
	 * 
	 * @param a
	 * @return
	 */
	public boolean isEqv(Atom a) {
		return equals(a);
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.SymbolName#getName()
	 */
	public String getName() {
		//return (genId != 0) ? "#" + name + genId : name;
		return (name == null) ? "#:" + hashCode() : name;
	}

	/**
	 * 
	 * @return
	 */
	public boolean isGenerated() {
		//return genId > 0;
		return name == null;
	}

	/**
	 * @return the replaced
	 */
	public boolean isReplaced() {
		return replace != null;
	}

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

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

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

	/*
	public boolean equals(Object o) {
		if(o instanceof Symbol) {
			Symbol s = (Symbol)o;

			return (name.equals(s.name) &&
					(genId == s.genId) &&
					(replaced == s.replaced));
		}
		return false;
	}

	public int hashCode() {
		int l = 17;

		l = 37 * l + name.hashCode();
		l = 37 * l + (int)(l ^ (l >>> 32));
		return l;
	}
	*/

	//
	/*package*/ Symbol getEnclosedSymbol() {
		Symbol sym = this;

		while(sym.macroSymbol != null) {
			sym = sym.macroSymbol;
		}
		return sym;
		//return macroSymbol;
	}

	/*package*/ /*Symbol getReplacedRoot() {
		Symbol sym = this;

		while(sym.replace != null) {
			sym = sym.replace;
		}
		return sym;
	}*/

	/*
	 * 
	 */
	public String toString() {
		return ("Symbol:" + getName() + ":" +
				System.identityHashCode(this));
	}

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

	/* (non-Javadoc)
	 * @see net.morilib.lisp.SymbolName#getSymbol()
	 */
	public Symbol getSymbol() {
		return this;
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.Datum#getType()
	 */
	@Override
	public LispType getType() {
		return LispType.SYMBOL;
	}

}
