/*-
 * -\-\-
 * Helios Services
 * --
 * Copyright (C) 2016 Spotify AB
 * --
 * Licensed 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 com.spotify.helios.servicescommon;

import static com.google.common.base.Throwables.throwIfInstanceOf;
import static com.google.common.collect.Sets.newHashSet;
import static com.spotify.helios.servicescommon.ZooKeeperAclProviders.digest;
import static com.spotify.helios.servicescommon.ZooKeeperAclProviders.heliosAclProvider;

import com.google.common.collect.Lists;
import com.spotify.helios.servicescommon.coordination.CuratorClientFactoryImpl;
import com.spotify.helios.servicescommon.coordination.DefaultZooKeeperClient;
import com.spotify.helios.servicescommon.coordination.ZooKeeperClient;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.AuthInfo;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.api.ACLProvider;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.data.ACL;

public class ZooKeeperAclInitializer {

  public static void main(final String[] args) throws Exception {
    if (args.length == 6) {
      initializeAcl(args[0], args[1], args[2], args[3], args[4], args[5]);
    } else {
      System.out.println("usage: <ZK connect string> <ZK cluster ID> "
                         + "<master user> <master password> <agent user> <agent password>");
      System.exit(-1);
    }
  }

  static void initializeAcl(final String zooKeeperConnectionString,
                            final String zooKeeperClusterId,
                            final String masterUser,
                            final String masterPassword,
                            final String agentUser,
                            final String agentPassword)
      throws KeeperException {
    final ACLProvider aclProvider = heliosAclProvider(
        masterUser, digest(masterUser, masterPassword),
        agentUser, digest(agentUser, agentPassword));
    final List<AuthInfo> authorization = Lists.newArrayList(new AuthInfo(
        "digest", String.format("%s:%s", masterUser, masterPassword).getBytes()));

    final RetryPolicy zooKeeperRetryPolicy = new ExponentialBackoffRetry(1000, 3);
    final CuratorFramework curator = new CuratorClientFactoryImpl().newClient(
        zooKeeperConnectionString,
        (int) TimeUnit.SECONDS.toMillis(60),
        (int) TimeUnit.SECONDS.toMillis(15),
        zooKeeperRetryPolicy,
        aclProvider,
        authorization);

    final ZooKeeperClient client = new DefaultZooKeeperClient(curator, zooKeeperClusterId);
    try {
      client.start();
      initializeAclRecursive(client, "/", aclProvider);
    } finally {
      client.close();
    }
  }

  static void initializeAclRecursive(final ZooKeeperClient client, final String path,
                                     final ACLProvider aclProvider)
      throws KeeperException {
    try {
      final List<ACL> expected = aclProvider.getAclForPath(path);
      final List<ACL> actual = client.getAcl(path);

      if (newHashSet(expected).equals(newHashSet(actual))) {
        // actual ACL matches expected
      } else {
        client.setAcl(path, expected);
      }

      for (final String child : client.getChildren(path)) {
        initializeAclRecursive(client, path.replaceAll("/$", "") + "/" + child, aclProvider);
      }
    } catch (Exception e) {
      throwIfInstanceOf(e, KeeperException.class);
      throw new RuntimeException(e);
    }
  }
}
