/*
 * 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_robot_issue_scsi_req (struct smc_ctrl_block *smc)
{
	struct ndmconn *	conn = (struct ndmconn *) smc->app_data;
	struct smc_scsi_req *	sr = &smc->scsi_req;
	int			rc;

	rc = ndmscsi_execute (conn, (struct ndmscsi_request *) sr, 0);
	return rc;
}


int
ndmca_robot_prep_target (struct ndm_session *sess)
{
	struct smc_ctrl_block *	smc = &sess->control_acb.smc_cb;
	int			rc;

	NDMOS_MACRO_ZEROFILL (smc);

	smc->app_data = sess->plumb.robot;
	smc->issue_scsi_req = ndmca_robot_issue_scsi_req;

	rc = ndmscsi_use (sess->plumb.robot,
				&sess->control_acb.job.robot_target);
	if (rc) return rc;

	return 0;
}

int
ndmca_robot_obtain_info (struct ndm_session *sess)
{
	struct smc_ctrl_block *	smc = &sess->control_acb.smc_cb;
	int			rc;

	rc = smc_inquire (smc);
	if (rc) return rc;

	rc = smc_get_elem_aa (smc);
	if (rc) return rc;

	rc = smc_read_elem_status (smc);
	if (rc) return rc;

	return 0;
}

int
ndmca_robot_init_elem_status (struct ndm_session *sess)
{
	struct smc_ctrl_block *	smc = &sess->control_acb.smc_cb;
	int			rc;

	ndmalogf (sess, 0, 1,
	    "Commanding robot to initialize element status (take inventory)");

	rc = smc_init_elem_status (smc);
	if (rc) {
		ndmalogf (sess, 0, 0, "init-elem-status failed");
		return rc;
	}

	return 0;
}

int
ndmca_robot_startup (struct ndm_session *sess)
{
	int		rc;

	if (!sess->control_acb.job.have_robot)
		return -1;	/* Huh? why were we called */

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

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

	return 0;
}

int
ndmca_robot_move (struct ndm_session *sess, int src_addr, int dst_addr)
{
	struct ndm_control_agent *ca = &sess->control_acb;
	struct smc_ctrl_block *	smc = &ca->smc_cb;
	int			rc;
	unsigned int		t;

	ndmalogf (sess, 0, 2, "robot moving @%d to @%d",
			src_addr, dst_addr);

	rc = -1;
	for (t = 0; t <= ca->job.robot_timeout; t += 10) {
		if (t > 0) {
			ndmalogf (sess, 0, 2,
				"Pausing ten seconds before retry (%d/%d)",
				t, ca->job.robot_timeout);
			sleep (10);
		}
		rc = smc_move (smc, src_addr, dst_addr,
					0, smc->elem_aa.mte_addr);
		if (rc == 0) break;
	}

	if (rc == 0) {
		ndmalogf (sess, 0, 2, "robot move OK @%d to @%d",
				src_addr, dst_addr);
	} else {
		ndmalogf (sess, 0, 2, "robot move BAD @%d to @%d",
			src_addr, dst_addr);
	}

	return rc;
}

int
ndmca_robot_load (struct ndm_session *sess, int slot_addr)
{
	struct smc_ctrl_block *	smc = &sess->control_acb.smc_cb;
	unsigned		dte_addr = smc->elem_aa.dte_addr;
	int			rc;

	if (sess->control_acb.job.drive_addr_given)
		dte_addr = sess->control_acb.job.drive_addr;

	ndmalogf (sess, 0, 1,
			"Commanding robot to load slot @%d into drive @%d",
			slot_addr, dte_addr);

	rc = ndmca_robot_move (sess, slot_addr, dte_addr);

	return rc;
}

int
ndmca_robot_unload (struct ndm_session *sess, int slot_addr)
{
	struct smc_ctrl_block *	smc = &sess->control_acb.smc_cb;
	int			dte_addr = smc->elem_aa.dte_addr;
	int			rc;

	if (sess->control_acb.job.drive_addr_given)
		dte_addr = sess->control_acb.job.drive_addr;

	/* tricky part -- some (most?) robots need the drive to eject */

	ndmalogf (sess, 0, 1,
			"Commanding robot to unload drive @%d to slot @%d",
			dte_addr, slot_addr);

	rc = ndmca_robot_move (sess, dte_addr, slot_addr);

	return rc;
}


