/*
 * Copyright 2016, 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.server.validate;

import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Message;

import java.util.List;

import static java.lang.String.format;

/**
 * Validates messages according to Spine custom protobuf options and provides validation error messages.
 *
 * @author Alexander Litus
 */
public class MessageValidator {

    private boolean isMessageInvalid = false;

    private boolean isValidated = false;

    private String errorMessage = "";

    /**
     * Validates messages according to Spine custom protobuf options and sets validation error messages.
     *
     * @param message a message to validate
     */
    public void validate(Message message) {
        // TODO:2016-03-11:alexander.litus: check 1st field in commands
        final StringBuilder errMsgBuilder = newStringBuilder();
        final Descriptor msgDescriptor = message.getDescriptorForType();
        final List<FieldDescriptor> fields = msgDescriptor.getFields();
        for (FieldDescriptor field : fields) {
            final Object value = message.getField(field);
            final FieldValidator<?> validator = FieldValidator.newInstance(field, value);
            if (validator.isFieldInvalid()) {
                isMessageInvalid = true;
                final List<String> errorMessages = validator.getErrorMessages();
                appendErrorMessages(errMsgBuilder, errorMessages);
            }
        }
        if (isMessageInvalid) {
            errorMessage = buildErrorMessage(errMsgBuilder, msgDescriptor);
        }
        isValidated = true;
    }

    /**
     * Returns false if the validated {@link Message} is invalid.
     * {@link #validate(Message)} method must be called first.
     */
    public boolean isMessageInvalid() {
        checkValidated();
        return isMessageInvalid;
    }

    /**
     * Returns a validation error message constructed from error messages for different fields.
     */
    public String getErrorMessage() {
        checkValidated();
        return errorMessage;
    }

    private static void appendErrorMessages(StringBuilder builder, List<String> errorMessages) {
        for (String msg : errorMessages) {
            builder.append(msg)
                   .append("; ");
        }
    }

    private static String buildErrorMessage(StringBuilder errMsgBuilder, Descriptor msgDescriptor) {
        errMsgBuilder.insert(0, format("Message %s is invalid: ", msgDescriptor.getFullName()));
        // replace two last chars "; " with the dot
        errMsgBuilder.replace(errMsgBuilder.length() - 2, errMsgBuilder.length(), ".");
        return errMsgBuilder.toString();
    }

    private void checkValidated() {
        if (!isValidated) {
            throw new IllegalStateException("'validate()' method was not called.");
        }
    }

    private static StringBuilder newStringBuilder() {
        final int capacity = 256;
        return new StringBuilder(capacity);
    }
}

@@ -1215,16 +1215,77 @@
 .List;%0A%0A
+import static com.google.common.collect.Lists.newLinkedList;%0A
 import s
@@ -1926,54 +1926,50 @@
 nal 
+List%3C
 String
-Builder errMsgBuilder = newStringBuilder
+%3E errorMessages = newLinkedList
 ();%0A
@@ -2422,38 +2422,33 @@
 al List%3CString%3E 
-errorM
+m
 essages = valida
@@ -2487,23 +2487,17 @@
         
-appendE
+e
 rrorMess
@@ -2504,30 +2504,17 @@
 ages
-(errMsgBuilder, errorM
+.addAll(m
 essa
@@ -2623,26 +2623,26 @@
 sage(err
-MsgBuilder
+orMessages
 , msgDes
@@ -3182,18 +3182,19 @@
 tic 
-void appen
+String buil
 dErr
@@ -3206,33 +3206,9 @@
 sage
-s(StringBuilder builder, 
+(
 List
@@ -3220,511 +3220,627 @@
 ng%3E 
-errorM
+m
 essages
-) %7B%0A        for (String msg : errorMessages) %7B%0A            builder.append(msg)%0A                   .append(%22; %22);%0A        %7D%0A    %7D%0A%0A    private static String buildErrorMessage(StringBuilder errMsgBuilder, Descriptor msgDescriptor) %7B%0A        errMsgBuilder.insert(0, format(%22Message %25s is invalid: %22, msgDescriptor.getFullName()));%0A        // replace two last chars %22; %22 with the dot%0A        errMsgBuilder.replace(errMsgBuilder.length() - 2, errMsgBuilder.length(), %22.%22);%0A        return errMsgB
+, Descriptor msgDescriptor) %7B%0A        final int averageMsgLength = 32;%0A        final StringBuilder builder = new StringBuilder(messages.size() * averageMsgLength);%0A        builder.append(format(%22Message %25s is invalid: %22, msgDescriptor.getFullName()));%0A        for (int i = 0; i %3C messages.size() ; i++) %7B%0A            final String msg = messages.get(i);%0A            builder.append(msg);%0A            final boolean isLast = i == (messages.size() - 1);%0A            if (isLast) %7B%0A                builder.append('.');%0A            %7D else %7B%0A                builder.append(%22; %22);%0A            %7D%0A        %7D%0A        return b
 uild
@@ -4028,146 +4028,7 @@
    %7D
-%0A%0A    private static StringBuilder newStringBuilder() %7B%0A        final int capacity = 256;%0A        return new StringBuilder(capacity);%0A    %7D
 %0A%7D%0A

Improve the way the validation error message is build.