package ru.yandex.qe.dispenser.domain.property;

import java.util.Collection;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import com.google.common.collect.ImmutableList;
import org.jetbrains.annotations.Nullable;

import ru.yandex.qe.dispenser.api.v1.DiProperty;
import ru.yandex.qe.dispenser.domain.index.LongIndexable;

public class Property implements LongIndexable {

    private final String entityKey;
    private final String propertyKey;
    private final Value<?> value;
    private final long id;

    public Property(final long id,
                    final String entityKey,
                    final String propertyKey,
                    final Value<?> value) {
        this.id = id;
        this.entityKey = entityKey;
        this.propertyKey = propertyKey;
        this.value = value;
    }

    @Override
    public long getId() {
        return id;
    }

    public String getEntityKey() {
        return entityKey;
    }

    public String getPropertyKey() {
        return propertyKey;
    }

    public Value<?> getValue() {
        return value;
    }


    public DiProperty toView() {
        return new DiProperty.Builder()
                .entityKey(entityKey)
                .propertyKey(propertyKey)
                .type(value.getType().getView())
                .value(value.get())
                .build();
    }

    public static final class Value<T> {
        private final T value;
        private final Type<T> type;

        public Value(final Type<T> type, final T value) {
            this.type = type;
            this.value = value;
        }

        public T get() {
            return value;
        }

        public Type<T> getType() {
            return type;
        }

        @SuppressWarnings("unchecked")
        public <K> K getAs(final Type<K> type) {
            if (type == this.type) {
                return (K) value;
            }
            throw new IllegalArgumentException("Invalid type");
        }
    }

    public abstract static class Type<T> {
        private final String name;
        private final Class<T> typeClass;
        private final DiProperty.Type typeView;

        public static final Type<Boolean> BOOLEAN = new Type<Boolean>("BOOLEAN", Boolean.class, DiProperty.Type.BOOLEAN) {
        };

        public static final Type<String> STRING = new Type<String>("STRING", String.class, DiProperty.Type.STRING) {
        };

        private static final Collection<Type<?>> TYPES = ImmutableList.of(
                BOOLEAN,
                STRING
        );

        public static Collection<Type<?>> values() {
            return TYPES;
        }

        private static final Map<String, Type<?>> TYPES_BY_NAME;

        static {
            TYPES_BY_NAME = TYPES.stream()
                    .collect(Collectors.toMap(Type::getName, Function.identity()));
        }

        public static Type<?> valueOf(final String name) {
            final Type<?> type = TYPES_BY_NAME.get(name);
            if (type == null) {
                throw new IllegalArgumentException("Invalid type name");
            }
            return type;
        }

        protected Type(final String name, final Class<T> typeClass, final DiProperty.Type typeView) {
            this.name = name;
            this.typeClass = typeClass;
            this.typeView = typeView;
        }

        public String getName() {
            return name;
        }

        public DiProperty.Type getView() {
            return typeView;
        }

        public boolean isInstance(final Object object) {
            return typeClass.isInstance(object);
        }

        @SuppressWarnings("unchecked")
        @Nullable
        public Value<T> createValue(final Object object) {
            if (isInstance(object)) {
                return new Value<>(this, (T) object);
            }
            return null;
        }
    }

}
