/*******************************************************************************
 * Copyright (c) 2009-2011, 2013 Luaj.org. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 ******************************************************************************/
package org.luaj.vm2;

import java.lang.ref.WeakReference;

import org.luaj.vm2.LuaTable.Slot;
import org.luaj.vm2.LuaTable.StrongSlot;

/**
 * Subclass of {@link LuaTable} that provides weak key and weak value semantics. 
 * <p> 
 * Normally these are not created directly, but indirectly when changing the mode 
 * of a {@link LuaTable} as lua script executes.  
 * <p>
 * However, calling the constructors directly when weak tables are required from 
 * Java will reduce overhead.  
 */
public class WeakTable implements Metatable {

	private boolean weakkeys, weakvalues;
	private LuaValue backing;

	public static LuaTable make(boolean weakkeys, boolean weakvalues) {
		LuaString mode;
		if ( weakkeys && weakvalues ) {
			mode = LuaString.valueOf("kv");
		} else if ( weakkeys ) {
			mode = LuaString.valueOf("k");
		} else if ( weakvalues ) {
			mode = LuaString.valueOf("v");
		} else {
			return LuaTable.tableOf();
		}
		LuaTable table = LuaTable.tableOf();
		LuaTable mt = LuaTable.tableOf(new LuaValue[] { LuaValue.MODE, mode });
		table.setmetatable(mt);
		return table;
	}

	/**
	 * Construct a table with weak keys, weak values, or both
	 * @param weakkeys true to let the table have weak keys
	 * @param weakvalues true to let the table have weak values
	 */
	public WeakTable(boolean weakkeys, boolean weakvalues, LuaValue backing) {
		this.weakkeys = weakkeys;
		this.weakvalues = weakvalues;
		this.backing = backing;
	}

	public boolean useWeakKeys() {
		return weakkeys;
	}

	public boolean useWeakValues() {
		return weakvalues;
	}

	public LuaValue toLuaValue() {
		return backing;
	}

	public Slot entry(LuaValue key, LuaValue value) {
		value = value.strongvalue();
		if ( value == null )
			return null;
		if ( weakkeys && !( key.isnumber() || key.isstring() || key.isboolean() )) {
			if ( weakvalues && !( value.isnumber() || value.isstring() || value.isboolean() )) {
				return new WeakKeyAndValueSlot( key, value, null );
			} else {
				return new WeakKeySlot( key, value, null );
			}
		}
		if ( weakvalues && ! (value.isnumber() || value.isstring() || value.isboolean() )) {
			return new WeakValueSlot( key, value, null );
		}
		return LuaTable.defaultEntry( key, value );
	}

	public static abstract class WeakSlot implements Slot {

		protected Object key;
		protected Object value;
		protected Slot next;

		protected WeakSlot(Object key, Object value, Slot next) {
			this.key = key;
			this.value = value;
			this.next = next;
		}

		public abstract int keyindex( int hashMask );

		public abstract Slot set(LuaValue value);

		public StrongSlot first() {
			LuaValue key = strongkey();
			LuaValue value = strongvalue();
			if ( key != null && value != null ) {
				return new LuaTable.NormalEntry(key, value);
			} else {
				if ( key == null ) {
					this.key = null;
					this.value = null;
				}
				if ( value == null ) {
					this.value = null;
				}
				return null;
			}
		}

		public StrongSlot find(LuaValue key) {
			StrongSlot first = first();
			return ( first != null ) ? first.find( key ) : null;
		}

		public boolean keyeq(LuaValue key) {
			StrongSlot first = first();
			return ( first != null ) && first.keyeq( key );
		}

		public Slot rest() {
			return next;
		}

		public int arraykey(int max) {
			LuaValue key = strongkey();
			if ( key != null && key.isinttype() ) {
				int k = key.toint();
				return ( k >= 1 && k <= max ) ? k : 0;
			}
			return 0;
		}

		public Slot set(StrongSlot target, LuaValue value) {
			LuaValue key = strongkey();
			if ( key != null && target.find( key ) != null ) {
				return set( value );
			} else if ( key != null ) {
				// Our key is still good.
				next = next.set( target, value );
				return this;
			} else {
				// our key was dropped, remove ourselves from the chain.
				return next.set( target, value );
			}
		}

		public Slot add( Slot entry ) {
			next = ( next != null ) ? next.add( entry ) : entry;
			if ( strongkey() != null && strongvalue() != null ) {
				return this;
			} else {
				return next;
			}
		}

		public Slot remove( StrongSlot target ) {
			LuaValue key = strongkey();
			if ( key == null ) {
				return next.remove( target );
			} else if ( target.keyeq( key ) ) {
				this.value = null;
				return this;
			} else {
				next = next.remove( target );
				return this;
			}
		}

		public Slot relink( Slot rest ) {
			if ( strongkey() != null && strongvalue() != null ) {
				if ( rest == null && this.next == null ) {
					return this;
				} else {
					return copy( rest );
				}
			} else {
				return rest;
			}
		}

		public LuaValue strongkey() {
			return (LuaValue) key;
		}

		public LuaValue strongvalue() {
			return (LuaValue) value;
		}

		protected abstract WeakSlot copy( Slot next );
	}

