package org.apache.helix;

/*
 * 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.
 */

import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.NotImplementedException;
import org.apache.helix.zookeeper.zkclient.DataUpdater;
import org.apache.helix.zookeeper.zkclient.IZkChildListener;
import org.apache.helix.zookeeper.zkclient.IZkDataListener;
import org.apache.zookeeper.data.Stat;


/**
 * Generic interface for accessing and manipulating data on a backing store like Zookeeper.
 * @param <T> The type of record to use
 */
public interface BaseDataAccessor<T> {
  /**
   * This will always attempt to create the znode, if it exists it will return false. Will
   * create parents if they do not exist. For performance reasons, it may try to create
   * child first and only if it fails it will try to create parents
   * @param path path to the ZNode to create
   * @param record the data to write to the ZNode
   * @param options Set the type of ZNode see the valid values in {@link AccessOption}
   * @return true if creation succeeded, false otherwise (e.g. if the ZNode exists)
   */
  boolean create(String path, T record, int options);

  /**
   * This will always attempt to create the znode, if it exists it will return false. Will
   * create parents if they do not exist. For performance reasons, it may try to create
   * child first and only if it fails it will try to create parents
   * @param path path to the ZNode to create
   * @param record the data to write to the ZNode
   * @param options Set the type of ZNode see the valid values in {@link AccessOption}
   * @param ttl TTL of the node in milliseconds, if options supports it
   * @return true if creation succeeded, false otherwise (e.g. if the ZNode exists)
   */
  default boolean create(String path, T record, int options, long ttl) {
    throw new UnsupportedOperationException("create with TTL support is not implemented.");
  }

  /**
   * This will always attempt to set the data on existing node. If the ZNode does not
   * exist it will create it and all its parents ZNodes if necessary
   * @param path path to the ZNode to set
   * @param record the data to write to the ZNode
   * @param options Set the type of ZNode see the valid values in {@link AccessOption}
   * @return true if data was successfully set, false otherwise
   */
  boolean set(String path, T record, int options);

  /**
   * This will attempt to set the data on existing node only if version matches.
   * If the ZNode does not exist it will create it and all its parent ZNodes only if expected
   * version is -1
   * @param path path to the ZNode to set
   * @param record the data to write to the ZNode
   * @param options Set the type of ZNode see the valid values in {@link AccessOption}
   * @param expectVersion the expected version of the data to be overwritten, -1 means match any
   *          version
   * @return true if data was successfully set, false otherwise (e.g. if the version mismatches)
   */
  boolean set(String path, T record, int expectVersion, int options);

  /**
   * This will attempt to update the data using the updater. If the ZNode
   * does not exist it will create it and all its parent ZNodes.
   * Updater will be invoked with null value if node does not exist.
   * @param path path to the ZNode to update
   * @param updater an update routine for the data to merge in
   * @param options Set the type of ZNode see the valid values in {@link AccessOption}
   * @return true if data update succeeded, false otherwise
   */
  boolean update(String path, DataUpdater<T> updater, int options);

  /**
   * This will attempt to update the data using the updater using
   * each updater for the corresponding path.
   * This should be used on existing ZNodes only.
   * @param updaterByPath updaters for each path to update
   * @return true if all the updates succeeded, false otherwise
   */
  default boolean multiSet(Map<String, DataUpdater<T>> updaterByPath) {
    throw new NotImplementedException("multiSet is not implemented");
  }

  /**
   * This will remove the ZNode and all its descendants if any
   * @param path path to the root ZNode to remove
   * @param options Set the type of ZNode see the valid values in {@link AccessOption}
   * @return true if the removal succeeded, false otherwise
   */
  boolean remove(String path, int options);

  /**
   * This will remove the ZNode, if the ZNode's version matches the provided expectedVersion. This
   * operation will fail if the node has any children.
   * @param path Path to the ZNode to update
   * @param options Set the type of ZNode see the valid values in {@link AccessOption}
   * @param expectedVersion the expected version of the node to be removed, -1 means match any
   * version
   * @return true if the removal succeeded, false otherwise
   */
  default boolean removeWithExpectedVersion(String path, int options, int expectedVersion) {
    throw new NotImplementedException("removeWithExpectedVersion is not implemented");
  }

  /**
   * Use it when creating children under a parent node. This will use async api for better
   * performance. If the child already exists it will return false.
   * @param paths the paths to the children ZNodes
   * @param records List of data to write to each of the path
   * @param options Set the type of ZNode see the valid values in {@link AccessOption}
   * @return For each child: true if creation succeeded, false otherwise (e.g. if the child exists)
   */
  boolean[] createChildren(List<String> paths, List<T> records, int options);

  /**
   * Use it when creating children under a parent node. This will use async api for better
   * performance. If the child already exists it will return false.
   * @param paths the paths to the children ZNodes
   * @param records List of data to write to each of the path
   * @param options Set the type of ZNode see the valid values in {@link AccessOption}
   * @param ttl TTL of the node in milliseconds, if options supports it
   * @return For each child: true if creation succeeded, false otherwise (e.g. if the child exists)
   */
  default boolean[] createChildren(List<String> paths, List<T> records, int options, long ttl) {
    throw new UnsupportedOperationException("createChildren with TTL support is not implemented.");
  }

  /**
   * can set multiple children under a parent node. This will use async api for better
   * performance. If this child does not exist it will create it.
   * @param paths the paths to the children ZNodes
   * @param records List of data with which to overwrite the corresponding ZNodes
   * @param options Set the type of ZNode see the valid values in {@link AccessOption}
   * @return For each child: true if the data was set, false otherwise
   */
  boolean[] setChildren(List<String> paths, List<T> records, int options);

