/*
 * Copyright (c) 1998,1999,2000
 *	Traakan, Inc., Los Altos, CA
 *	All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice unmodified, this list of conditions, and the following
 *    disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * Project:  NDMJOB
 * Ident:    $Id: $
 *
 * Description:
 *
 */


#include "ndmagents.h"


#ifndef NDMOS_OPTION_NO_CONTROL_AGENT

int
ndmca_test_query_conn_types (struct ndm_session *sess,
			     struct ndmconn *ref_conn)
{
    struct ndmconn *conn = ref_conn;
    struct ndm_control_agent *ca = &sess->control_acb;
    int	rc;
    unsigned int i;

    switch (conn->protocol_version) {
	default:	return -1234;

#ifndef NDMOS_OPTION_NO_NDMP2
	case NDMP2VER:
	    NDMC_WITH_VOID_REQUEST(ndmp2_config_get_mover_type, NDMP2VER)
	        rc = NDMC_CALL(conn);
		if (rc) {
		    ndmalogf (sess, "Test", 1, "GET_MOVER_TYPE failed");
		    return rc;
		}

		for (i = 0; i < reply->methods.methods_len; i++) {
		    switch(reply->methods.methods_val[i]) {
			case NDMP2_ADDR_LOCAL:
			    ca->has_local_addr = 1;
			    break;
			case NDMP2_ADDR_TCP:
			    ca->has_tcp_addr = 1;
			    break;
			default:
			    break;
		    }
		}

		NDMC_FREE_REPLY();
	    NDMC_ENDWITH
	    break;
#endif /* !NDMOS_OPTION_NO_NDMP2 */

#ifndef NDMOS_OPTION_NO_NDMP3
	case NDMP3VER:
	    NDMC_WITH_VOID_REQUEST(ndmp3_config_get_connection_type, NDMP3VER)
		rc = NDMC_CALL(conn);
		if (rc) {
		    ndmalogf (sess, "Test", 1, "GET_CONNECTION_TYPE failed");
		    return rc;
		}

		for (i = 0; i < reply->addr_types.addr_types_len; i++) {
		    switch(reply->addr_types.addr_types_val[i]) {
			case NDMP3_ADDR_LOCAL:
			    ca->has_local_addr = 1;
			    break;
			case NDMP3_ADDR_TCP:
			    ca->has_tcp_addr = 1;
			    break;
			default:
			    break;
		    }
		}
		NDMC_FREE_REPLY();
	    NDMC_ENDWITH
	    break;
#endif /* !NDMOS_OPTION_NO_NDMP3 */

#ifndef NDMOS_OPTION_NO_NDMP4
	case NDMP4VER:
	    NDMC_WITH_VOID_REQUEST(ndmp4_config_get_connection_type, NDMP4VER)
		rc = NDMC_CALL(conn);
		if (rc) {
		    ndmalogf (sess, "Test", 1, "GET_CONNECTION_TYPE failed");
		    return rc;
		}

		for (i = 0; i < reply->addr_types.addr_types_len; i++) {
		    switch(reply->addr_types.addr_types_val[i]) {
			case NDMP4_ADDR_LOCAL:
			    ca->has_local_addr = 1;
			    break;
			case NDMP4_ADDR_TCP:
			    ca->has_tcp_addr = 1;
			    break;
			default:
			    break;
		    }
		}
		NDMC_FREE_REPLY();
	    NDMC_ENDWITH
	    break;
#endif /* !NDMOS_OPTION_NO_NDMP4 */
	}

	return 0;
}


int
ndmca_test_load_tape (struct ndm_session *sess)
{
	struct ndm_control_agent *ca = &sess->control_acb;
	int			rc;

	ca->tape_mode = NDMP9_TAPE_READ_MODE;
	ca->is_label_op = 1;

	rc = ndmca_op_robot_startup (sess, 1);
	if (rc) return rc;

	rc = ndmca_connect_tape_agent(sess);
	if (rc) {
		ndmconn_destruct (sess->plumb.tape);
		return rc;	/* already tattled */
	}

	rc = ndmca_media_load_first (sess);
	if (rc) return rc;

	ndmca_tape_close (sess);

	return 0;
}

