package com.ctrip.framework.apollo.util;

import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;

/**
 * An OrderedProperties instance will keep appearance order in config file.
 *
 * <strong>
 * Warnings: 1. It should be noticed that stream APIs or JDk1.8 APIs( listed in
 * https://github.com/ctripcorp/apollo/pull/2861) are not implemented here. 2. {@link Properties}
 * implementation are different between JDK1.8 and later JDKs. At least, {@link Properties} have an
 * individual implementation in JDK10. Hence, there should be an individual putAll method here.
 * </strong>
 *
 * @author songdragon@zts.io
 */
public class OrderedProperties extends Properties {

  private static final long serialVersionUID = -1741073539526213291L;
  private final Set<String> propertyNames;

  public OrderedProperties() {
    propertyNames = Collections.synchronizedSet(new LinkedHashSet<String>());
  }

  @Override
  public synchronized Object put(Object key, Object value) {
    addPropertyName(key);
    return super.put(key, value);
  }

  private void addPropertyName(Object key) {
    if (key instanceof String) {
      propertyNames.add((String) key);
    }
  }

  @Override
  public Set<String> stringPropertyNames() {
    return propertyNames;
  }

  @Override
  public Enumeration<?> propertyNames() {
    return Collections.enumeration(propertyNames);
  }

  @Override
  public synchronized Enumeration<Object> keys() {
    return new Enumeration<Object>() {
      private final Iterator<String> i = propertyNames.iterator();

      @Override
      public boolean hasMoreElements() {
        return i.hasNext();
      }

      @Override
      public Object nextElement() {
        return i.next();
      }
    };
  }

  @Override
  public Set<Object> keySet() {
    return new LinkedHashSet<Object>(propertyNames);
  }


  @Override
  public Set<Entry<Object, Object>> entrySet() {
    Set<Entry<Object, Object>> original = super.entrySet();
    LinkedHashMap<Object, Entry<Object, Object>> entryMap = new LinkedHashMap<>();
    for (String propertyName : propertyNames) {
      entryMap.put(propertyName, null);
    }

    for (Entry<Object, Object> entry : original) {
      entryMap.put(entry.getKey(), entry);
    }

    return new LinkedHashSet<>(entryMap.values());
  }

  @Override
  public synchronized void putAll(Map<?, ?> t) {
    super.putAll(t);
    for (Object name : t.keySet()) {
      addPropertyName(name);
    }
  }

  @Override
  public synchronized void clear() {
    super.clear();
    this.propertyNames.clear();
  }

  @Override
  public synchronized Object remove(Object key) {
    if (key instanceof String) {
      this.propertyNames.remove(key);
    }
    return super.remove(key);
  }

}
