/*
 * Destination job support for CUPS.
 *
 * Copyright © 2020-2024 by OpenPrinting.
 * Copyright 2012-2017 by Apple Inc.
 *
 * Licensed under Apache License v2.0.  See the file "LICENSE" for more information.
 */

/*
 * Include necessary headers...
 */

#include "cups-private.h"
#include "debug-internal.h"


/*
 * 'cupsCancelDestJob()' - Cancel a job on a destination.
 *
 * The "job_id" is the number returned by cupsCreateDestJob.
 *
 * Returns @code IPP_STATUS_OK@ on success and
 * @code IPP_STATUS_ERROR_NOT_AUTHORIZED@ or
 * @code IPP_STATUS_ERROR_FORBIDDEN@ on failure.
 *
 * @since CUPS 1.6/macOS 10.8@
 */

ipp_status_t                            /* O - Status of cancel operation */
cupsCancelDestJob(http_t      *http,	/* I - Connection to destination */
                  cups_dest_t *dest,	/* I - Destination */
                  int         job_id)	/* I - Job ID */
{
  cups_dinfo_t	*info;			/* Destination information */


  if ((info = cupsCopyDestInfo(http, dest)) != NULL)
  {
    ipp_t	*request;		/* Cancel-Job request */

    request = ippNewRequest(IPP_OP_CANCEL_JOB);

    ippSetVersion(request, info->version / 10, info->version % 10);

    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, info->uri);
    ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", job_id);
    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser());

    ippDelete(cupsDoRequest(http, request, info->resource));
    cupsFreeDestInfo(info);
  }

  return (cupsLastError());
}


/*
 * 'cupsCloseDestJob()' - Close a job and start printing.
 *
 * Use when the last call to cupsStartDocument passed 0 for "last_document".
 * "job_id" is the job ID returned by cupsCreateDestJob. Returns @code IPP_STATUS_OK@
 * on success.
 *
 * @since CUPS 1.6/macOS 10.8@
 */

ipp_status_t				/* O - IPP status code */
cupsCloseDestJob(
    http_t       *http,			/* I - Connection to destination */
    cups_dest_t  *dest,			/* I - Destination */
    cups_dinfo_t *info, 		/* I - Destination information */
    int          job_id)		/* I - Job ID */
{
  int			i;		/* Looping var */
  ipp_t			*request = NULL;/* Close-Job/Send-Document request */
  ipp_attribute_t	*attr;		/* operations-supported attribute */


  DEBUG_printf(("cupsCloseDestJob(http=%p, dest=%p(%s/%s), info=%p, job_id=%d)", (void *)http, (void *)dest, dest ? dest->name : NULL, dest ? dest->instance : NULL, (void *)info, job_id));

 /*
  * Get the default connection as needed...
  */

  if (!http)
    http = _cupsConnect();

 /*
  * Range check input...
  */

  if (!http || !dest || !info || job_id <= 0)
  {
    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
    DEBUG_puts("1cupsCloseDestJob: Bad arguments.");
    return (IPP_STATUS_ERROR_INTERNAL);
  }

 /*
  * Build a Close-Job or empty Send-Document request...
  */

  if ((attr = ippFindAttribute(info->attrs, "operations-supported",
                               IPP_TAG_ENUM)) != NULL)
  {
    for (i = 0; i < attr->num_values; i ++)
      if (attr->values[i].integer == IPP_OP_CLOSE_JOB)
      {
        request = ippNewRequest(IPP_OP_CLOSE_JOB);
        break;
      }
  }

  if (!request)
    request = ippNewRequest(IPP_OP_SEND_DOCUMENT);

  if (!request)
  {
    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(ENOMEM), 0);
    DEBUG_puts("1cupsCloseDestJob: Unable to create Close-Job/Send-Document "
               "request.");
    return (IPP_STATUS_ERROR_INTERNAL);
  }

  ippSetVersion(request, info->version / 10, info->version % 10);

  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
               NULL, info->uri);
  ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id",
                job_id);
  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
               NULL, cupsUser());
  if (ippGetOperation(request) == IPP_OP_SEND_DOCUMENT)
    ippAddBoolean(request, IPP_TAG_OPERATION, "last-document", 1);

 /*
  * Send the request and return the status...
  */

  ippDelete(cupsDoRequest(http, request, info->resource));

  DEBUG_printf(("1cupsCloseDestJob: %s (%s)", ippErrorString(cupsLastError()),
                cupsLastErrorString()));

  return (cupsLastError());
}


