package io.luna.game.model.mobile.attr;

import com.google.common.base.MoreObjects;

import java.util.IdentityHashMap;
import java.util.Map;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;

/**
 * A {@link String} wrapper also known as an alias that defines behavior and functionality for attributes. {@code String}
 * keys are forcibly interned and aliased on startup into an {@link IdentityHashMap} to be easily accessible as well as high
 * performing.
 * <p>
 * <p>
 * The naming convention for all {@code String} keys is {@code lower_underscore}. Spaces and uppercase letters are not
 * allowed.
 *
 * @param <T> The {@link Object} type represented by this key.
 * @author lare96 <http://github.org/lare96>
 */
public final class AttributeKey<T> {

    /**
     * An {@link IdentityHashMap} of {@link String} keys mapped to their {@code AttributeKey} aliases. All {@code String}s
     * added to this map are forcibly interned so we can compare them by their identity for faster performance.
     */
    public static final Map<String, AttributeKey> ALIASES = new IdentityHashMap<>();

    /**
     * Aliases all attributes that will be used while Luna is online. This is called eagerly on startup.
     */
    public static void init() {
        AttributeKey.forPersistent("run_energy", 100);
        AttributeKey.forPersistent("first_login", true);
    }

    /**
     * Aliases {@code name} with an initial value of {@code initialValue} that will be written to and read from the character
     * file.
     *
     * @param name The name of this key.
     * @param initialValue The initial value of this key.
     */
    public static <T> void forPersistent(String name, T initialValue) {
        ALIASES.put(name, new AttributeKey<>(name, initialValue, true));
    }

    /**
     * Aliases {@code name} with an initial value of {@code initialValue}.
     *
     * @param name The name of this key.
     * @param initialValue The initial value of this key.
     */
    public static <T> void forTransient(String name, T initialValue) {
        ALIASES.put(name, new AttributeKey<>(name, initialValue, false));
    }

    /**
     * The name of this alias.
     */
    private final String name;

    /**
     * The initial value of this alias.
     */
    private final T initialValue;

    /**
     * If the value of this alias should be serialized.
     */
    private final boolean isPersistent;

    /**
     * The fully-qualified class name of this attribute type.
     */
    private final String typeName;

    /**
     * Creates a new {@link AttributeKey}.
     *
     * @param name The name of this alias.
     * @param initialValue The initial value of this alias.
     * @param isPersistent If the value of this alias should be serialized.
     */
    private AttributeKey(String name, T initialValue, boolean isPersistent) {
        checkArgument(!name.isEmpty(), "attribute name length <= 0");
        checkState(!ALIASES.containsKey(name.intern()), "attribute already aliased");

        this.name = name.intern();
        this.initialValue = initialValue;
        this.isPersistent = isPersistent;
        typeName = initialValue.getClass().getName();
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this).add("name", name).add("persistent", isPersistent).add("type", typeName)
            .toString();
    }

    /**
     * @return The name of this alias.
     */
    public String getName() {
        return name;
    }

    /**
     * @return The initial value of this alias.
     */
    public T getInitialValue() {
        return initialValue;
    }

    /**
     * @return {@code true} if the value of this alias should be serialized, {@code false} otherwise.
     */
    public boolean isPersistent() {
        return isPersistent;
    }

    /**
     * @return The fully-qualified class name of this attribute type.
     */
    public String getTypeName() {
        return typeName;
    }
}

@@ -34,16 +34,59 @@
 .attr;%0A%0A
+import com.google.common.base.CharMatcher;%0A
 import c
@@ -581,23 +581,16 @@
 %0A * %3Cp%3E%0A
- * %3Cp%3E%0A
  * The n
@@ -2952,24 +2952,111 @@
 rsistent) %7B%0A
+        checkState(!ALIASES.containsKey(name.intern()), %22attribute already aliased%22);%0A%0A
         chec
@@ -3113,16 +3113,17 @@
 %3C= 0%22);%0A
+%0A
         
@@ -3131,76 +3131,311 @@
 heck
-State(!ALIASES.containsKey(name.intern()), %22attribute already aliase
+Argument(CharMatcher.WHITESPACE.matchesNoneOf(name),%0A            %22attribute name has whitespace characters, use underscores characters instead%22);%0A%0A        checkArgument(CharMatcher.JAVA_UPPER_CASE.matchesNoneOf(name),%0A            %22attribute name has uppercase characters, use lowercase characters instea
 d%22);

Check for whitespace and uppercase before aliasing attributes.