package com.atlassian.bitbucket.property;

import com.atlassian.bitbucket.util.BuilderSupport;

import javax.annotation.Nonnull;
import java.util.Map;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * An implementation of {@link PropertySupport} for use in SCM entities, with write-once immutability provided by
 * {@link #setProperties(PropertyMap)}. Note: The recommended way to use this class is by composition.
 */
public class SimplePropertySupport implements PropertySupport {

    private transient PropertyMap properties;

    public SimplePropertySupport(@Nonnull AbstractPropertyBuilder<?> builder) {
        properties = builder.properties.build();
    }

    @Nonnull
    @Override
    public PropertyMap getProperties() {
        return properties;
    }

    /**
     * Set properties on this entity. Note: this class allows to set a {@link PropertyMap#isEmpty() non-empty} instance
     * of properties only <i>once</i> in its life time, either when constructing an instance, or by using this method.
     * Once a non-empty instance of properties is set, any subsequent attempts to call this method will result in a
     * runtime exception.
     * <p>
     * Clients can use {@link #getProperties()} to query the current state of the properties.
     *
     * @param properties the properties to set
     * @throws IllegalStateException if the properties has previously been set to a
     * {@link PropertyMap#isEmpty() non-empty} value
     */
    public void setProperties(@Nonnull PropertyMap properties) {
        checkNotNull(properties, "properties");

        if (this.properties != null && !this.properties.isEmpty()) {
            throw new IllegalStateException("Properties have already been set; you cannot override once set.");
        }
        this.properties = properties;
    }

    public abstract static class AbstractPropertyBuilder<B extends AbstractPropertyBuilder<B>> extends BuilderSupport {

        protected final PropertyMap.Builder properties = new PropertyMap.Builder();

        protected AbstractPropertyBuilder() {
        }
        
        protected AbstractPropertyBuilder(@Nonnull PropertySupport entity) {
            properties(checkNotNull(entity, "entity").getProperties());
        }

        @Nonnull
        public B property(@Nonnull String key, @Nonnull Object value) {
            properties.property(key, value);

            return self();
        }

        @Nonnull
        public B properties(@Nonnull Map<String, Object> other) {
            properties.properties(other);

            return self();
        }

        @Nonnull
        protected abstract B self();
    }
}
