/*
 * Licensed to Crate under one or more contributor license agreements.
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.  Crate 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.
 *
 * However, if you have executed another commercial license agreement
 * with Crate these terms will supersede the license and you may use the
 * software solely pursuant to the terms of the relevant commercial
 * agreement.
 */

package io.crate.metadata.settings.session;

import com.google.common.collect.ImmutableMap;
import io.crate.action.sql.SessionContext;
import io.crate.metadata.SearchPath;
import io.crate.protocols.postgres.PostgresWireProtocol;
import io.crate.types.DataTypes;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.Singleton;

import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

import static io.crate.metadata.SearchPath.createSearchPathFrom;

@Singleton
public class SessionSettingRegistry {

    private static final String SEARCH_PATH_KEY = "search_path";
    public static final String HASH_JOIN_KEY = "enable_hashjoin";
    static final String MAX_INDEX_KEYS = "max_index_keys";
    private static final String SERVER_VERSION_NUM = "server_version_num";
    private static final String SERVER_VERSION = "server_version";
    private final Map<String, SessionSetting<?>> settings;

    @Inject
    public SessionSettingRegistry(Set<SessionSettingProvider> sessionSettingProviders) {
        var builder = ImmutableMap.<String, SessionSetting<?>>builder()
            .put(SEARCH_PATH_KEY,
                 new SessionSetting<>(
                     SEARCH_PATH_KEY,
                     objects -> {}, // everything allowed, empty list (resulting by ``SET .. TO DEFAULT`` results in defaults
                     objects -> createSearchPathFrom(objectsToStringArray(objects)),
                     SessionContext::setSearchPath,
                     s -> iterableToString(s.searchPath()),
                     () -> iterableToString(SearchPath.pathWithPGCatalogAndDoc()),
                     "Sets the schema search order.",
                     DataTypes.STRING.getName()))
            .put(HASH_JOIN_KEY,
                 new SessionSetting<>(
                     HASH_JOIN_KEY,
                     objects -> {
                         if (objects.length != 1) {
                             throw new IllegalArgumentException(HASH_JOIN_KEY + " should have only one argument.");
                         }
                     },
                     objects -> DataTypes.BOOLEAN.value(objects[0]),
                     SessionContext::setHashJoinEnabled,
                     s -> Boolean.toString(s.hashJoinsEnabled()),
                     () -> String.valueOf(true),
                     "Considers using the Hash Join instead of the Nested Loop Join implementation.",
                     DataTypes.BOOLEAN.getName()))
            .put(MAX_INDEX_KEYS,
                 new SessionSetting<>(
                     MAX_INDEX_KEYS,
                     objects -> {},
                     Function.identity(),
                     (s, v) -> {
                         throw new UnsupportedOperationException("\"" + MAX_INDEX_KEYS + "\" cannot be changed.");
                     },
                     s -> String.valueOf(32),
                     () -> String.valueOf(32),
                     "Shows the maximum number of index keys.",
                     DataTypes.INTEGER.getName()))
            .put(SERVER_VERSION_NUM,
                 new SessionSetting<>(
                     SERVER_VERSION_NUM,
                     objects -> {},
                     Function.identity(),
                     (s, v) -> {
                         throw new UnsupportedOperationException("\"" + SERVER_VERSION_NUM + "\" cannot be changed.");
                     },
                     s -> String.valueOf(PostgresWireProtocol.SERVER_VERSION_NUM),
                     () -> String.valueOf(PostgresWireProtocol.SERVER_VERSION_NUM),
                     "Reports the emulated PostgreSQL version number",
                     DataTypes.INTEGER.getName()
                 )
            )
            .put(SERVER_VERSION,
                 new SessionSetting<>(
                     SERVER_VERSION,
                     objects -> {},
                     Function.identity(),
                     (s, v) -> {
                         throw new UnsupportedOperationException("\"" + SERVER_VERSION + "\" cannot be changed.");
                     },
                     s -> String.valueOf(PostgresWireProtocol.PG_SERVER_VERSION),
                     () -> String.valueOf(PostgresWireProtocol.PG_SERVER_VERSION),
                     "Reports the emulated PostgreSQL version number",
                     DataTypes.STRING.getName()
                 )
            );
        for (var providers : sessionSettingProviders) {
            for (var setting : providers.sessionSettings()) {
                builder.put(setting.name(), setting);
            }
        }
        this.settings = builder.build();
    }

    public Map<String, SessionSetting<?>> settings() {
        return settings;
    }

    private static String[] objectsToStringArray(Object[] objects) {
        String[] strings = new String[objects.length];
        for (int i = 0; i < objects.length; i++) {
            strings[i] = DataTypes.STRING.value(objects[i]);
        }
        return strings;
    }

    private static String iterableToString(Iterable<String> iterable) {
        Iterator<String> it = iterable.iterator();
        StringBuilder sb = new StringBuilder();
        while (it.hasNext()) {
            sb.append(it.next());
            if (it.hasNext()) {
                sb.append(", ");
            }
        }
        return sb.toString();
    }
}

@@ -1048,438 +1048,438 @@
 ort 
-com.google.common.collect.ImmutableMap;%0Aimport io.crate.action.sql.SessionContext;%0Aimport io.crate.metadata.SearchPath;%0Aimport io.crate.protocols.postgres.PostgresWireProtocol;%0Aimport io.crate.types.DataTypes;%0Aimport org.elasticsearch.common.inject.Inject;%0Aimport org.elasticsearch.common.inject.Singleton;%0A%0Aimport java.util.Iterator;%0Aimport java.util.Map;%0Aimport java.util.Set;%0Aimport java.util.function.Function
+static io.crate.metadata.SearchPath.createSearchPathFrom;%0A%0Aimport java.util.Map;%0Aimport java.util.Set;%0Aimport java.util.function.Function;%0A%0Aimport com.google.common.collect.ImmutableMap;%0A%0Aimport org.elasticsearch.common.inject.Inject;%0Aimport org.elasticsearch.common.inject.Singleton;%0A%0Aimport io.crate.action.sql.SessionContext;%0Aimport io.crate.metadata.SearchPath;%0Aimport io.crate.protocols.postgres.PostgresWireProtocol
 ;%0A
-%0A
 import 
-static 
 io.c
@@ -1487,48 +1487,23 @@
 ate.
-metadata.SearchPath.createSearchPathFrom
+types.DataTypes
 ;%0A%0A@
@@ -2516,33 +2516,34 @@
    s -%3E 
-iterableToString(
+String.join(%22, %22, 
 s.search
@@ -2578,33 +2578,34 @@
   () -%3E 
-iterableToString(
+String.join(%22, %22, 
 SearchPa
@@ -6038,369 +6038,7 @@
    %7D
-%0A%0A    private static String iterableToString(Iterable%3CString%3E iterable) %7B%0A        Iterator%3CString%3E it = iterable.iterator();%0A        StringBuilder sb = new StringBuilder();%0A        while (it.hasNext()) %7B%0A            sb.append(it.next());%0A            if (it.hasNext()) %7B%0A                sb.append(%22, %22);%0A            %7D%0A        %7D%0A        return sb.toString();%0A    %7D
 %0A%7D%0A

Replace iterableToString with String.join