/*
 * 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.protobuf;

import com.google.common.collect.ImmutableSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spine3.Internal;
import org.spine3.validate.internal.EntityType;

import javax.annotation.Nullable;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import static com.google.common.collect.Maps.newHashMap;
import static org.spine3.io.IoUtil.loadAllProperties;

/**
 * A map from Protobuf package to {@link EntityType} (the package contains files for this entity type (aggregate, etc)).
 *
 * <p>If there is a {@code state_of} option in the entity state Protobuf message definition,
 * all files in the current package are considered belonging to the specified type of the entity
 * (containing entity state, commands, events, etc).
 *
 * <p>This information is needed when validating a command to check if it is sent to an entity
 * (to check entity ID field in the command message).
 *
 * @author Alexander Litus
 */
@Internal
public class EntityPackagesMap {

    /**
     * A path to a file which contains Protobuf packages and appropriate {@link EntityType}s.
     * Is generated by Gradle during build process.
     */
    private static final String PROPS_FILE_PATH = "entities.properties";

    /**
     * A map from Protobuf package to {@link EntityType}.
     * The package contains files for this type of entity (aggregate, etc).
     *
     * <p>Example:
     * <p>{@code com.example.order} - {@code AGGREGATE}
     */
    private static final Map<String, EntityType> packagesMap = buildPackagesMap();

    private EntityPackagesMap() {}

    /**
     * Returns an entity type for the Protobuf package.
     *
     * @param protoPackage a protobuf package name where entity files are located
     * @return a type of the entity to which the package belongs
     */
    @Nullable
    public static EntityType get(String protoPackage) {
        final EntityType result = packagesMap.get(protoPackage);
        return result;
    }

    /**
     * Returns {@code true} if the map contains an entity type for a Protobuf package, {@code false} otherwise.
     *
     * @param protoPackage a protobuf package name where entity files are located
     * @return {@code true} if the package belongs to an entity
     */
    public static boolean contains(String protoPackage) {
        final EntityType result = get(protoPackage);
        final boolean contains = result != null;
        return contains;
    }

    private static Map<String, EntityType> buildPackagesMap() {
        final Map<String, EntityType> result = newHashMap();
        final ImmutableSet<Properties> propertiesSet = loadAllProperties(PROPS_FILE_PATH);
        for (Properties properties : propertiesSet) {
            putTo(result, properties);
        }
        if (log().isDebugEnabled()) {
            log().debug("Entry count in EntityPackagesMap: " + result.size());
        }
        return result;
    }

    private static void putTo(Map<String, EntityType> result, Properties properties) {
        final Set<String> protoPackages = properties.stringPropertyNames();
        for (String protoPackage : protoPackages) {
            final String entityTypeStr = properties.getProperty(protoPackage);
            final EntityType entityType = EntityType.valueOf(entityTypeStr);
            result.put(protoPackage, entityType);
        }
    }

    private enum LogSingleton {
        INSTANCE;
        @SuppressWarnings("NonSerializableFieldInSerializableClass")
        private final Logger value = LoggerFactory.getLogger(EntityPackagesMap.class);
    }

    private static Logger log() {
        return LogSingleton.INSTANCE.value;
    }
}

@@ -1082,19 +1082,19 @@
 mmutable
-Set
+Map
 ;%0Aimport
@@ -1098,56 +1098,46 @@
 ort 
-org.slf4j.Logger;%0Aimport org.slf4j.LoggerFactory
+com.google.common.collect.ImmutableSet
 ;%0Aim
@@ -1249,30 +1249,8 @@
 le;%0A
-import java.util.Map;%0A
 impo
@@ -1301,65 +1301,8 @@
 t;%0A%0A
-import static com.google.common.collect.Maps.newHashMap;%0A
 impo
@@ -2442,24 +2442,33 @@
 tatic final 
+Immutable
 Map%3CString, 
@@ -3433,16 +3433,25 @@
  static 
+Immutable
 Map%3CStri
@@ -3501,19 +3501,36 @@
   final 
-Map
+ImmutableMap.Builder
 %3CString,
@@ -3546,27 +3546,38 @@
 pe%3E 
-result = newHashMap
+builder = ImmutableMap.builder
 ();%0A
@@ -3739,22 +3739,23 @@
   putTo(
-result
+builder
 , proper
@@ -3783,148 +3783,30 @@
     
-if (log().isDebugEnabled()) %7B%0A            log().debug(%22Entry count in EntityPackagesMap: %22 + result.size());%0A        %7D%0A        return result
+return builder.build()
 ;%0A  
@@ -3840,19 +3840,36 @@
 d putTo(
-Map
+ImmutableMap.Builder
 %3CString,
@@ -3881,22 +3881,19 @@
 tyType%3E 
-result
+map
 , Proper
@@ -4207,22 +4207,19 @@
         
-result
+map
 .put(pro
@@ -4261,305 +4261,7 @@
    %7D
-%0A%0A    private enum LogSingleton %7B%0A        INSTANCE;%0A        @SuppressWarnings(%22NonSerializableFieldInSerializableClass%22)%0A        private final Logger value = LoggerFactory.getLogger(EntityPackagesMap.class);%0A    %7D%0A%0A    private static Logger log() %7B%0A        return LogSingleton.INSTANCE.value;%0A    %7D
 %0A%7D%0A

Make entity packages map immutable.