package ru.yandex.webmaster3.core.semantic.review_business.biz.model;


import com.google.common.collect.Sets;
import org.json.JSONException;
import ru.yandex.webmaster3.core.semantic.review_business.ModelUtils;
import ru.yandex.webmaster3.core.semantic.review_business.biz.model.impl.json.TagJsonConversions;

import java.util.Set;

/**
 * Tags are extension point of the review.
 * Typically tags should contain additional rating
 * or rating about business features such as
 * * service level
 * * cost adequacy
 * * etc...
 * <p/>
 * Getters of this class are used to count hashCode, so to keep contract (see first point in {@link Object#hashCode()})
 * they should return objects which have same hashCode value during application run time
 * In general - implementation should be immutable and return references which are also immutable
 * <p/>
 * @author Dima Schitinin <dimas@yandex-team.ru>
 */
public abstract class Tag {
    /**
     * Type of the tag.
     * We expect that number of available types should be visible.
     * Type is used for understand how to work with tag:
     * * how to serialize it
     * * which visual control should be used for edit tag
     * * etc...
     */
    public enum Type {
        /**
         * Boolean type may be used for tags that contains
         * info about "like" or "dislike"
         * for some feature of the business.
         * Deprecated. Use ATTITUDE
         */
        @Deprecated
        BOOLEAN {
            @Override
            public boolean validateValue(String value) {
                return false;
            }

            @Override
            public Float normalizeRating5(String value) {
                return null;
            }
        },

        /**
         * Tags represent 5-stars ratings should have this type
         */
        RATING5 {
            private final Set<String> values = Sets.newHashSet("1", "2", "3", "4", "5");

            @Override
            public boolean validateValue(String value) {
                return values.contains(value);
            }

            @Override
            public Float normalizeRating5(String value) {
                return Float.parseFloat(value);
            }
        },

        /**
         * Tags represent 10-stars ratings should have this type
         */
        RATING10 {
            private final Set<String> values = Sets.newHashSet("1", "2", "3", "4", "5", "6", "7", "8", "9", "10");

            @Override
            public boolean validateValue(String value) {
                return values.contains(value);
            }

            @Override
            public Float normalizeRating5(String value) {
                return ((Float.valueOf(value) - 1) / 9) * 4 + 1;
            }
        },

        /**
         * Deprecated. Use ATTITUDE
         */
        @Deprecated
        RATING3 {
            private final Set<String> values = Sets.newHashSet("1", "2", "3");

            @Override
            public boolean validateValue(String value) {
                return values.contains(value);
            }

            @Override
            public Float normalizeRating5(String value) {
                return null;
            }
        },

        /**
         * Tags represent choice like No/Yes/Unknown
         */
        ATTITUDE {
            private final Set<String> values = Sets.newHashSet("-1", "0", "+1", "1");

            @Override
            public boolean validateValue(String value) {
                return values.contains(value);
            }

            @Override
            public Float normalizeRating5(String value) {
                return (Float.parseFloat(value) + 1) * 2 + 1;
            }
        };

        public abstract boolean validateValue(String value);

        public abstract Float normalizeRating5(String value);

        public String getTypeName() {
            return this.toString();
        }

        public static Type getTypeByName(String typeName) {
            return valueOf(typeName);
        }
    }

    /**
     * @return value of the tag. Null signals for off-design state.
     */
    public abstract String getValue();

    /**
     * Name (maybe unique identity) of tag.
     *
     * @return name of the tag. Null signals for off-design state.
     */
    public abstract String getName();

    /**
     * @return type of the tag. Null signals for off-design state.
     */
    public abstract Type getType();

    /**
     * Caption of the tag while it will be rendered.
     * Optional element.
     * Language-specific element.
     *
     * @return caption of the tag
     *         or null if it's inaccessible.
     */
    public abstract String getCaption();

    @Override
    public boolean equals(final Object o) {
        if (this == o) return true;
        if (!(o instanceof Tag)) return false;

        final Tag tag = (Tag) o;

        return hashCode() == tag.hashCode()
                && ModelUtils.equals(getName(), tag.getName())
                && ModelUtils.equals(getValue(), tag.getValue())
                && ModelUtils.equals(getType(), tag.getType())
                && ModelUtils.equals(getCaption(), tag.getCaption());
    }

    @Override
    public int hashCode() {
        return ModelUtils.hashCode(
                getName()
                ,getValue()
                ,getType()
                ,getCaption()
        );
    }

    @Override
    public String toString() {
        try {
            return TagJsonConversions.toJson(this).toString(1);
        } catch (JSONException e) {
            throw new AssertionError("Not expected to be thrown");
        }
    }
}