  /**
   * Can update multiple nodes using async api for better performance. If a child does not
   * exist it will create it.
   * @param paths paths to the children ZNodes
   * @param updaters List of update routines for records to update
   * @param options Set the type of ZNode see the valid values in {@link AccessOption}
   * @return For each child, true if the data is updated successfully, false otherwise
   */
  boolean[] updateChildren(List<String> paths, List<DataUpdater<T>> updaters, int options);

  /**
   * remove multiple paths using async api. will remove any child nodes if any
   * @param paths paths to the ZNodes to remove
   * @param options Set the type of ZNode see the valid values in {@link AccessOption}
   * @return For each ZNode, true if successfully removed, false otherwise
   */
  boolean[] remove(List<String> paths, int options);

  /**
   * Get the {@link T} corresponding to the path
   * @param path path to the ZNode
   * @param stat retrieve the stat of the ZNode
   * @param options Set the type of ZNode see the valid values in {@link AccessOption}
   * @return the record data stored at the ZNode
   */
  T get(String path, Stat stat, int options);

  /**
   * Get List of {@link T} corresponding to the paths using async api
   * @param paths paths to the ZNodes
   * @param stats retrieve a list of stats for the ZNodes
   * @param options Set the type of ZNode see the valid values in {@link AccessOption}
   * @return List of record data stored at each ZNode
   */
  @Deprecated
  List<T> get(List<String> paths, List<Stat> stats, int options);

  /**
   * Get List of {@link T} corresponding to the paths using async api
   * @param paths paths to the ZNodes
   * @param stats retrieve a list of stats for the ZNodes
   * @param options Set the type of ZNode see the valid values in {@link AccessOption}
   * @param throwException Will throw an exception when it set to be true and one of the path
   *                             is failed to read.
   * @return List of record data stored at each ZNode, node value will return null if the node does
   *         not exist
   */
  List<T> get(List<String> paths, List<Stat> stats, int options, boolean throwException)
      throws HelixException;

  /**
   * Get the children under a parent path using async api
   *
   * For this API, if some of child node is failed to read, Helix will return the data of read
   * nodes. So user may get partial data. No exception will be thrown even if it is failed to read
   * all the data.
   *
   * @param parentPath path to the immediate parent ZNode
   * @param stats Zookeeper Stat objects corresponding to each child
   * @param options Set the type of ZNode see the valid values in {@link AccessOption}
   * @return A list of children of the parent ZNode
   */
  @Deprecated
  List<T> getChildren(String parentPath, List<Stat> stats, int options);

  /**
   * Get the children under a parent path using async api
   *
   * If some of child node is failed to read, Helix will do the retry within retry count. If the
   * result still cannot be retrieved completely, Helix will throw an HelixException.
   *
   * @param parentPath path to the immediate parent ZNode
   * @param stats Zookeeper Stat objects corresponding to each child
   * @param options Set the type of ZNode see the valid values in {@link AccessOption}
   * @param retryCount The number of retries that data is not completed read
   * @param retryInterval The interval between two retries
   * @return A list of children of the parent ZNode
   */
  List<T> getChildren(String parentPath, List<Stat> stats, int options, int retryCount,
      int retryInterval)
      throws HelixException;

  /**
   * Returns the child names given a parent path
   * @param parentPath path to the immediate parent ZNode
   * @param options Set the type of ZNode see the valid values in {@link AccessOption}
   * @return a list of the names of all of the parent ZNode's children
   */
  List<String> getChildNames(String parentPath, int options);

  /**
   * checks if the path exists in zk
   * @param path path to the ZNode to test
   * @param options Set the type of ZNode see the valid values in {@link AccessOption}
   * @return true if the ZNode exists, false otherwise
   */
  boolean exists(String path, int options);

  /**
   * checks if the all the paths exists
   * @param paths paths to the ZNodes to test
   * @param options Set the type of ZNode see the valid values in {@link AccessOption}
   * @return for each path, true if a valid ZNode exists, false otherwise
   */
  boolean[] exists(List<String> paths, int options);

  /**
   * Get the stats of all the paths
   * @param paths paths of the ZNodes to query
   * @param options Set the type of ZNode see the valid values in {@link AccessOption}
   * @return Zookeeper Stat object for each path
   */
  Stat[] getStats(List<String> paths, int options);

  /**
   * Get the stats of a single path
   * @param path path of the ZNode to query
   * @param options Set the type of ZNode see the valid values in {@link AccessOption}
   * @return Zookeeper Stat object corresponding to the ZNode
   */
  Stat getStat(String path, int options);

  /**
   * Subscribe data listener to path
   * @param path path to the ZNode to listen to
   * @param listener the listener to register for changes
   */
  void subscribeDataChanges(String path, IZkDataListener listener);

  /**
   * Unsubscribe data listener to path
   * @param path path to the ZNode to stop listening to
   * @param listener the listener currently subscribed to the ZNode
   */
  void unsubscribeDataChanges(String path, IZkDataListener listener);

  /**
   * Subscribe child listener to path
   * @param path path to the immediate parent ZNode
   * @param listener the listener to register for changes
   * @return
   */
  List<String> subscribeChildChanges(String path, IZkChildListener listener);

  /**
   * Unsubscribe child listener to path
   * @param path path to the immediate parent ZNode
   * @param listener the listener currently subscribed to the children
   */
  void unsubscribeChildChanges(String path, IZkChildListener listener);

  /**
   * TODO refactor this. reset() should not be in data accessor
   * reset the cache if any, when session expiry happens
   */
  void reset();

  /**
   * Close the connection to the metadata store
   */
  default void close() {
    System.out.println("Default close() was invoked! No operation was executed.");
  }
}
