/*
 * Copyright DataStax, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.datastax.driver.mapping;

import com.datastax.driver.mapping.annotations.*;

import java.lang.annotation.Annotation;
import java.util.*;

/**
 * Various checks on mapping annotations.
 */
class AnnotationChecks {

    // The package containing the mapping annotations
    private static final Package MAPPING_PACKAGE = Table.class.getPackage();

    /**
     * Checks that a class is decorated with the given annotation, and return the annotation instance.
     * Also validates that no other mapping annotation is present.
     */
    static <T extends Annotation> T getTypeAnnotation(Class<T> annotation, Class<?> annotatedClass) {
        T instance = annotatedClass.getAnnotation(annotation);
        if (instance == null)
            throw new IllegalArgumentException(String.format("@%s annotation was not found on %s",
                    annotation.getSimpleName(), annotatedClass));

        // Check that no other mapping annotations are present
        validateAnnotations(annotatedClass, annotation);

        return instance;
    }

    private static void validateAnnotations(Class<?> clazz, Class<? extends Annotation> allowed) {
        Collection<Annotation> classAnnotations = new HashSet<Annotation>();
        Collections.addAll(classAnnotations, clazz.getAnnotations());
        Class<? extends Annotation> invalid = validateAnnotations(classAnnotations, Collections.singleton(allowed));
        if (invalid != null)
            throw new IllegalArgumentException(String.format("Cannot have both @%s and @%s on %s",
                    allowed.getSimpleName(), invalid.getSimpleName(),
                    clazz));
    }

    /**
     * Checks that a field is only annotated with the given mapping annotations, and that its "frozen" annotations are valid.
     */
    static void validateAnnotations(String propertyName, Map<Class<? extends Annotation>, Annotation> annotations, Collection<? extends Class<? extends Annotation>> allowed) {
        Class<? extends Annotation> invalid = validateAnnotations(annotations.values(), allowed);
        if (invalid != null) {
            throw new IllegalArgumentException(String.format("Annotation @%s is not allowed on property '%s'",
                    invalid.getSimpleName(),
                    propertyName));
        }
        checkValidPrimaryKey(propertyName, annotations);
        checkValidComputed(propertyName, annotations);
    }

    // Returns the offending annotation if there is one
    private static Class<? extends Annotation> validateAnnotations(Collection<Annotation> annotations, Collection<? extends Class<? extends Annotation>> allowed) {
        for (Annotation annotation : annotations) {
            Class<? extends Annotation> actual = annotation.annotationType();
            if (actual.getPackage().equals(MAPPING_PACKAGE) && !allowed.contains(actual))
                return actual;
        }
        return null;
    }

    private static void checkValidPrimaryKey(String propertyName, Map<Class<? extends Annotation>, Annotation> annotations) {
        if (annotations.containsKey(PartitionKey.class) && annotations.containsKey(ClusteringColumn.class))
            throw new IllegalArgumentException(String.format("Property '%s' cannot be annotated with both @PartitionKey and @ClusteringColumn", propertyName));
    }

    private static void checkValidComputed(String propertyName, Map<Class<? extends Annotation>, Annotation> annotations) {
        if (annotations.containsKey(Computed.class)) {
            Computed computed = (Computed) annotations.get(Computed.class);
            if (computed.value().isEmpty()) {
                throw new IllegalArgumentException(String.format("Property '%s': attribute 'value' of annotation @Computed is mandatory for computed properties", propertyName));
            }
            if (annotations.containsKey(Column.class)) {
                throw new IllegalArgumentException(String.format("Property '%s' cannot be annotated with both @Column and @Computed", propertyName));
            }
        }
    }

    static void validateOrder(List<AliasedMappedProperty> properties, String annotation) {
        for (int i = 0; i < properties.size(); i++) {
            AliasedMappedProperty property = properties.get(i);
            int pos = property.mappedProperty.getPosition();
            if (pos != i)
                throw new IllegalArgumentException(String.format("Invalid ordering value %d for annotation %s of property '%s', was expecting %d",
                        pos, annotation, property.mappedProperty.getPropertyName(), i));
        }
    }
}