	static class WeakKeySlot extends WeakSlot {

		private final int keyhash;

		protected WeakKeySlot( LuaValue key, LuaValue value, Slot next ) {
			super(weaken(key), value, next);
			keyhash = key.hashCode();
		}

		protected WeakKeySlot( WeakKeySlot copyFrom, Slot next ) {
			super( copyFrom.key, copyFrom.value, next );
			this.keyhash = copyFrom.keyhash;
		}

		public int keyindex( int mask ) {
			return LuaTable.hashmod( keyhash, mask );
		}

		public Slot set(LuaValue value) {
			this.value = value;
			return this;
		}

		public LuaValue strongkey() {
			return strengthen( key );
		}

		protected WeakSlot copy( Slot rest ) {
			return new WeakKeySlot( this, rest );
		}
	}

	static class WeakValueSlot extends WeakSlot {

		protected WeakValueSlot( LuaValue key, LuaValue value, Slot next ) {
			super( key, weaken(value), next);
		}

		protected WeakValueSlot( WeakValueSlot copyFrom, Slot next ) {
			super( copyFrom.key, copyFrom.value, next );
		}

		public int keyindex( int mask ) {
			return LuaTable.hashSlot( strongkey(), mask );
		}

		public Slot set(LuaValue value) {
			this.value = weaken(value);
			return this;
		}

		public LuaValue strongvalue() {
			return strengthen( value );
		}

		protected WeakSlot copy(Slot next) {
			return new WeakValueSlot( this, next );
		}
	}

	static class WeakKeyAndValueSlot extends WeakSlot {

		private final int keyhash;

		protected WeakKeyAndValueSlot( LuaValue key, LuaValue value, Slot next ) {
			super( weaken(key), weaken(value), next );
			keyhash = key.hashCode();
		}

		protected WeakKeyAndValueSlot(WeakKeyAndValueSlot copyFrom, Slot next) {
			super( copyFrom.key, copyFrom.value, next );
			keyhash = copyFrom.keyhash;
		}

		public int keyindex( int hashMask ) {
			return LuaTable.hashmod( keyhash, hashMask );
		}

		public Slot set(LuaValue value) {
			this.value = weaken(value);
			return this;
		}

		public LuaValue strongkey() {
			return strengthen( key );
		}

		public LuaValue strongvalue() {
			return strengthen( value );
		}

		protected WeakSlot copy( Slot next ) {
			return new WeakKeyAndValueSlot( this, next );
		}
	}

	/**
	 * Self-sent message to convert a value to its weak counterpart
	 * @param value value to convert
	 * @return {@link LuaValue} that is a strong or weak reference, depending on type of {@code value}
	 */
	protected static LuaValue weaken( LuaValue value ) {
		switch ( value.type() ) {
			case LuaValue.TFUNCTION:
			case LuaValue.TTHREAD:
			case LuaValue.TTABLE:
				return new WeakValue(value);
			case LuaValue.TUSERDATA:
				return new WeakUserdata(value);
			default:
				return value;
		}
	}

	/**
	 * Unwrap a LuaValue from a WeakReference and/or WeakUserdata.
	 * @param ref reference to convert
	 * @return LuaValue or null
	 * @see #weaken(LuaValue)
	 */
	protected static LuaValue strengthen(Object ref) {
		if ( ref instanceof WeakReference ) {
			ref = ((WeakReference) ref).get();
		}
		if ( ref instanceof WeakValue ) {
			return ((WeakValue) ref).strongvalue();
		}
		return (LuaValue) ref;
	}

	/** Internal class to implement weak values. 
	 * @see WeakTable
	 */
	static class WeakValue extends LuaValue {
		WeakReference ref;

		protected WeakValue(LuaValue value) {
			ref = new WeakReference(value);
		}

		public int type() {
			illegal("type","weak value");
			return 0;
		}

		public String typename() {
			illegal("typename","weak value");
			return null;
		}

		public String toString() {
			return "weak<"+ref.get()+">";
		}

		public LuaValue strongvalue() {
			Object o = ref.get();
			return (LuaValue)o;
		}

		public boolean raweq(LuaValue rhs) {
			Object o = ref.get();
			return o!=null && rhs.raweq((LuaValue)o);
		}
	}

	/** Internal class to implement weak userdata values. 
	 * @see WeakTable
	 */
	static final class WeakUserdata extends WeakValue {
		private final WeakReference ob;
		private final LuaValue mt;

		private WeakUserdata(LuaValue value) {
			super(value);
			ob = new WeakReference(value.touserdata());
			mt = value.getmetatable();
		}

		public LuaValue strongvalue() {
			Object u = ref.get();
			if ( u != null )
				return (LuaValue) u;
			Object o = ob.get();
			if ( o != null ) {
				LuaValue ud = LuaValue.userdataOf(o,mt);
				ref = new WeakReference(ud);
				return ud;
			} else {
				return null;
			}
		}
	}

	public LuaValue wrap(LuaValue value) {
		return weakvalues ? weaken( value ) : value;
	}

	public LuaValue arrayget(LuaValue[] array, int index) {
		LuaValue value = array[index];
		if (value != null) {
			value = strengthen(value);
			if (value == null) {
				array[index] = null;
			}
		}
		return value;
	}
}
