/*
 * 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.curator.x.async.modeled;

import com.google.common.collect.ImmutableSet;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
import java.util.function.UnaryOperator;
import org.apache.curator.framework.api.CuratorEvent;
import org.apache.curator.framework.api.UnhandledErrorListener;
import org.apache.curator.x.async.AsyncCuratorFramework;
import org.apache.curator.x.async.WatchMode;
import org.apache.curator.x.async.modeled.details.ModeledFrameworkImpl;
import org.apache.zookeeper.WatchedEvent;

public class ModeledFrameworkBuilder<T> {
    private AsyncCuratorFramework client;
    private ModelSpec<T> modelSpec;
    private WatchMode watchMode;
    private UnaryOperator<WatchedEvent> watcherFilter;
    private UnhandledErrorListener unhandledErrorListener;
    private UnaryOperator<CuratorEvent> resultFilter;
    private Set<ModeledOptions> modeledOptions;

    /**
     * Build a new ModeledFramework instance
     *
     * @return new ModeledFramework instance
     */
    public ModeledFramework<T> build() {
        return ModeledFrameworkImpl.build(
                client, modelSpec, watchMode, watcherFilter, unhandledErrorListener, resultFilter, modeledOptions);
    }

    /**
     * Add watchers as appropriate to the Modeled Curator's ZNode using
     * {@link org.apache.curator.x.async.WatchMode#stateChangeAndSuccess}
     *
     * @return this for chaining
     * @see org.apache.curator.x.async.AsyncStage#event()
     */
    public ModeledFrameworkBuilder<T> watched() {
        this.watchMode = WatchMode.stateChangeAndSuccess;
        return this;
    }

    /**
     * Add watchers as appropriate using the given watchMode to the Modeled Curator's ZNode
     *
     * @param watchMode watcher style
     * @return this for chaining
     * @see org.apache.curator.x.async.AsyncStage#event()
     */
    public ModeledFrameworkBuilder<T> watched(WatchMode watchMode) {
        this.watchMode = watchMode;
        return this;
    }

    /**
     * Add watchers as appropriate using the given watchMode and filter to the Modeled Curator's ZNode
     *
     * @param watchMode watcher style
     * @param watcherFilter filter
     * @return this for chaining
     * @see org.apache.curator.x.async.AsyncStage#event()
     */
    public ModeledFrameworkBuilder<T> watched(WatchMode watchMode, UnaryOperator<WatchedEvent> watcherFilter) {
        this.watchMode = watchMode;
        this.watcherFilter = watcherFilter;
        return this;
    }

    /**
     * Use the given unhandledErrorListener for operations on the Modeled Curator's ZNode
     *
     * @param unhandledErrorListener listener
     * @return this for chaining
     */
    public ModeledFrameworkBuilder<T> withUnhandledErrorListener(UnhandledErrorListener unhandledErrorListener) {
        this.unhandledErrorListener = unhandledErrorListener;
        return this;
    }

    /**
     * Use the given result filter for operations on the Modeled Curator's ZNode
     *
     * @param resultFilter filter
     * @return this for chaining
     */
    public ModeledFrameworkBuilder<T> withResultFilter(UnaryOperator<CuratorEvent> resultFilter) {
        this.resultFilter = resultFilter;
        return this;
    }

    /**
     * Change the model spec to use
     *
     * @param modelSpec model spec
     * @return this for chaining
     */
    public ModeledFrameworkBuilder<T> withModelSpec(ModelSpec<T> modelSpec) {
        this.modelSpec = Objects.requireNonNull(modelSpec, "modelSpec cannot be null");
        return this;
    }

    /**
     * Change the client to use
     *
     * @param client new client
     * @return this for chaining
     */
    public ModeledFrameworkBuilder<T> withClient(AsyncCuratorFramework client) {
        this.client = Objects.requireNonNull(client, "client cannot be null");
        return this;
    }

    /**
     * Change the modeled options
     *
     * @param modeledOptions new options set
     * @return this for chaining
     */
    public ModeledFrameworkBuilder<T> withOptions(Set<ModeledOptions> modeledOptions) {
        this.modeledOptions = ImmutableSet.copyOf(Objects.requireNonNull(modeledOptions, "client cannot be null"));
        return this;
    }

    ModeledFrameworkBuilder() {
        modeledOptions = Collections.singleton(ModeledOptions.ignoreMissingNodesForChildren);
    }

    ModeledFrameworkBuilder(AsyncCuratorFramework client, ModelSpec<T> modelSpec) {
        this.client = Objects.requireNonNull(client, "client cannot be null");
        this.modelSpec = Objects.requireNonNull(modelSpec, "modelSpec cannot be null");
        modeledOptions = Collections.singleton(ModeledOptions.ignoreMissingNodesForChildren);
    }
}
