/*
 * 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.server.event.enrich;

import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import org.spine3.protobuf.KnownTypes;
import org.spine3.protobuf.TypeUrl;

import java.util.Collection;
import java.util.Properties;
import java.util.Set;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.spine3.io.IoUtil.loadAllProperties;

/**
 * A map from an event enrichment Protobuf type name to the corresponding type name(s) of event(s) to enrich.
 *
 * <p>Example:
 * <p>{@code proto.type.MyEventEnrichment} - {@code proto.type.FirstEvent},{@code proto.type.SecondEvent}
 *
 * @author Alexander Litus
 */
class EventEnrichmentsMap {

    /**
     * A path to the file which contains enrichment and event Protobuf type names.
     * Is generated by Gradle during the build process.
     */
    private static final String PROPS_FILE_PATH = "enrichments.properties";

    /** A separator between event types in the `.properties` file. */
    private static final String EVENT_TYPE_SEPARATOR = ",";

    private static final ImmutableMultimap<String, String> enrichmentsMap = buildEnrichmentsMap();

    private EventEnrichmentsMap() {
    }

    /** Returns the immutable map instance. */
    static ImmutableMultimap<String, String> getInstance() {
        return enrichmentsMap;
    }

    private static ImmutableMultimap<String, String> buildEnrichmentsMap() {
        final ImmutableSet<Properties> propertiesSet = loadAllProperties(PROPS_FILE_PATH);
        final Builder builder = new Builder(propertiesSet);
        final ImmutableMultimap<String, String> result = builder.build();
        return result;
    }

    private static class Builder {

        private static final String PACKAGE_WILDCARD_INDICATOR = ".*";

        private final Iterable<Properties> properties;
        private final ImmutableMultimap.Builder<String, String> builder;

        Builder(Iterable<Properties> properties) {
            this.properties = properties;
            this.builder = ImmutableMultimap.builder();
        }

        ImmutableMultimap<String, String> build() {
            for (Properties props : this.properties) {
                put(props);
            }
            return builder.build();
        }

        private void put(Properties props) {
            final Set<String> enrichmentTypes = props.stringPropertyNames();
            for (String enrichmentType : enrichmentTypes) {
                final String eventTypesStr = props.getProperty(enrichmentType);
                final Iterable<String> eventTypes = FluentIterable.from(eventTypesStr.split(EVENT_TYPE_SEPARATOR));
                put(enrichmentType, eventTypes);
            }
        }

        private void put(String enrichmentType, Iterable<String> eventTypes) {
            for (String eventType : eventTypes) {
                if (isPackage(eventType)) {
                    putAll(enrichmentType, eventType);
                } else {
                    builder.put(enrichmentType, eventType);
                }
            }
        }

        private void putAll(String enrichmentType, String eventsPackage) {
            final int lastSignificantCharPos = eventsPackage.length() - PACKAGE_WILDCARD_INDICATOR.length();
            final String packageName = eventsPackage.substring(0, lastSignificantCharPos);
            final Collection<TypeUrl> eventTypes = KnownTypes.getTypesFromPackage(packageName);
            for (TypeUrl type : eventTypes) {
                final String typeQualifier = type.getTypeName();
                builder.put(enrichmentType, typeQualifier);
            }
        }

        private static boolean isPackage(String qualifier) {
            checkNotNull(qualifier);
            checkArgument(!qualifier.isEmpty());

            final int indexOfWildcardChar = qualifier.indexOf(PACKAGE_WILDCARD_INDICATOR);
            final int qualifierLength = qualifier.length();

            final boolean result = indexOfWildcardChar == (qualifierLength - PACKAGE_WILDCARD_INDICATOR.length());
            return result;
        }
    }
}

@@ -1810,16 +1810,44 @@
 r Litus%0A
+ * @author Dmytro Dashenkov%0A
  */%0Aclas
@@ -2887,24 +2887,100 @@
  Builder %7B%0A%0A
+        /**%0A         * Constant indicating a package qualifier.%0A         */%0A
         priv
@@ -3830,21 +3830,25 @@
 g%3E event
-Types
+Qualifier
  = Fluen
@@ -3947,21 +3947,25 @@
 e, event
-Types
+Qualifier
 );%0A     
@@ -4050,28 +4050,33 @@
 tring%3E event
-Type
+Qualifier
 s) %7B%0A       
@@ -4101,24 +4101,34 @@
 vent
-Type : eventType
+Qualifier : eventQualifier
 s) %7B
@@ -4163,20 +4163,25 @@
 ge(event
-Type
+Qualifier
 )) %7B%0A   
@@ -4203,16 +4203,32 @@
   putAll
+TypesFromPackage
 (enrichm
@@ -4233,36 +4233,41 @@
 hmentType, event
-Type
+Qualifier
 );%0A             
@@ -4331,20 +4331,25 @@
 e, event
-Type
+Qualifier
 );%0A     
@@ -4378,32 +4378,363 @@
    %7D%0A        %7D%0A%0A
+        /**%0A         * Puts all the events from the given package into the map to match the given enrichment type.%0A         *%0A         * @param enrichmentType type of the enrichment for the given events%0A         * @param eventsPackage  package qualifier representing the protobuf package containing the event to enrich%0A         */%0A
         private 
@@ -4744,16 +4744,32 @@
 d putAll
+TypesFromPackage
 (String 
@@ -5292,32 +5292,205 @@
    %7D%0A        %7D%0A%0A
+        /**%0A         * @return %7B@code true%7D if the given qualifier is a package according to the contract%0A         * of %7B@code %22enrichment_for%22) option notation%0A         */%0A
         private 

Refactor and add documentation to some methods in `EventEnrichmentsMap.Builder`.