/*
 * 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 org.apache.gobblin.config;

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigValueFactory;


/** A helper class to create {@link Config} objects */
public class ConfigBuilder {
  private final Map<String, Object> primitiveProps = new HashMap<>();
  private final Optional<String> originDestription;
  private Config currentConfig;

  ConfigBuilder(Optional<String> originDescription) {
    this.originDestription = originDescription;
    this.currentConfig =
        originDescription.isPresent() ? ConfigFactory.empty(this.originDestription.get()) : ConfigFactory.empty();
  }

  /**
   * Loads properties which have a given name prefix into the config. The following restrictions
   * apply:
   * <ul>
   *    <li>No property can have a name that is equal to the prefix
   *    <li>After removal of the prefix, the remaining property name should start with a letter.
   * </ul>
   *
   * @param  props          the collection from where to load the properties
   * @param  scopePrefix    only properties with this prefix will be considered. The prefix will be
   *                        removed from the names of the keys added to the {@link Config} object.
   *                        The prefix can be an empty string but cannot be null.
   */
  public ConfigBuilder loadProps(Properties props, String scopePrefix) {
    Preconditions.checkNotNull(props);
    Preconditions.checkNotNull(scopePrefix);

    int scopePrefixLen = scopePrefix.length();

    for (Map.Entry<Object, Object> propEntry : props.entrySet()) {
      String propName = propEntry.getKey().toString();
      if (propName.startsWith(scopePrefix)) {
        String scopedName = propName.substring(scopePrefixLen);
        if (scopedName.isEmpty()) {
          throw new RuntimeException("Illegal scoped property:" + propName);
        }
        if (!Character.isAlphabetic(scopedName.charAt(0))) {
          throw new RuntimeException(
              "Scoped name for property " + propName + " should start with a character: " + scopedName);
        }
        this.primitiveProps.put(scopedName, propEntry.getValue());
      }
    }

    return this;
  }

  public ConfigBuilder addPrimitive(String name, Object value) {
    this.primitiveProps.put(name, value);
    return this;
  }

  public ConfigBuilder addList(String name, Iterable<? extends Object> values) {
    this.currentConfig = this.originDestription.isPresent()
        ? this.currentConfig.withValue(name, ConfigValueFactory.fromIterable(values, this.originDestription.get()))
        : this.currentConfig.withValue(name, ConfigValueFactory.fromIterable(values));
    return this;
  }

  public static ConfigBuilder create() {
    return new ConfigBuilder(Optional.<String> absent());
  }

  public static ConfigBuilder create(String originDescription) {
    return new ConfigBuilder(Optional.of(originDescription));
  }

  public Config build() {
    return ConfigFactory.parseMap(this.primitiveProps).withFallback(this.currentConfig);
  }

}