int
ndmca_test_unload_tape (struct ndm_session *sess)
{
	ndmca_tape_open (sess);

	ndmca_media_unload_current(sess);

	return 0;
}

int
ndmca_test_check_expect_errs (struct ndmconn *conn, int rc,
  ndmp9_error expect_errs[])
{
	struct ndm_session *sess = conn->context;
	int		protocol_version = conn->protocol_version;
	struct ndmp_xa_buf *xa = &conn->call_xa_buf;
	unsigned	msg = xa->request.header.message;
	char *		msgname = ndmp_message_to_str (protocol_version, msg);
	ndmp9_error	reply_error = conn->last_reply_error;
	int		i;

	/* make sure we have a 'test' active */
	ndmca_test_open (sess, msgname, ndmp9_error_to_str (expect_errs[0]));

	if (rc >= 0) {
		/* Call succeeded. Body valid */
		rc = 1;
		for (i = 0; (int)expect_errs[i] >= 0; i++) {
			if (reply_error == expect_errs[i]) {
				rc = 0;
				break;
			}
		}

		if (rc) {
			if (reply_error != NDMP9_NO_ERR
			  && expect_errs[0] != NDMP9_NO_ERR) {
				/* both are errors, don't be picky */
				rc = 2;
			} else {
				/* intolerable mismatch */
			}
		} else {
			/* Worked as expected */
		}
	}

	if (rc != 0) {
	    char tmpbuf[128];

	    for (i = 0; (int)expect_errs[i] >= 0; i++) {
		ndmalogf (sess, "Test", 1,
			  "%s #%d -- .... %s %s",
			  sess->control_acb.test_phase,
			  sess->control_acb.test_step,
			  (i==0) ? "expected" : "or",
			  ndmp9_error_to_str (expect_errs[i]));
	    }

	    sprintf(tmpbuf, "got %s (error expected)", ndmp9_error_to_str (reply_error));

	    if (rc == 2)
		ndmca_test_warn (sess, tmpbuf);
	    else
		ndmca_test_fail (sess, tmpbuf);

	    ndma_tattle (conn, xa, rc);

	    if (rc == 2)
		rc = 0;
	}

	return rc;
}

int
ndmca_test_check_expect (struct ndmconn *conn, int rc, ndmp9_error expect_err)
{
	ndmp9_error		errs[2];

	errs[0] = expect_err;
	errs[1] = -1;

	return ndmca_test_check_expect_errs (conn, rc, errs);
}

int
ndmca_test_check_expect_no_err (struct ndmconn *conn, int rc)
{
	return ndmca_test_check_expect (conn, rc, NDMP9_NO_ERR);
}

int
ndmca_test_check_expect_illegal_state (struct ndmconn *conn, int rc)
{
	return ndmca_test_check_expect (conn, rc, NDMP9_ILLEGAL_STATE_ERR);
}

int
ndmca_test_check_expect_illegal_args (struct ndmconn *conn, int rc)
{
	return ndmca_test_check_expect (conn, rc, NDMP9_ILLEGAL_ARGS_ERR);
}


int
ndmca_test_call (struct ndmconn *conn,
  struct ndmp_xa_buf *xa, ndmp9_error expect_err)
{
	struct ndm_session *sess = conn->context;
	int		protocol_version = conn->protocol_version;
	unsigned	msg = xa->request.header.message;
	char *		msgname = ndmp_message_to_str (protocol_version, msg);
	unsigned	reply_error;
	int		rc;

	/* close previous test if there is one */
	ndmca_test_close (sess);

	/* open new 'test' */
	ndmca_test_open (sess, msgname, ndmp9_error_to_str (expect_err));

