/*
 * Copyright 2017, TeamDev Ltd. All rights reserved.
 *
 * Redistribution and use in source and/or binary forms, with or without
 * modification, must retain the above copyright notice and the following
 * disclaimer.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.spine3.validate;

import com.google.common.base.Optional;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Message;
import org.spine3.annotation.Internal;
import org.spine3.base.ConversionException;
import org.spine3.base.FieldPath;
import org.spine3.protobuf.Messages;
import org.spine3.string.Stringifiers;

import javax.annotation.Nullable;
import java.lang.reflect.Type;
import java.util.List;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Throwables.getRootCause;

/**
 * Serves as an abstract base for all {@linkplain ValidatingBuilder validating builders}.
 *
 * @author Illia Shepilov
 * @author Alex Tymchenko
 */
public abstract class AbstractValidatingBuilder<T extends Message, B extends Message.Builder>
                                                  implements ValidatingBuilder<T, B> {

    /**
     * The builder for the original {@code Message}.
     */
    private final B messageBuilder;

    /**
     * The class of the {@code Message} being built.
     */
    private final Class<T> messageClass;

    /**
     * The state of the message, serving as a base value for this {@code ValidatingBuilder}.
     *
     * <p>Used to verify if any modifications were made by a user via {@code ValidatingBuilder}
     * public API calls.
     *
     * <p>Has {@code null} value if not set via {@linkplain #setOriginalState(Message)
     * setOriginalState(..)}.
     */
    @Nullable
    private T originalState;

    private Modifications modifications;

    protected AbstractValidatingBuilder() {
        this.messageClass = TypeInfo.getMessageClass(getClass());
        this.messageBuilder = createBuilder();
    }

    @Override
    public T build() throws ConstraintViolationThrowable {
        final T message = internalBuild();
        validateResult(message);
        return message;
    }

    @Override
    public void clear() {
        messageBuilder.clear();
        originalState = null;
    }

    /**
     * Converts the passed `raw` value and returns it.
     *
     * @param <V>   the type of the converted value
     * @param value the value to convert
     * @param type  the key of the {@code StringifierRegistry} storage
     *              to obtain the {@code Stringifier}
     * @return the converted value
     * @throws ConversionException if passed value cannot be converted
     */
    public <V> V convert(String value, Type type) throws ConversionException {
        try {
            final V convertedValue = Stringifiers.fromString(value, type);
            return convertedValue;
        } catch (RuntimeException ex) {
            final Throwable rootCause = getRootCause(ex);
            throw new ConversionException(ex.getMessage(), rootCause);
        }
    }

    @Override
    public <V> void validate(FieldDescriptor descriptor, V fieldValue, String fieldName)
            throws ConstraintViolationThrowable {
        final FieldPath fieldPath = FieldPath.newBuilder()
                                             .addFieldName(fieldName)
                                             .build();
        final FieldValidator<?> validator =
                FieldValidatorFactory.create(descriptor, fieldValue, fieldPath);
        final List<ConstraintViolation> violations = validator.validate();
        onViolations(violations);
    }

    /**
     * Checks whether any modifications has been made to the fields of message being built.
     *
     * @return {@code true} if any modifications has been made, {@code false} otherwise.
     */
    @Override
    public boolean isDirty() {
        final T message = internalBuild();

        final boolean result = originalState != null
                ? originalState.equals(message)
                : Validate.isNotDefault(message);
        return result;
    }

    @Override
    public void setOriginalState(T state) {
        checkNotNull(state);
        this.originalState = state;

        messageBuilder.clear();
        messageBuilder.mergeFrom(state);
    }

    protected B getMessageBuilder() {
        return messageBuilder;
    }

    @Override
    public ValidatingBuilder<T, B> mergeFrom(T message) {
        messageBuilder.mergeFrom(message);
        return this;
    }

    /**
     * Builds a message without triggering its validation.
     *
     * <p>Exposed to those who wish to obtain the state anyway, e.g. for reporting.
     *
     * @return the message built from the values set by the user
     */
    @Internal
    public T internalBuild() {
        final B resultBuilder = createBuilder();
        if(getOriginalState().isPresent()) {
            resultBuilder.mergeFrom(getOriginalState().get());
        }
        resultBuilder.mergeFrom(getMessageBuilder().build());
        return (T) resultBuilder.build();
    }

    /**
     * Obtains the original message state, if it has been set via
     * {@linkplain #setOriginalState(Message) setOriginalState(..)}.
     *
     * @return original state as {@code Optional} value if it is set,
     *         or {@code Optional.absent()} otherwise
     */
    private Optional<T> getOriginalState() {
        return Optional.fromNullable(originalState);
    }

    private B createBuilder() {

        @SuppressWarnings("unchecked")  // OK, since it is guaranteed by the class declaration.
        final B result = (B) Messages.newInstance(messageClass)
                                     .newBuilderForType();
        return result;
    }

    private void validateResult(T message) throws ConstraintViolationThrowable {
        final List<ConstraintViolation> violations = MessageValidator.newInstance()
                                                                     .validate(message);
        onViolations(violations);
    }

    private static void onViolations(List<ConstraintViolation> violations)
            throws ConstraintViolationThrowable {
        if (!violations.isEmpty()) {
            throw new ConstraintViolationThrowable(violations);
        }
    }

    public Modifications getModifications() {
        return modifications;
    }

    public static class Modifications {

        private volatile int modificationCount = 0;

        private final Object lock = new Object();

        public void commitModification() {
            synchronized (lock) {
                modificationCount++;
            }
        }

        public int getAndClear() {
            synchronized (lock) {
                final int result = modificationCount;
                modificationCount = 0;
                return result;
            }
        }
    }
}

@@ -2544,50 +2544,8 @@
 e;%0A%0A
-    private Modifications modifications;%0A%0A
     
@@ -5410,14 +5410,12 @@
 for 
-report
+logg
 ing.
@@ -5769,24 +5769,25 @@
 ild());%0A
+%0A
         
 return (
@@ -5778,22 +5778,191 @@
         
-return
+@SuppressWarnings(%22unchecked%22)  // OK, as real types of %60B%60%0A                                        // are always generated to be compatible with %60T%60.%0A        final T result =
  (T) res
@@ -5977,24 +5977,47 @@
 er.build();%0A
+        return result;%0A
     %7D%0A%0A    /
@@ -7220,598 +7220,7 @@
    %7D
-%0A%0A    public Modifications getModifications() %7B%0A        return modifications;%0A    %7D%0A%0A    public static class Modifications %7B%0A%0A        private volatile int modificationCount = 0;%0A%0A        private final Object lock = new Object();%0A%0A        public void commitModification() %7B%0A            synchronized (lock) %7B%0A                modificationCount++;%0A            %7D%0A        %7D%0A%0A        public int getAndClear() %7B%0A            synchronized (lock) %7B%0A                final int result = modificationCount;%0A                modificationCount = 0;%0A                return result;%0A            %7D%0A        %7D%0A    %7D
 %0A%7D%0A

Remove unused functionality from `AbstractValidatingBuilder`.