/* This file is part of VoltDB.
 * Copyright (C) 2008-2020 VoltDB Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

package txnIdSelfCheck;

import org.voltcore.logging.VoltLogger;
import org.voltdb.client.Client;
import org.voltdb.client.ClientResponse;
import org.voltdb.client.NoConnectionsException;
import org.voltdb.client.ProcCallException;
import java.security.SecureRandom;

import java.io.IOException;

public enum TxnId2Utils {;

    static VoltLogger log = new VoltLogger("TxnId2Utils");

    static ClientResponse doAdHoc(Client client, String query) throws ProcCallException {
        return doProcCall(client, "@AdHoc", query);
    }

    static boolean isConnectionTransactionOrCatalogIssue(String statusString) {
        return statusString.matches("(?s).*AdHoc transaction -?[0-9]+ wasn.t planned against the current catalog version.*") ||
                statusString.matches(".*Connection to database host \\(.*\\) was lost before a response was received.*") ||
                statusString.matches(".*Transaction dropped due to change in mastership. It is possible the transaction was committed.*") ||
                statusString.matches("(?s).*Transaction being restarted due to fault recovery or shutdown.*") ||
                statusString.matches("(?s).*Invalid catalog update.  Catalog or deployment change was planned against one version of the cluster configuration but that version was no longer live.*") ||
                statusString.matches("(?s).*Ad Hoc Planner task queue is full.*") ||
                statusString.matches("(?s).*Transaction Interrupted.*");
    }

    static boolean isServerUnavailableIssue(String statusString) {
        return statusString.matches("(?s).*No response received in the allotted time.*") ||
                statusString.matches(".*Server is currently unavailable; try again later.*") ||
                statusString.matches(".*Server is paused.*") ||
                statusString.matches("(?s).*Server is shutting down.*") ||
                statusString.matches("(?s).*VoltDB failed to create the transaction internally.*it should be safe to resend the work, as the work was never started.*"); /*-5 SERVER_UNAVAILABLE*/
    }

    static boolean isConnectionTransactionCatalogOrServerUnavailableIssue(String statusString) {
        return isConnectionTransactionOrCatalogIssue(statusString) ||
                isServerUnavailableIssue(statusString);
    }

    static boolean isTransactionStateIndeterminate(ClientResponse cr) {
        String statusString = cr.getStatusString();
        return (statusString.matches("(?s).*No response received in the allotted time.*") ||
                statusString.matches(".*Connection to database host \\(.*\\) was lost before a response was received.*"));
    }


    static ClientResponse doProcCall(Client client, String proc, Object... parms) throws ProcCallException {
        return doProcCall(client, proc, false, parms);
    }
    static ClientResponse doProcCallOneShot(Client client, String proc, Object... parms) throws ProcCallException {
        return doProcCall(client, proc, true, parms);
    }
    static ClientResponse doProcCall(Client client, String proc, boolean oneshot, Object... parms) throws ProcCallException {
        Boolean sleep = false;
        Boolean noConnections = false;
        ClientResponse cr = null;
        while (true) {
            try {
                if (proc == "@AdHoc")
                    cr = client.callProcedure("@AdHoc", (String) parms[0]);
                else
                    cr = client.callProcedure(proc, parms);
                if (cr.getStatus() == ClientResponse.SUCCESS) {
                    Benchmark.txnCount.incrementAndGet();
                    return cr;
                } else {
                    log.debug(cr.getStatusString());
                    Benchmark.hardStop("unexpected response", cr);
                }
            } catch (NoConnectionsException e) {
                noConnections = true;
            } catch (IOException e) {
                Benchmark.hardStop(e);
            } catch (ProcCallException e) {
                cr = e.getClientResponse();
                String ss = cr.getStatusString();
                log.debug(ss);
                if (isConnectionTransactionOrCatalogIssue(ss)
                    ) { /* continue */ }
                else if (isServerUnavailableIssue(ss)) {
                    sleep = true;
                }
                else {
                    throw e;
                }
            }
            if (oneshot)
                return cr;
            if (sleep | noConnections) {
                try { Thread.sleep(3000); } catch (Exception f) { }
                sleep = false;
                if (noConnections)
                    while (client.getConnectedHostList().size() == 0);
                noConnections = false;
            }
        }
    }

    static long getRowCount(Client client, String tableName) throws NoConnectionsException, IOException, ProcCallException {
        ClientResponse cr = doAdHoc(client, "select count(*) from " + tableName + ";");
        return cr.getResults()[0].asScalarLong();
    }

    static final String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    static SecureRandom rnd = new SecureRandom();

    static String randomString( int len ){
        StringBuilder sb = new StringBuilder( len );
        for( int i = 0; i < len; i++ )
            sb.append( AB.charAt( rnd.nextInt(AB.length()) ) );
        return sb.toString();
    }
}
