/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF 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.
 */
package com.datastax.oss.driver.internal.core.config.map;

import static com.typesafe.config.ConfigFactory.defaultReference;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;

import com.datastax.oss.driver.api.core.config.DefaultDriverOption;
import com.datastax.oss.driver.api.core.config.DriverConfig;
import com.datastax.oss.driver.api.core.config.DriverConfigLoader;
import com.datastax.oss.driver.api.core.config.DriverExecutionProfile;
import com.datastax.oss.driver.api.core.config.DriverOption;
import com.datastax.oss.driver.api.core.config.OptionsMap;
import com.datastax.oss.driver.api.core.config.TypedDriverOption;
import com.datastax.oss.driver.api.core.type.reflect.GenericType;
import com.datastax.oss.driver.internal.core.config.MockOptions;
import com.datastax.oss.driver.internal.core.config.MockTypedOptions;
import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigFactory;
import java.util.Optional;
import org.junit.Test;

public class MapBasedDriverConfigLoaderTest {

  @Test
  public void should_reflect_changes_in_real_time() {
    OptionsMap source = new OptionsMap();
    source.put(MockTypedOptions.INT1, 1);

    DriverConfigLoader loader = DriverConfigLoader.fromMap(source);
    DriverConfig config = loader.getInitialConfig();
    assertThat(config.getDefaultProfile().getInt(MockOptions.INT1)).isEqualTo(1);

    source.put(MockTypedOptions.INT1, 2);
    assertThat(config.getDefaultProfile().getInt(MockOptions.INT1)).isEqualTo(2);
  }

  /**
   * Checks that, if we ask to pre-fill the default profile, then we get the same set of options as
   * the built-in reference.conf.
   */
  @Test
  public void should_fill_default_profile_like_reference_file() {
    OptionsMap optionsMap = OptionsMap.driverDefaults();
    DriverExecutionProfile mapBasedConfig =
        DriverConfigLoader.fromMap(optionsMap).getInitialConfig().getDefaultProfile();
    DriverExecutionProfile fileBasedConfig =
        new DefaultDriverConfigLoader(
                () -> {
                  // Only load reference.conf since we are focusing on driver defaults
                  ConfigFactory.invalidateCaches();
                  return defaultReference().getConfig(DefaultDriverConfigLoader.DEFAULT_ROOT_PATH);
                })
            .getInitialConfig()
            .getDefaultProfile();

    // Make sure we're not missing any options. -1 is for CONFIG_RELOAD_INTERVAL, which is not
    // defined by OptionsMap because it is irrelevant for the map-based config.
    assertThat(mapBasedConfig.entrySet()).hasSize(fileBasedConfig.entrySet().size() - 1);

    for (TypedDriverOption<?> option : TypedDriverOption.builtInValues()) {
      if (option.getRawOption() == DefaultDriverOption.CONFIG_RELOAD_INTERVAL) {
        continue;
      }
      Optional<Object> fileBasedValue = get(fileBasedConfig, option);
      Optional<Object> mapBasedValue = get(mapBasedConfig, option);
      assertThat(mapBasedValue)
          .as("Wrong value for %s in OptionsMap", option.getRawOption())
          .isEqualTo(fileBasedValue);
    }
  }

  private Optional<Object> get(DriverExecutionProfile config, TypedDriverOption<?> typedOption) {
    DriverOption option = typedOption.getRawOption();
    GenericType<?> type = typedOption.getExpectedType();
    Object value = null;
    if (config.isDefined(option)) {
      // This is ugly, we have no other way than enumerating all possible types.
      // This kind of bridging code between OptionsMap and DriverConfig is unlikely to exist
      // anywhere outside of this test.
      if (type.equals(GenericType.listOf(String.class))) {
        value = config.getStringList(option);
      } else if (type.equals(GenericType.STRING)) {
        value = config.getString(option);
      } else if (type.equals(GenericType.DURATION)) {
        value = config.getDuration(option);
      } else if (type.equals(GenericType.INTEGER)) {
        value = config.getInt(option);
      } else if (type.equals(GenericType.BOOLEAN)) {
        value = config.getBoolean(option);
      } else if (type.equals(GenericType.LONG)) {
        try {
          value = config.getLong(option);
        } catch (ConfigException.WrongType e) {
          value = config.getBytes(option);
        }
      } else if (type.equals(GenericType.mapOf(GenericType.STRING, GenericType.STRING))) {
        value = config.getStringMap(option);
      } else {
        fail("Unexpected type " + type);
      }
    }
    return Optional.ofNullable(value);
  }
}