	rc = ndma_call_no_tattle (conn, xa);

	reply_error = ndmnmb_get_reply_error (&xa->reply);

	if (rc >= 0) {
		/* Call succeeded. Body valid */
		if (reply_error == expect_err) {
			/* Worked exactly as expected */
			rc = 0;
		} else if (reply_error != NDMP9_NO_ERR
		        && expect_err != NDMP9_NO_ERR) {
			/* both are errors, don't be picky about the codes */
			rc = 2;
		} else {
			/* intolerable mismatch */
			rc = 1;
		}
	}

	if (rc != 0) {
	    char tmpbuf[128];
	    sprintf(tmpbuf, "got %s (call)", ndmp9_error_to_str (reply_error));
	    if (rc == 2)
		ndmca_test_warn (sess, tmpbuf);
	    else
		ndmca_test_fail (sess, tmpbuf);

	    ndma_tattle (conn, xa, rc);

	    if (rc == 2)
		rc = 0;
	}

	return rc;
}

/*
 * start or open a test if not already opened
 */
void
ndmca_test_open (struct ndm_session *sess, char *test_name, char *sub_test_name)
{
    static char test_name_buf[512];

    if (sess->control_acb.active_test == 0) {
	/* record name */
	if (sub_test_name)
	    sprintf(test_name_buf, "%s/%s", test_name, sub_test_name);
	else
	    strcpy(test_name_buf, test_name);
	sess->control_acb.active_test = test_name_buf;

	/* make sure flags are cleared */
	sess->control_acb.active_test_failed = (char *)0;
	sess->control_acb.active_test_warned = (char *)0;

    }
}

void
ndmca_test_warn (struct ndm_session *sess, char *warn_msg)
{
    static char warn_msg_buf[512];

    ndmca_test_open (sess, "UNKNOWN WARN", 0);

    strcpy(warn_msg_buf, warn_msg);
    sess->control_acb.active_test_warned = warn_msg_buf;
}

void
ndmca_test_fail (struct ndm_session *sess, char *fail_msg)
{
    static char fail_msg_buf[512];

    ndmca_test_open (sess, "UNKNOWN FAIL", 0);

    strcpy(fail_msg_buf, fail_msg);
    sess->control_acb.active_test_failed = fail_msg_buf;
}



/*
 * close or end a test if not already closed
 */
void
ndmca_test_close (struct ndm_session *sess)
{
    if (sess->control_acb.active_test != 0) {
	/* count test */
	sess->control_acb.n_step_tests++;

	/* display results */
	if (sess->control_acb.active_test_failed) {
	    ndmalogf (sess, "Test", 1,
		      "%s #%d -- Failed %s %s",
		      sess->control_acb.test_phase,
		      sess->control_acb.test_step,
		      sess->control_acb.active_test,
		      sess->control_acb.active_test_failed);
	    sess->control_acb.n_step_fail++;
	    exit(1);
	} else if (sess->control_acb.active_test_warned) {
	    ndmalogf (sess, "Test", 1,
		      "%s #%d -- Almost %s %s",
		      sess->control_acb.test_phase,
		      sess->control_acb.test_step,
		      sess->control_acb.active_test,
		      sess->control_acb.active_test_warned);
	    sess->control_acb.n_step_warn++;
	    exit(1);
	} else {
	    ndmalogf (sess, "Test", 2,
		      "%s #%d -- Passed %s",
		      sess->control_acb.test_phase,
		      sess->control_acb.test_step,
		      sess->control_acb.active_test);
	    sess->control_acb.n_step_pass++;
	}

	/* clear flags */
	sess->control_acb.active_test = (char *)0;
	sess->control_acb.active_test_failed = (char *)0;
	sess->control_acb.active_test_warned = (char *)0;

	/* advance test count */
	sess->control_acb.test_step++;

    }
}


/*
 * start a test phase (part of a series)
 */
