package common.world;

import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;

import common.block.Block;
import common.collect.ImmutableMap;
import common.collect.ImmutableTable;
import common.collect.Iterables;
import common.collect.Maps;
import common.collect.StandardTable;
import common.collect.Table;
import common.init.BlockRegistry;
import common.properties.IProperty;

public class State {
//	private static final Joiner COMMA_JOINER = Joiner.on(',');
	private static final Function<Entry<IProperty, Comparable>, String> MAP_ENTRY_TO_STRING = 
		new Function<Entry<IProperty, Comparable>, String>() {
			public String apply(Entry<IProperty, Comparable> entry) {
				if(entry == null) {
					return "<NULL>";
				}
				else {
					IProperty prop = entry.getKey();
					return prop.getName() + "=" + prop.getName(entry.getValue());
				}
			}
	};

	private final Block block;
	private final ImmutableMap<IProperty, Comparable> properties;
	private ImmutableTable<IProperty, Comparable, State> map;

	public State(Block block, ImmutableMap<IProperty, Comparable> properties) {
		this.block = block;
		this.properties = properties;
	}

	public <T extends Comparable<T>> State cycleProperty(IProperty<T> property) {
		Collection<T> values = property.getAllowedValues();
		T value = this.getValue(property);
		Iterator<T> iter = values.iterator();
		while(iter.hasNext()) {
			if(iter.next().equals(value)) {
				if(iter.hasNext())
					return this.withProperty(property, (T)iter.next());
				return this.withProperty(property, (T)values.iterator().next());
			}
		}
		return this.withProperty(property, (T)iter.next());
	}

	public Collection<IProperty> getPropertyNames() {
		return Collections.<IProperty>unmodifiableCollection(this.properties.keySet());
	}

	public <T extends Comparable<T>> T getValue(IProperty<T> property) {
		if(!this.properties.containsKey(property)) {
			throw new IllegalArgumentException("Cannot get property " + property + " as it does not exist in " + this.block);
		}
		else {
			return (T)((Comparable)property.getValueClass().cast(this.properties.get(property)));
		}
	}

	public <T extends Comparable<T>, V extends T> State withProperty(IProperty<T> property, V value) {
		if(!this.properties.containsKey(property)) {
			throw new IllegalArgumentException("Cannot set property " + property + " as it does not exist in " + this.block);
		}
		else if(!property.getAllowedValues().contains(value)) {
			throw new IllegalArgumentException("Cannot set property " + property + " to " + value + " on block "
					+ BlockRegistry.REGISTRY.getNameForObject(this.block) + ", it is not an allowed value");
		}
		else {
			return (State)(this.properties.get(property) == value ? this : (State)this.map.get(property, value));
		}
	}

	public ImmutableMap<IProperty, Comparable> getProperties() {
		return this.properties;
	}

	public Block getBlock() {
		return this.block;
	}

	public boolean equals(Object obj) {
		return this == obj;
	}

	public int hashCode() {
		return this.properties.hashCode();
	}

	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append(BlockRegistry.REGISTRY.getNameForObject(this.getBlock()));
		if(!this.getProperties().isEmpty()) {
			sb.append("[");
			Iterable<String> iter = Iterables.transform(this.getProperties().entrySet(), MAP_ENTRY_TO_STRING);
			boolean flag = false;
			for(String s : iter) {
				if(flag)
					sb.append(",");
				else
					flag = true;
				sb.append(s);
			}
//			COMMA_JOINER.appendTo(sb, );
			sb.append("]");
		}
		return sb.toString();
	}

	public void buildTable(Map<Map<IProperty, Comparable>, State> map) {
		if(this.map != null) {
			throw new IllegalStateException();
		}
		else {
			Table<IProperty, Comparable, State> table = new StandardTable<IProperty, Comparable, State>();
			for(IProperty<? extends Comparable> prop : this.properties.keySet()) {
				for(Comparable value : prop.getAllowedValues()) {
					if(value != this.properties.get(prop)) {
						Map<IProperty, Comparable> props = Maps.<IProperty, Comparable>newHashMap(this.properties);
						props.put(prop, value);
						table.put(prop, value, map.get(props));
					}
				}
			}
			this.map = ImmutableTable.<IProperty, Comparable, State>copyOf(table);
		}
	}
}