struct smc_element_descriptor *
ndmca_robot_find_element (struct ndm_session *sess, int element_address)
{
	struct smc_ctrl_block *		smc = &sess->control_acb.smc_cb;
	unsigned int			i;
	struct smc_element_descriptor *	edp;

	for (i = 0; i < smc->n_elem_desc; i++) {
		edp = &smc->elem_desc[i];
		if (edp->element_address == element_address)
			return edp;
	}

	return 0;
}

int
ndmca_robot_check_ready (struct ndm_session *sess)
{
	struct smc_ctrl_block *		smc = &sess->control_acb.smc_cb;
	unsigned			first_dte_addr;
	unsigned			n_dte_addr;
	int				rc;
	unsigned int			i;
	int				errcnt = 0;
	struct smc_element_descriptor *	edp;

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

	if (sess->control_acb.job.remedy_all) {
		first_dte_addr = smc->elem_aa.dte_addr;
		n_dte_addr = smc->elem_aa.dte_count;
	} else {
		n_dte_addr = 1;
		if (sess->control_acb.job.drive_addr_given) {
			first_dte_addr = sess->control_acb.job.drive_addr;
		} else {
			first_dte_addr = smc->elem_aa.dte_addr;
		}
	}

	for (i = 0; i < n_dte_addr; i++) {
		edp = ndmca_robot_find_element (sess, first_dte_addr+i);

		if (!edp->Full)
			continue;

		ndmalogf (sess, 0, 1, "tape drive @%d not empty",
				edp->element_address);
		errcnt++;
	}

	return errcnt;
}

int
ndmca_robot_remedy_ready (struct ndm_session *sess)
{
	struct smc_ctrl_block *		smc = &sess->control_acb.smc_cb;
	int				rc;
	unsigned int			i;
	int				errcnt;
	struct smc_element_descriptor *	edp;
	struct smc_element_descriptor *	edp2;
	unsigned			first_dte_addr;
	unsigned			n_dte_addr;
	char				prefix[60];

	errcnt = 0;

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

	if (sess->control_acb.job.remedy_all) {
		first_dte_addr = smc->elem_aa.dte_addr;
		n_dte_addr = smc->elem_aa.dte_count;
	} else {
		n_dte_addr = 1;
		if (sess->control_acb.job.drive_addr_given) {
			first_dte_addr = sess->control_acb.job.drive_addr;
		} else {
			first_dte_addr = smc->elem_aa.dte_addr;
		}
	}

	for (i = 0; i < n_dte_addr; i++) {
		edp = ndmca_robot_find_element (sess, first_dte_addr+i);

		if (!edp->Full)
			continue;

		sprintf (prefix, "drive @%d not empty", edp->element_address);

		if (!edp->SValid) {
			ndmalogf (sess, 0, 1, "%s, invalid source", prefix);
			errcnt++;
			continue;
		}

		sprintf (NDMOS_API_STREND(prefix), ", src @%d",
							edp->src_se_addr);

		edp2 = ndmca_robot_find_element (sess, edp->src_se_addr);

		if (edp2->element_type_code != SMC_ELEM_TYPE_SE) {
			ndmalogf (sess, 0, 1, "%s, not slot", prefix);
			errcnt++;
			continue;
		}

		if (edp2->Full) {
			ndmalogf (sess, 0, 1, "%s, but slot Full", prefix);
			errcnt++;
			continue;
		}

		rc = ndmca_robot_move (sess,
				edp->element_address, edp->src_se_addr);
		if (rc) {
			ndmalogf (sess, 0, 1, "%s, move failed", prefix);
			errcnt++;
			continue;
		}
	}

	return errcnt;
}



/*
 * ndmca_robot_query() incrementally obtains info so that we
 * can print progress.
 */

