/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.bval.jsr.job;

import java.util.HashSet;
import java.util.Set;

import javax.validation.ClockProvider;
import javax.validation.ConstraintValidatorContext;
import javax.validation.ConstraintViolation;
import javax.validation.MessageInterpolator;
import javax.validation.ValidationException;
import javax.validation.metadata.ConstraintDescriptor;
import javax.validation.metadata.CrossParameterDescriptor;

import org.apache.bval.jsr.descriptor.ComposedD;
import org.apache.bval.jsr.descriptor.ConstraintD;
import org.apache.bval.jsr.descriptor.CrossParameterD;
import org.apache.bval.jsr.util.ContainerElementNodeBuilderCustomizableContextImpl;
import org.apache.bval.jsr.util.LeafNodeBuilderCustomizableContextImpl;
import org.apache.bval.jsr.util.NodeBuilderCustomizableContextImpl;
import org.apache.bval.jsr.util.NodeBuilderDefinedContextImpl;
import org.apache.bval.jsr.util.NodeImpl;
import org.apache.bval.jsr.util.PathImpl;
import org.apache.bval.util.Exceptions;
import org.apache.bval.util.Lazy;
import org.apache.bval.util.Validate;

public class ConstraintValidatorContextImpl<T> implements ConstraintValidatorContext, MessageInterpolator.Context {
    private class ConstraintViolationBuilderImpl implements ConstraintValidatorContext.ConstraintViolationBuilder {
        private final String template;
        private final PathImpl path;

        ConstraintViolationBuilderImpl(String template, PathImpl path) {
            this.template = template;
            this.path = path;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public NodeBuilderDefinedContext addNode(String name) {
            PathImpl p;
            if (path.isRootPath()) {
                p = PathImpl.create();
                p.getLeafNode().setName(name);
            } else {
                p = PathImpl.copy(path);
                p.addNode(new NodeImpl.PropertyNodeImpl(name));
            }
            return new NodeBuilderDefinedContextImpl(ConstraintValidatorContextImpl.this, template, p);
        }

        @Override
        public NodeBuilderCustomizableContext addPropertyNode(String name) {
            return new NodeBuilderCustomizableContextImpl(ConstraintValidatorContextImpl.this, template, path, name);
        }

        @Override
        public LeafNodeBuilderCustomizableContext addBeanNode() {
            return new LeafNodeBuilderCustomizableContextImpl(ConstraintValidatorContextImpl.this, template, path);
        }

        @Override
        public NodeBuilderDefinedContext addParameterNode(int index) {
            Exceptions.raiseUnless(frame.descriptor instanceof CrossParameterDescriptor, ValidationException::new,
                "Cannot add parameter node for %s", f -> f.args(frame.descriptor.getClass().getName()));

            final CrossParameterD<?, ?> crossParameter =
                ComposedD.unwrap(frame.descriptor, CrossParameterD.class).findFirst().get();

            final String parameterName = crossParameter.getParent().getParameterDescriptors().get(index).getName();

            final NodeImpl node = new NodeImpl.ParameterNodeImpl(parameterName, index);
            if (!path.isRootPath()) {
                path.removeLeafNode();
            }
            path.addNode(node);
            return new NodeBuilderDefinedContextImpl(ConstraintValidatorContextImpl.this, template, path);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public ConstraintValidatorContext addConstraintViolation() {
            addError(template, path);
            return ConstraintValidatorContextImpl.this;
        }

        @Override
        public ContainerElementNodeBuilderCustomizableContext addContainerElementNode(String name,
            Class<?> containerType, Integer typeArgumentIndex) {
            return new ContainerElementNodeBuilderCustomizableContextImpl(ConstraintValidatorContextImpl.this, template,
                path, name, containerType, typeArgumentIndex);
        }
    }

    private final ValidationJob<T>.Frame<?> frame;
    private final ConstraintD<?> constraint;
    private final Lazy<Set<ConstraintViolation<T>>> violations = new Lazy<>(HashSet::new);
    private boolean defaultConstraintViolationDisabled;

    /**
     * Temporary for code migration
     */
    // TODO delete
    @Deprecated
    protected ConstraintValidatorContextImpl() {
        this.frame = null;
        this.constraint = null;
    }

    ConstraintValidatorContextImpl(ValidationJob<T>.Frame<?> frame, ConstraintD<?> constraint) {
        super();
        this.frame = Validate.notNull(frame, "frame");
        this.constraint = Validate.notNull(constraint, "constraint");
    }

    @Override
    public void disableDefaultConstraintViolation() {
        this.defaultConstraintViolationDisabled = true;
    }

    @Override
    public String getDefaultConstraintMessageTemplate() {
        return constraint.getMessageTemplate();
    }

    @Override
    public ConstraintViolationBuilder buildConstraintViolationWithTemplate(String messageTemplate) {
        return new ConstraintViolationBuilderImpl(messageTemplate, frame.context.getPath());
    }

    @Override
    public ClockProvider getClockProvider() {
        return frame.getJob().validatorContext.getClockProvider();
    }

    @Override
    public <U> U unwrap(Class<U> type) {
        try {
            return type.cast(this);
        } catch (ClassCastException e) {
            throw new ValidationException(e);
        }
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    public void addError(String messageTemplate, PathImpl propertyPath) {
        violations.get().add(((ValidationJob) frame.getJob()).createViolation(messageTemplate, this, propertyPath));
    }

    ValidationJob<T>.Frame<?> getFrame() {
        return frame;
    }

    Set<ConstraintViolation<T>> getRequiredViolations() {
        if (!violations.optional().isPresent()) {
            if (defaultConstraintViolationDisabled) {
                Exceptions.raise(ValidationException::new, "Expected custom constraint violation(s)");
            }
            addError(getDefaultConstraintMessageTemplate(), frame.context.getPath());
        }
        return violations.get();
    }

    @Override
    public ConstraintDescriptor<?> getConstraintDescriptor() {
        return constraint;
    }

    @Override
    public Object getValidatedValue() {
        return frame.context.getValue();
    }
}

@@ -5070,210 +5070,8 @@
 d;%0A%0A
-    /**%0A     * Temporary for code migration%0A     */%0A    // TODO delete%0A    @Deprecated%0A    protected ConstraintValidatorContextImpl() %7B%0A        this.frame = null;%0A        this.constraint = null;%0A    %7D%0A%0A
     

remove unused code