/*
 * 'cupsCreateDestJob()' - Create a job on a destination.
 *
 * Returns @code IPP_STATUS_OK@ or @code IPP_STATUS_OK_SUBST@ on success, saving the job ID
 * in the variable pointed to by "job_id".
 *
 * @since CUPS 1.6/macOS 10.8@
 */

ipp_status_t				/* O - IPP status code */
cupsCreateDestJob(
    http_t        *http,		/* I - Connection to destination */
    cups_dest_t   *dest,		/* I - Destination */
    cups_dinfo_t  *info, 		/* I - Destination information */
    int           *job_id,		/* O - Job ID or 0 on error */
    const char    *title,		/* I - Job name */
    int           num_options,		/* I - Number of job options */
    cups_option_t *options)		/* I - Job options */
{
  ipp_t			*request,	/* Create-Job request */
			*response;	/* Create-Job response */
  ipp_attribute_t	*attr;		/* job-id attribute */


  DEBUG_printf(("cupsCreateDestJob(http=%p, dest=%p(%s/%s), info=%p, "
                "job_id=%p, title=\"%s\", num_options=%d, options=%p)", (void *)http, (void *)dest, dest ? dest->name : NULL, dest ? dest->instance : NULL, (void *)info, (void *)job_id, title, num_options, (void *)options));

 /*
  * Get the default connection as needed...
  */

  if (!http)
    http = _cupsConnect();

 /*
  * Range check input...
  */

  if (job_id)
    *job_id = 0;

  if (!http || !dest || !info || !job_id)
  {
    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
    DEBUG_puts("1cupsCreateDestJob: Bad arguments.");
    return (IPP_STATUS_ERROR_INTERNAL);
  }

 /*
  * Build a Create-Job request...
  */

  if ((request = ippNewRequest(IPP_OP_CREATE_JOB)) == NULL)
  {
    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(ENOMEM), 0);
    DEBUG_puts("1cupsCreateDestJob: Unable to create Create-Job request.");
    return (IPP_STATUS_ERROR_INTERNAL);
  }

  ippSetVersion(request, info->version / 10, info->version % 10);

  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
               NULL, info->uri);
  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
               NULL, cupsUser());
  if (title)
    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name", NULL,
                 title);

  cupsEncodeOptions2(request, num_options, options, IPP_TAG_OPERATION);
  cupsEncodeOptions2(request, num_options, options, IPP_TAG_JOB);
  cupsEncodeOptions2(request, num_options, options, IPP_TAG_SUBSCRIPTION);

 /*
  * Send the request and get the job-id...
  */

  response = cupsDoRequest(http, request, info->resource);

  if ((attr = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER)) != NULL)
  {
    *job_id = attr->values[0].integer;
    DEBUG_printf(("1cupsCreateDestJob: job-id=%d", *job_id));
  }

  ippDelete(response);

 /*
  * Return the status code from the Create-Job request...
  */

  DEBUG_printf(("1cupsCreateDestJob: %s (%s)", ippErrorString(cupsLastError()),
                cupsLastErrorString()));

  return (cupsLastError());
}


/*
 * 'cupsFinishDestDocument()' - Finish the current document.
 *
 * Returns @code IPP_STATUS_OK@ or @code IPP_STATUS_OK_SUBST@ on success.
 *
 * @since CUPS 1.6/macOS 10.8@
 */

