/*
 * This file is part of Bisq.
 *
 * Bisq is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 *
 * Bisq is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Bisq. If not, see <http://www.gnu.org/licenses/>.
 */

package haveno.network.p2p.network;

import java.io.File;

import java.util.Date;
import lombok.extern.slf4j.Slf4j;
import org.berndpruenster.netlayer.tor.ExternalTor;
import org.berndpruenster.netlayer.tor.Tor;
import org.berndpruenster.netlayer.tor.TorCtlException;

import java.net.ConnectException;
import java.net.UnknownHostException;

/**
 * This class creates a brand new instance of the Tor onion router.
 *
 * When asked, the class checks for the authentication method selected and
 * connects to the given control port. Finally, a {@link Tor} instance is
 * returned for further use.
 *
 * @author Florian Reimair
 *
 */
@Slf4j
public class RunningTor extends TorMode {

    private final String controlHost;
    private final int controlPort;
    private final String password;
    private final File cookieFile;
    private final boolean useSafeCookieAuthentication;


    public RunningTor(final File torDir,
                      final String controlHost,
                      final int controlPort,
                      final String password,
                      final File cookieFile,
                      final boolean useSafeCookieAuthentication) {
        super(torDir);
        this.controlHost = controlHost;
        this.controlPort = controlPort;
        this.password = password;
        this.cookieFile = cookieFile;
        this.useSafeCookieAuthentication = useSafeCookieAuthentication;
    }

    @Override
    public Tor getTor() throws TorCtlException {
        long ts1 = new Date().getTime();
        boolean retry = true;
        long twoMinutesInMilli = 1000 * 60 * 2;

        while (retry && ((new Date().getTime() - ts1) <= twoMinutesInMilli)) {
            retry = false;
            try {
                log.info("Connecting to running tor");

                Tor result;
                if (!password.isEmpty())
                    result = new ExternalTor(controlHost, controlPort, password);
                else if (cookieFile != null && cookieFile.exists())
                    result = new ExternalTor(controlHost, controlPort, cookieFile, useSafeCookieAuthentication);
                else
                    result = new ExternalTor(controlHost, controlPort);

                boolean isTorBootstrapped = result.control.waitUntilBootstrapped();
                if (!isTorBootstrapped) {
                    log.error("Couldn't bootstrap Tor.");
                }

                log.info(
                        "\n################################################################\n"
                                + "Connecting to Tor successful after {} ms. Start publishing hidden service.\n"
                                + "################################################################",
                        (new Date().getTime() - ts1)); // takes usually a few seconds

                return result;
            } catch (Exception e) {
                // netlayer throws UnknownHostException when tor docker container is not ready yet.
                // netlayer throws ConnectException before tor container bind to control port.
                if (e instanceof UnknownHostException || e instanceof ConnectException) {
                    log.warn("Couldn't connect to Tor control port. Retrying...", e);
                    retry = true;
                }

                log.error("Couldn't connect to Tor.", e);
            }
        }

        return null;
    }

    @Override
    public String getHiddenServiceDirectory() {
        return new File(torDir, HIDDEN_SERVICE_DIRECTORY).getAbsolutePath();
    }

}
