package ru.yandex.wmconsole.verification.whois;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.Socket;
import java.nio.charset.Charset;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ru.yandex.wmconsole.verification.WhoIsVerifier;

/**
 * @author avhaliullin
 */
public class WhoisRecordReader {
    private static final Logger log = LoggerFactory.getLogger(WhoisRecordReader.class);

    private final Pattern REDIRECT = Pattern.compile("\\s*whois server:\\s+([\\w.]+)\\s*", Pattern.CASE_INSENSITIVE);
    private final Pattern REMOVE_DOMAIN_WORD =
            Pattern.compile("(.*no match for .*domain.*|.*no entries found.*|.*domain.*not found.*|.*no information.*for.*domain.*)|" +
                    "(unsupported character encoding)|invalid query", Pattern.CASE_INSENSITIVE);
    private final char[] END_OF_STREAM = {13, 10};

    private final Queue<String> serversQueue;
    private final Set<String> serversSet;
    private final String hostName;
    private BufferedReader reader;
    private Socket socket;
    private String servName;
    private boolean usingDomainWord = false;

    public WhoisRecordReader(String hostName) {
        this.hostName = hostName;

        String topLevelDomain = hostName.substring(hostName.lastIndexOf('.') + 1);
        String servName = topLevelDomain + ".whois-servers.net";

        serversQueue = new LinkedList<String>();
        serversSet = new HashSet<String>();

        serversQueue.add(servName);
        serversSet.add(servName);
    }

    private void connect(String servName, boolean useDomainWord, boolean waiting) throws IOException {
        close();
        if (waiting) {
            log.debug("Waiting for " + WhoIsVerifier.WAITING_TIME + "ms.");
            try {
                Thread.sleep(WhoIsVerifier.WAITING_TIME);
            } catch (InterruptedException e) {
                log.debug("Interrupted!");
                return;
            }
        }
        log.info("Connecting to \"" + servName + "\", useDomainWord=" + useDomainWord);
        socket = new Socket(servName, 43);
        Writer out = new OutputStreamWriter(socket.getOutputStream(), Charset.forName("US-ASCII"));

        log.info("Asking about \"" + hostName + "\"");
        out.write((useDomainWord ? "domain " : "") + hostName);
        out.write(END_OF_STREAM);
        out.flush();

        reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        usingDomainWord = useDomainWord;
    }

    public String readLine() throws IOException {
        String line;
        if (reader == null || (line = reader.readLine()) == null) {
            if (serversQueue.isEmpty()) {
                close();
                return null;
            }

            servName = serversQueue.poll();

            connect(servName, true, false);
            return readLine();
        }

        Matcher redirectMatcher = REDIRECT.matcher(line);
        if (redirectMatcher.find()) {
            String redirectedTo = redirectMatcher.group(1);
            if (!serversSet.contains(redirectedTo)) {
                log.debug("Redirect to \"" + redirectedTo + "\" found. Line: \"" + line + "\"");
                serversSet.add(redirectedTo);
                serversQueue.add(redirectedTo);
            }
        }

        if (usingDomainWord && REMOVE_DOMAIN_WORD.matcher(line).find()) {
            log.debug("Using \"domain\" prefix is unsupported. Line: " + line);
            connect(servName, false, true);
            return readLine();
        }
        return line;
    }

    public void close() throws IOException {
        if (socket != null) {
            socket.close();
        }
    }
}