void
ndmca_test_phase (struct ndm_session *sess, char *test_phase, char *desc)
{
	ndmalogf (sess, "TEST", 0, "Test %s -- %s", test_phase, desc);

	sess->control_acb.test_phase = test_phase;
	sess->control_acb.test_step = 1;
	sess->control_acb.n_step_pass = 0;
	sess->control_acb.n_step_fail = 0;
	sess->control_acb.n_step_warn = 0;
	sess->control_acb.n_step_tests = 0;
}

void
ndmca_test_log_step (struct ndm_session *sess, int level, char *msg)
{
	int had_active = (sess->control_acb.active_test != 0);

	ndmalogf (sess, "Test", level, "%s #%d -- %s",
		  sess->control_acb.test_phase,
		  sess->control_acb.test_step,
		  msg);

	/* in case we have a open test -- close it */
	ndmca_test_close (sess);

	/* advance test count if we didn't have an active test */
	if (!had_active)
	    sess->control_acb.test_step++;

}

void
ndmca_test_log_note (struct ndm_session *sess, int level, char *msg)
{
	ndmalogf (sess, "Test", level, "%s #%d %s",
		sess->control_acb.test_phase,
		sess->control_acb.test_step,
		msg);
}

/*
 * finish a test phase (part of a series)
 */
void
ndmca_test_done_phase (struct ndm_session *sess)
{
	struct ndm_control_agent *ca = &sess->control_acb;
	char *			status;
	int had_active = (sess->control_acb.active_test != 0);

	/* close previous test if there is one */
	ndmca_test_close (sess);

	if (ca->n_step_fail)
		status = "Failed";
	else if (ca->n_step_warn)
		status = "Almost";
	else if (ca->n_step_pass > 0)
		status = "Passed";
	else
		status = "Whiffed";

	ndmalogf (sess, "TEST", 0, "Test %s %s -- pass=%d warn=%d fail=%d (total %d)",
			ca->test_phase,
			status,
			ca->n_step_pass,
			ca->n_step_warn,
			ca->n_step_fail,
			ca->n_step_tests);

	ca->total_n_step_pass += ca->n_step_pass;
	ca->total_n_step_warn += ca->n_step_warn;
	ca->total_n_step_fail += ca->n_step_fail;
	ca->total_n_step_tests += ca->n_step_tests;

	/* advance test count if we didn't have an active test so
	 * clean up phases have a new test count
         */
	if (!had_active)
	    sess->control_acb.test_step++;
}

/*
 * finish a test series (which may include test phases)
 */
void
ndmca_test_done_series (struct ndm_session *sess, char *series_name)
{
	struct ndm_control_agent *ca = &sess->control_acb;
	char *			status;

	/* close previous test if there is one */
	ndmca_test_close (sess);

	if (ca->total_n_step_fail)
		status = "Failed";
	else if (ca->total_n_step_warn)
		status = "Almost";
	else
		status = "Passed";

	ndmalogf (sess, "TEST", 0, "FINAL %s %s -- pass=%d warn=%d fail=%d (total %d)",
			series_name,
			status,
			ca->total_n_step_pass,
			ca->total_n_step_warn,
			ca->total_n_step_fail,
			ca->total_n_step_tests);
}



void
ndmca_test_fill_data (char *buf, int bufsize, int recno, int fileno)
{
	char *		src;
	char *		srcend;
	char *		dst = buf;
	char *		dstend = buf+bufsize;
	unsigned short	sequence = 0;
	struct {
		unsigned short	fileno;
		unsigned short	sequence;
		unsigned long	recno;
	}		x;

	x.fileno = fileno;
	x.recno = recno;

	srcend = (char *) &x;
	srcend += sizeof x;

	while (dst < dstend) {
		x.sequence = sequence++;
		src = (char *) &x;

		while (src < srcend && dst < dstend)
			*dst++ = *src++;
	}
}
#endif /* !NDMOS_OPTION_NO_CONTROL_AGENT */
