package org.gnunet.testbed;

import com.google.common.base.Charsets;
import org.gnunet.testbed.ControllerStatusCallback;
import org.gnunet.testbed.messages.HelperInitMessage;
import org.gnunet.testbed.messages.HelperReplyMessage;
import org.gnunet.util.Helper;
import org.gnunet.util.RunaboutMessageReceiver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;

/**
 * A controller process.
 * The controller process is either a local helper process, or an ssh process that starts and controls
 * the testbed helper on a remote machine.
 */
public class ControllerProc {
    private static final Logger logger = LoggerFactory
            .getLogger(ControllerProc.class);
    private Helper helper;
    private ControllerStatusCallback cb;
    private Host host;

    public class ControllerProcReceiver extends RunaboutMessageReceiver {
        public void visit(HelperReplyMessage m) {
            System.out.println("got controller proc message");
            CompressedConfig ccfg = new CompressedConfig(m.compressed_config);
            host.cfg = ccfg.decompress();
            cb.onStartupSuccess(host.cfg);
        }

        @Override
        public void handleError() {
            cb.onStartupFailure();
        }
    }

    /**
     * Create a controller proc. Nothing will hapen until ControllerProc.start is called.
     */
    public ControllerProc() {
        // empty
    }

    /**
     * Starts a controller process at the given host.  The given host's configration
     * is used as a Template configuration to use for the remote controller; the
     * remote controller will be started with a slightly modified configuration
     * (port numbers, unix domain sockets and service home values are changed as per
     * TESTING library on the remote host).  The modified configuration replaces the
     * host's existing configuration before signalling success through the
     * GNUNET_TESTBED_ControllerStatusCallback()
     *
     * @param trustedIP the ip address of the controller which will be set as TRUSTED
     *          HOST(all connections form this ip are permitted by the testbed) when
     *          starting testbed controller at host. This can either be a single ip
     *          address or a network address in CIDR notation.
     * @param host the host where the controller has to be started.  CANNOT be NULL.
     * @param cb function called when the controller is successfully started or
     *          dies unexpectedly; GNUNET_TESTBED_controller_stop shouldn't be
     *          called if cb is called with GNUNET_SYSERR as status. Will never be
     *          called in the same task as 'GNUNET_TESTBED_controller_start'
     *          (synchronous errors will be signalled by returning NULL). This
     *          parameter cannot be NULL.
     */
    public void start(String trustedIP, Host host, ControllerStatusCallback cb) {
        this.cb = cb;
        this.host = host;
        if (host.isLocal()) {
            List<String> args = new ArrayList<String>();
            helper = new Helper(false, "gnunet-helper-testbed", args, new ControllerProcReceiver());
        } else {
            throw new AssertionError("not implemented yet");
        }
        helper.send(makeHelperInitMessage(trustedIP, host));
    }

    /**
     * Stop the controller process (also will terminate all peers and controllers
     * dependent on this controller).  This function blocks until the testbed has
     * been fully terminated (!). The controller status cb will not be called.
     */
    public void stop() {
        logger.debug("stopping controller");
        helper.kill(false);
    }


    private HelperInitMessage makeHelperInitMessage(String trustedIP, Host host) {
        HelperInitMessage m = new HelperInitMessage();
        if (host.hostname == null) {
            m.hostname = new byte[0];
            m.hostname_size = 0;
        } else {
            m.hostname_size =  host.hostname.length();
            m.hostname = host.hostname.getBytes(Charsets.UTF_8);
        }
        m.trusted_ip_size = trustedIP.length();
        m.trusted_ip = trustedIP;

        CompressedConfig ccfg = new CompressedConfig(host.cfg);
        m.compressed_config = ccfg.compressedData;
        m.config_size = ccfg.getUncompressedSize();

        return m;
    }
}
