// assembleJob prepares for printing a job by fetching the job's printer,
// ticket, and the job's PDF (what we're printing)
//
// The caller is responsible to remove the returned PDF file.
//
// Errors are returned as a string (last return value), for reporting
// to GCP and local logging.
func (pm *PrinterManager) assembleJob(job *lib.Job) (lib.Printer, cdd.CloudJobTicket, *os.File, string, cdd.PrintJobStateDiff) {
	printer, exists := pm.gcpPrintersByGCPID.Get(job.GCPPrinterID)
	if !exists {
		return lib.Printer{}, cdd.CloudJobTicket{}, nil,
			fmt.Sprintf("Failed to find GCP printer %s for job %s", job.GCPPrinterID, job.GCPJobID),
			cdd.PrintJobStateDiff{
				State: cdd.JobState{
					Type:               "STOPPED",
					ServiceActionCause: &cdd.ServiceActionCause{ErrorCode: "PRINTER_DELETED"},
				},
			}
	}

	ticket, err := pm.gcp.Ticket(job.GCPJobID)
	if err != nil {
		return lib.Printer{}, cdd.CloudJobTicket{}, nil,
			fmt.Sprintf("Failed to get a ticket for job %s: %s", job.GCPJobID, err),
			cdd.PrintJobStateDiff{
				State: cdd.JobState{
					Type:              "STOPPED",
					DeviceActionCause: &cdd.DeviceActionCause{ErrorCode: "INVALID_TICKET"},
				},
			}
	}

	pdfFile, err := cups.CreateTempFile()
	if err != nil {
		return lib.Printer{}, cdd.CloudJobTicket{}, nil,
			fmt.Sprintf("Failed to create a temporary file for job %s: %s", job.GCPJobID, err),
			cdd.PrintJobStateDiff{
				State: cdd.JobState{
					Type:              "STOPPED",
					DeviceActionCause: &cdd.DeviceActionCause{ErrorCode: "OTHER"},
				},
			}
	}

	pm.downloadSemaphore.Acquire()
	t := time.Now()
	// Do not check err until semaphore is released and timer is stopped.
	err = pm.gcp.Download(pdfFile, job.FileURL)
	dt := time.Since(t)
	pm.downloadSemaphore.Release()
	if err != nil {
		// Clean up this temporary file so the caller doesn't need extra logic.
		os.Remove(pdfFile.Name())
		return lib.Printer{}, cdd.CloudJobTicket{}, nil,
			fmt.Sprintf("Failed to download PDF for job %s: %s", job.GCPJobID, err),
			cdd.PrintJobStateDiff{
				State: cdd.JobState{
					Type:              "STOPPED",
					DeviceActionCause: &cdd.DeviceActionCause{ErrorCode: "DOWNLOAD_FAILURE"},
				},
			}
	}

	glog.Infof("Downloaded job %s in %s", job.GCPJobID, dt.String())
	pdfFile.Close()

	return printer, ticket, pdfFile, "", cdd.PrintJobStateDiff{}
}
// assembleGCPJob prepares for printing a job by fetching the job's printer,
// ticket, and data (what we're printing)
//
// The caller is responsible to remove the returned file.
//
// Errors are returned as a string (last return value), for reporting
// to GCP and local logging.
func (pm *PrinterManager) assembleGCPJob(job *gcp.Job) (string, *cdd.CloudJobTicket, string, string, cdd.PrintJobStateDiff) {
	_, exists := pm.gcpPrintersByGCPID.Get(job.GCPPrinterID)
	if !exists {
		return "", nil, "",
			fmt.Sprintf("Failed to find GCP printer %s for job %s", job.GCPPrinterID, job.GCPJobID),
			cdd.PrintJobStateDiff{
				State: &cdd.JobState{
					Type:               cdd.JobStateAborted,
					ServiceActionCause: &cdd.ServiceActionCause{ErrorCode: cdd.ServiceActionCausePrinterDeleted},
				},
			}
	}

	ticket, err := pm.gcp.Ticket(job.GCPJobID)
	if err != nil {
		return "", nil, "",
			fmt.Sprintf("Failed to get a ticket for job %s: %s", job.GCPJobID, err),
			cdd.PrintJobStateDiff{
				State: &cdd.JobState{
					Type:              cdd.JobStateAborted,
					DeviceActionCause: &cdd.DeviceActionCause{ErrorCode: cdd.DeviceActionCauseInvalidTicket},
				},
			}
	}

	file, err := cups.CreateTempFile()
	if err != nil {
		return "", nil, "",
			fmt.Sprintf("Failed to create a temporary file for job %s: %s", job.GCPJobID, err),
			cdd.PrintJobStateDiff{
				State: &cdd.JobState{
					Type:              cdd.JobStateAborted,
					DeviceActionCause: &cdd.DeviceActionCause{ErrorCode: cdd.DeviceActionCauseOther},
				},
			}
	}

	pm.downloadSemaphore.Acquire()
	t := time.Now()
	// Do not check err until semaphore is released and timer is stopped.
	err = pm.gcp.Download(file, job.FileURL)
	dt := time.Since(t)
	pm.downloadSemaphore.Release()
	if err != nil {
		// Clean up this temporary file so the caller doesn't need extra logic.
		os.Remove(file.Name())
		return "", nil, "",
			fmt.Sprintf("Failed to download data for job %s: %s", job.GCPJobID, err),
			cdd.PrintJobStateDiff{
				State: &cdd.JobState{
					Type:              cdd.JobStateAborted,
					DeviceActionCause: &cdd.DeviceActionCause{ErrorCode: cdd.DeviceActionCauseDownloadFailure},
				},
			}
	}

	glog.Infof("Downloaded job %s in %s", job.GCPJobID, dt.String())
	defer file.Close()

	return job.GCPPrinterID, ticket, file.Name(), "", cdd.PrintJobStateDiff{}
}