// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <algorithm>
#include <array>
#include <string>

#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/version.h"
#include "chrome/browser/ash/printing/printer_info.h"
#include "chromeos/printing/cups_printer_status.h"
#include "chromeos/printing/printer_configuration.h"
#include "printing/backend/cups_jobs.h"
#include "printing/printer_status.h"

namespace {

const char kPdfMimeType[] = "application/pdf";
const char kPwgRasterMimeType[] = "image/pwg-raster";

// Wraps several printing data structures so that we can use
// PostTaskAndReplyWithResult().
struct QueryResult {
  printing::PrinterQueryResult result;
  printing::PrinterInfo printer_info;
  printing::PrinterStatus printer_status;
};

// Returns true if any of the |ipp_versions| are greater than or equal to 2.0.
bool AllowedIpp(const std::vector<base::Version>& ipp_versions) {
  return std::ranges::any_of(ipp_versions, [](const base::Version& version) {
    return version.IsValid() && version.components()[0] >= 2;
  });
}

// Returns true if |mime_type| is one of the supported types.
bool SupportedMime(const std::string& mime_type) {
  return mime_type == kPwgRasterMimeType || mime_type == kPdfMimeType;
}

// Returns true if |formats| contains one of the supported printer description
// languages for an autoconf printer identified by MIME type.
bool SupportsRequiredPDLS(const std::vector<std::string>& formats) {
  return std::ranges::any_of(formats, &SupportedMime);
}

// Returns true if |info| describes a printer for which we want to attempt
// automatic configuration.
bool IsAutoconf(const ::printing::PrinterInfo& info) {
  return info.ipp_everywhere || (AllowedIpp(info.ipp_versions) &&
                                 SupportsRequiredPDLS(info.document_formats));
}

// Dispatches an IPP request to |host| to retrieve printer information.  Returns
// a nullptr if the request fails.
QueryResult QueryPrinterImpl(const std::string& host,
                             const int port,
                             const std::string& path,
                             bool encrypted) {
  QueryResult result;
  result.result =
      ::printing::GetPrinterInfo(host, port, path, encrypted,
                                 &result.printer_info, &result.printer_status);
  if (result.result != ::printing::PrinterQueryResult::kSuccess) {
    LOG(ERROR) << "Could not retrieve printer info from "
               << (encrypted ? "ipps://" : "ipp://") << host << ":" << port
               << path << ": " << static_cast<int>(result.result);
  }

  return result;
}

// Handles the request for |info|.  Parses make and model information before
// calling |callback|.
void OnPrinterQueried(ash::PrinterInfoCallback callback,
                      const QueryResult& query_result) {
  const ::printing::PrinterQueryResult& result = query_result.result;
  const ::printing::PrinterInfo& printer_info = query_result.printer_info;
  const ::printing::PrinterStatus& printer_status = query_result.printer_status;
  if (result != ::printing::PrinterQueryResult::kSuccess) {
    VLOG(1) << "Could not reach printer";
    std::move(callback).Run(result, ::printing::PrinterStatus(),
                            /*make_and_model=*/std::string(),
                            /*document_formats=*/{}, /*ipp_everywhere=*/false,
                            chromeos::PrinterAuthenticationInfo{},
                            chromeos::IppPrinterInfo{});
    return;
  }
  std::move(callback).Run(
      result, printer_status, printer_info.make_and_model,
      printer_info.document_formats, IsAutoconf(printer_info),
      {.oauth_server = printer_info.oauth_server,
       .oauth_scope = printer_info.oauth_scope},
      {/*document_formats=*/printer_info.document_formats,
       /*document_format_default=*/printer_info.document_format_default,
       /*document_format_preferred=*/printer_info.document_format_preferred,
       /*urf_supported=*/printer_info.urf_supported,
       /*pdf_versions=*/printer_info.pdf_versions,
       /*ipp_features=*/printer_info.ipp_features,
       /*mopria_certified=*/printer_info.mopria_certified,
       /*printer_kind=*/printer_info.printer_kind});
}

}  // namespace

namespace ash {

void QueryIppPrinter(const std::string& host,
                     const int port,
                     const std::string& path,
                     bool encrypted,
                     PrinterInfoCallback callback) {
  DCHECK(!host.empty());

  // QueryPrinterImpl could block on a network call for a noticable amount of
  // time (100s of ms). Also the user is waiting on this result.  Thus, run at
  // USER_VISIBLE with MayBlock.
  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE, {base::TaskPriority::USER_VISIBLE, base::MayBlock()},
      base::BindOnce(&QueryPrinterImpl, host, port, path, encrypted),
      base::BindOnce(&OnPrinterQueried, std::move(callback)));
}

}  // namespace ash
