/*
 * Copyright (C) 2014 Indeed Inc.
 *
 * 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.indeed.imhotep.client;

import com.google.common.base.Charsets;
import com.google.common.base.Throwables;
import com.indeed.util.core.DataLoadingRunnable;
import com.indeed.util.zookeeper.ZooKeeperConnection;
import org.apache.log4j.Logger;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.data.Stat;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @author jsgroth
 */
public class ZkHostsReloader extends DataLoadingRunnable implements HostsReloader {
    private static final Logger log = Logger.getLogger(ZkHostsReloader.class);

    private static final int TIMEOUT = 60000;

    private final ZooKeeperConnection zkConnection;
    private final String zkPath;

    private volatile boolean closed;
    private volatile List<Host> hosts;

    public ZkHostsReloader(final String zkNodes, final boolean readHostsBeforeReturning) {
        this(zkNodes, "/imhotep/daemons", readHostsBeforeReturning);
    }

    public ZkHostsReloader(final String zkNodes, final String zkPath, final boolean readHostsBeforeReturning) {
        super("ZkHostsReloader");

        zkConnection = new ZooKeeperConnection(zkNodes, TIMEOUT);
        try {
            zkConnection.connect();
        } catch (Exception e) {
            throw Throwables.propagate(e);
        }
        this.zkPath = trimEndingSlash(zkPath);
        closed = false;
        hosts = new ArrayList<Host>();
        if (readHostsBeforeReturning) {
            int retries = 0;
            while (true) {
                updateLastLoadCheck();
                if (load()) {
                    loadComplete();
                    break;
                }
                try {
                    Thread.sleep(TIMEOUT);
                } catch (InterruptedException e) {
                    log.error("interrupted", e);
                }
                if (++retries == 5) {
                    throw new RuntimeException("unable to connect to zookeeper");
                }
            }
        }
    }

    private static String trimEndingSlash(final String s) {
        if (s.endsWith("/")) {
            return s.substring(0, s.length() - 1);
        }
        return s;
    }

    @Override
    public boolean load() {
        if (!closed) {
            try {
                try {
                    try {
                        final List<Host> newHosts = readHostsFromZK();
                        if (!newHosts.equals(hosts)) {
                            hosts = newHosts;
                        }
                        return true;
                    } catch (KeeperException e) {
                        log.error("zookeeper exception", e);
                        loadFailed();
                        zkConnection.close();
                        zkConnection.connect();
                    }
                } catch (IOException e) {
                    loadFailed();
                    log.error("io exception", e);
                }
            } catch (InterruptedException e) {
                log.error("interrupted", e);
                loadFailed();
            }
        }
        return false;
    }

    private List<Host> readHostsFromZK() throws KeeperException, InterruptedException {
        final List<String> childNodes = zkConnection.getChildren(zkPath, false);
        final List<Host> hosts = new ArrayList<Host>(childNodes.size());
        for (final String childNode : childNodes) {
            final byte[] data = zkConnection.getData(zkPath + "/" + childNode, false, new Stat());
            final String hostString = new String(data, Charsets.UTF_8);
            final String[] splitHostString = hostString.split(":");
            hosts.add(new Host(splitHostString[0], Integer.parseInt(splitHostString[1])));
        }
        Collections.sort(hosts);
        return hosts;
    }

    public List<Host> getHosts() {
        return hosts;
    }

    @Override
    public void shutdown() {
        closed = true;
        try {
            zkConnection.close();
        } catch (InterruptedException e) {
            log.error(e);
        }
    }
}