ipp_status_t				/* O - Status of document submission */
cupsFinishDestDocument(
    http_t       *http,			/* I - Connection to destination */
    cups_dest_t  *dest,			/* I - Destination */
    cups_dinfo_t *info) 		/* I - Destination information */
{
  DEBUG_printf(("cupsFinishDestDocument(http=%p, dest=%p(%s/%s), info=%p)", (void *)http, (void *)dest, dest ? dest->name : NULL, dest ? dest->instance : NULL, (void *)info));

 /*
  * Get the default connection as needed...
  */

  if (!http)
    http = _cupsConnect();

 /*
  * Range check input...
  */

  if (!http || !dest || !info)
  {
    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
    DEBUG_puts("1cupsFinishDestDocument: Bad arguments.");
    return (IPP_STATUS_ERROR_INTERNAL);
  }

 /*
  * Get the response at the end of the document and return it...
  */

  ippDelete(cupsGetResponse(http, info->resource));

  DEBUG_printf(("1cupsFinishDestDocument: %s (%s)",
                ippErrorString(cupsLastError()), cupsLastErrorString()));

  return (cupsLastError());
}


/*
 * 'cupsStartDestDocument()' - Start a new document.
 *
 * "job_id" is the job ID returned by cupsCreateDestJob.  "docname" is the name
 * of the document/file being printed, "format" is the MIME media type for the
 * document (see CUPS_FORMAT_xxx constants), and "num_options" and "options"
 * are the options do be applied to the document. "last_document" should be 1
 * if this is the last document to be submitted in the job.  Returns
 * @code HTTP_CONTINUE@ on success.
 *
 * @since CUPS 1.6/macOS 10.8@
 */

http_status_t				/* O - Status of document creation */
cupsStartDestDocument(
    http_t        *http,		/* I - Connection to destination */
    cups_dest_t   *dest,		/* I - Destination */
    cups_dinfo_t  *info, 		/* I - Destination information */
    int           job_id,		/* I - Job ID */
    const char    *docname,		/* I - Document name */
    const char    *format,		/* I - Document format */
    int           num_options,		/* I - Number of document options */
    cups_option_t *options,		/* I - Document options */
    int           last_document)	/* I - 1 if this is the last document */
{
  ipp_t		*request;		/* Send-Document request */
  http_status_t	status;			/* HTTP status */


  DEBUG_printf(("cupsStartDestDocument(http=%p, dest=%p(%s/%s), info=%p, job_id=%d, docname=\"%s\", format=\"%s\", num_options=%d, options=%p, last_document=%d)", (void *)http, (void *)dest, dest ? dest->name : NULL, dest ? dest->instance : NULL, (void *)info, job_id, docname, format, num_options, (void *)options, last_document));

 /*
  * Get the default connection as needed...
  */

  if (!http)
    http = _cupsConnect();

 /*
  * Range check input...
  */

  if (!http || !dest || !info || job_id <= 0)
  {
    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
    DEBUG_puts("1cupsStartDestDocument: Bad arguments.");
    return (HTTP_STATUS_ERROR);
  }

 /*
  * Create a Send-Document request...
  */

  if ((request = ippNewRequest(IPP_OP_SEND_DOCUMENT)) == NULL)
  {
    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(ENOMEM), 0);
    DEBUG_puts("1cupsStartDestDocument: Unable to create Send-Document "
               "request.");
    return (HTTP_STATUS_ERROR);
  }

  ippSetVersion(request, info->version / 10, info->version % 10);

  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
               NULL, info->uri);
  ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", job_id);
  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
               NULL, cupsUser());
  if (docname)
    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "document-name",
                 NULL, docname);
  if (format)
    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE,
                 "document-format", NULL, format);
  ippAddBoolean(request, IPP_TAG_OPERATION, "last-document", (char)last_document);

  cupsEncodeOptions2(request, num_options, options, IPP_TAG_OPERATION);
  cupsEncodeOptions2(request, num_options, options, IPP_TAG_DOCUMENT);

 /*
  * Send and delete the request, then return the status...
  */

  status = cupsSendRequest(http, request, info->resource, CUPS_LENGTH_VARIABLE);

  ippDelete(request);

  return (status);
}
