package ru.yandex.canvas.model.validation;

import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

import ru.yandex.canvas.model.CreativeData;
import ru.yandex.canvas.model.MediaSet;
import ru.yandex.canvas.model.MediaSetItem;
import ru.yandex.canvas.model.MediaSetSubItem;
import ru.yandex.canvas.model.elements.Element;
import ru.yandex.canvas.model.elements.Image;
import ru.yandex.canvas.model.elements.Logo;

import static java.util.stream.Collectors.toList;

/**
 * Helper superclass to validate image+mediaSet integrity and values.
 * Reports validation errors on the corresponding <b>image</b> element.
 *
 * @param <A> constraint annotation to be validated bu subclass
 * @author pupssman
 */
public abstract class ElementReportingMediaSetValidator<A extends Annotation> implements ConstraintValidator<A, CreativeData> {

    public ElementReportingMediaSetValidator() {
        super();
    }

    @Override
    public boolean isValid(CreativeData creativeData, ConstraintValidatorContext context) {
        if (creativeData.getMediaSets() == null) {
            return true;
        }

        boolean isValid = true;
        List<Element> elements = creativeData.getElements();
        for (int i = 0; i < elements.size(); i++) {
            Element element = elements.get(i);

            if (element.getAvailable() && getValidatedElementClasses().stream()
                    .anyMatch(clazz -> clazz.isInstance(element))) {
                Map<String, MediaSet> mediaSets = creativeData.getMediaSets();
                MediaSet mediaSet = (element.getMediaSet() == null || mediaSets.get(element.getMediaSet()) == null)
                        ? null : mediaSets.get(element.getMediaSet());

                if (!isValidElementWithMediaSet(element, mediaSet, mediaSets)) {
                    isValid = false;
                    context.disableDefaultConstraintViolation();
                    context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
                            .addPropertyNode("elements[" + i + "]")
                            .addConstraintViolation();
                }
            }
        }

        return isValid;
    }

    /**
     * @return classes of elements this validator applies to. Override in child classes to change defaults.
     */
    protected Collection<Class<? extends Element>> getValidatedElementClasses() {
        return Arrays.asList(Image.class, Logo.class);
    }

    /**
     * @param element   the element to validate
     * @param mediaSet  bound mediaSet, if any (may be null)
     * @param mediaSets
     * @return if the constraint is satisfied
     */
    protected abstract boolean isValidElementWithMediaSet(Element element, MediaSet mediaSet,
                                                          Map<String, MediaSet> mediaSets);

    protected List<String> getFileIds(MediaSet mediaSet) {
        return getSubItems(mediaSet).stream()
                .map(MediaSetSubItem::getEffectiveFileId)
                .filter(Objects::nonNull).collect(toList());
    }

    protected List<MediaSetSubItem> getSubItems(MediaSet mediaSet) {
        return mediaSet.getItems().stream()
                .map(MediaSetItem::getItems)
                .flatMap(Collection::stream).collect(toList());
    }
}