int
ndmca_robot_query (struct ndm_session *sess)
{
	struct smc_ctrl_block *	smc = &sess->control_acb.smc_cb;
	int			rc;
	unsigned int		i;
	char			buf[100];
	char			lnbuf[30];
	int			lineno, nline = 1;

	ndmalogqr (sess, "  Type");

	rc = smc_inquire (smc);
	if (rc) {
		ndmalogqr (sess, "    ERROR smc_inquire(): %s", smc->errmsg);
	} else {
		ndmalogqr (sess, "    '%s'", smc->ident);
	}


	ndmalogqr (sess, "  Elements");
	rc = smc_get_elem_aa (smc);
	if (rc) {
		ndmalogqr (sess, "    ERROR smc_get_elem_aa(): %s", smc->errmsg);
	} else {
		strcpy (lnbuf, "    ");
		for (lineno = 0, nline = 1; lineno < nline; lineno++) {
			rc = smc_pp_element_address_assignments (&smc->elem_aa,
								lineno, buf);
			if (rc < 0) {
				strcpy (buf, "PP-ERROR");
			}
			nline = rc;
			ndmalogqr (sess, "%s %s", lnbuf, buf);
		}
	}

	ndmalogqr (sess, "  Status");
	rc = smc_read_elem_status (smc);
	if (rc) {
		ndmalogqr (sess, "    ERROR smc_read_elem_status(): %s", smc->errmsg);
	} else {
		ndmalogqr (sess, "    E#  Addr Type Status");
		ndmalogqr (sess, "    --  ---- ---- ---------------------");
		for (i = 0; i < smc->n_elem_desc; i++) {
			struct smc_element_descriptor *	edp;

			edp = &smc->elem_desc[i];

			for (lineno = 0, nline = 1; lineno < nline; lineno++) {
				rc = smc_pp_element_descriptor (edp,
								lineno, buf);

				if (lineno == 0)
					sprintf (lnbuf, "    %2d ", i+1);
				else
					sprintf (lnbuf, "       ");

				if (rc < 0) {
					strcpy (buf, "PP-ERROR");
				}
				nline = rc;
				ndmalogqr (sess, "%s %s", lnbuf, buf);
			}
		}
	}

	return 0;
}


int
ndmca_robot_verify_media (struct ndm_session *sess)
{
	struct smc_ctrl_block *	smc = &sess->control_acb.smc_cb;
	struct ndm_media_table *mtab = &sess->control_acb.job.media_tab;
	int			rc;
	struct ndmmedia *	me;
	struct smc_element_descriptor *edp;
	int			i;
	unsigned int		j;
	int			errcnt = 0;

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

	for (i = 0; i < mtab->n_media; i++) {
		me = &mtab->media[i];

		if (! me->valid_slot) {
			me->slot_missing = 1;
			errcnt++;
			continue;	/* what now */
		}

		for (j = 0; j < smc->n_elem_desc; j++) {
			edp = &smc->elem_desc[j];

			if (edp->element_type_code != SMC_ELEM_TYPE_SE)
				continue;

			if (edp->element_address != me->slot_addr)
				continue;

			if (!edp->Full) {
				me->slot_empty = 1;
				errcnt++;
			} else {
				me->slot_empty = 0;
			}
			break;
		}
		if (j >= smc->n_elem_desc) {
			me->slot_bad = 1;
			errcnt++;
		}
	}

	return errcnt;
}

/*
 * For NDM_JOB_OP_LIST_LABELS, fill in media_tab based on non-empty slots.
 * Note: this might REALLY nerf on a cleaning cartridge.
 */

int
ndmca_robot_synthesize_media (struct ndm_session *sess)
{
	struct smc_ctrl_block *	smc = &sess->control_acb.smc_cb;
	struct ndm_media_table *mtab = &sess->control_acb.job.media_tab;
	int			rc;
	struct ndmmedia *	me;
	struct smc_element_descriptor *edp;
	unsigned int		i;

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

	for (i = 0; i < smc->n_elem_desc; i++) {
		edp = &smc->elem_desc[i];

		if (edp->element_type_code != SMC_ELEM_TYPE_SE)
			continue;

		if (!edp->Full)
			continue;

		me = &mtab->media[mtab->n_media++];
		NDMOS_MACRO_ZEROFILL (me);

		me->valid_slot = 1;
		me->slot_addr = edp->element_address;
	}

	return 0;
}
#endif /* !NDMOS_OPTION_NO_CONTROL_AGENT */
