/*
 * 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.HashMap;
import java.util.Map;

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

	/**
	 * 
	 *
	 *
	 * @author MORIGUCHI, Yuichiro 2011/05/24
	 */
	public 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, this);
					flyweight.put(name, res);
				}
			}
			return res;
		}

	}

	//
	static final Namespace DEFAULT_NAMESPACE = new Namespace();
	static final int NO_FOLD_CASE = 0;
	static final int UPPER_FOLD_CASE = 1;
	static final int LOWER_FOLD_CASE = 2;

	//
	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");

	//
	static int foldCase = NO_FOLD_CASE;

	// ----------------------------------------------------
	// symbol cannot serialize
	// ----------------------------------------------------
	private static Namespace namespace = DEFAULT_NAMESPACE;

	//
	private String name;
	private Symbol replace = null;
	private Symbol macroSymbol = null;
	private Namespace ns = null;

	//
	private Symbol(String name) {
		this.name = name;
	}

	//
	private Symbol(String name, Namespace ns) {
		this.name = name;
		this.ns   = ns;
	}

	/**
	 * 
	 * @param name
	 * @return
	 */
	public static Symbol getSymbol(String name) {
		switch(foldCase) {
		case NO_FOLD_CASE:
			return namespace.getSymbol(name);
		case LOWER_FOLD_CASE:
		case UPPER_FOLD_CASE:
			return namespace.getSymbol(name.toLowerCase());
		default:  throw new RuntimeException();
		}
	}

	/**
	 * 
	 * @param name
	 * @return
	 */
	public static Symbol getSymbolWithoutFoldCase(String name) {
		return namespace.getSymbol(name);
	}

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

		return res;
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.SymbolName#getName()
	 */
	public String getName() {
		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());
	}

	/**
	 * 
	 * @return
	 */
	public boolean isNormal() {
		String s = getName();

		for(int i = 0; i < s.length(); i++) {
			if(LispUtils.isReservedCharacter(s.charAt(i))) {
				return false;
			} else if(foldCase != NO_FOLD_CASE &&
					Character.isUpperCase(s.charAt(i))) {
				return false;
			}
		}
		return true;
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.Atom#print()
	 */
	public String toDisplay() {
		if(!isNormal()) {
			return "|" + getName() + "|";
		} else if(foldCase == NO_FOLD_CASE) {
			return getName();
		} else if(foldCase == LOWER_FOLD_CASE) {
			return getName().toLowerCase();
		} else if(foldCase == UPPER_FOLD_CASE) {
			return getName().toUpperCase();
		} else {
			throw new RuntimeException();
		}
	}

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

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

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

	/**
	 * 
	 */
	public boolean isMacroBound() {
		return name != null && name.indexOf("#:") >= 0;
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.Datum#toString()
	 */
	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() {
		int x = (name == null) ? -1 : name.indexOf("#:");

		return (x < 0) ? this : Symbol.getSymbol(name.substring(0, x));
	}

	/**
	 * 
	 * @return
	 */
	public Namespace getNamespace() {
		return ns;
	}

}
