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

#include "components/payments/content/android/payment_manifest_parser_android.h"

#include <stddef.h>
#include <utility>
#include <vector>

#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "components/payments/content/android/jni_headers/PaymentManifestParser_jni.h"
#include "components/payments/content/developer_console_logger.h"
#include "components/payments/core/error_logger.h"
#include "content/public/browser/web_contents.h"
#include "url/gurl.h"

namespace payments {
namespace {

class ParseCallback {
 public:
  explicit ParseCallback(const base::android::JavaParamRef<jobject>& jcallback)
      : jcallback_(jcallback) {}

  ~ParseCallback() {}

  // Copies payment method manifest into Java.
  void OnPaymentMethodManifestParsed(
      const std::vector<GURL>& web_app_manifest_urls,
      const std::vector<url::Origin>& supported_origins,
      bool all_origins_supported) {
    DCHECK_GE(100U, web_app_manifest_urls.size());
    DCHECK_GE(100000U, supported_origins.size());
    JNIEnv* env = base::android::AttachCurrentThread();

    if (web_app_manifest_urls.empty() && supported_origins.empty() &&
        !all_origins_supported) {
      // Can trigger synchronous deletion of PaymentManifestParserAndroid.
      Java_ManifestParseCallback_onManifestParseFailure(env, jcallback_);
      return;
    }

    base::android::ScopedJavaLocalRef<jobjectArray> juris =
        Java_PaymentManifestParser_createUriArray(env,
                                                  web_app_manifest_urls.size());

    for (size_t i = 0; i < web_app_manifest_urls.size(); ++i) {
      bool is_valid_uri = Java_PaymentManifestParser_addUri(
          env, juris, base::checked_cast<int>(i),
          base::android::ConvertUTF8ToJavaString(
              env, web_app_manifest_urls[i].spec()));
      DCHECK(is_valid_uri);
    }

    base::android::ScopedJavaLocalRef<jobjectArray> jorigins =
        Java_PaymentManifestParser_createUriArray(env,
                                                  supported_origins.size());

    for (size_t i = 0; i < supported_origins.size(); ++i) {
      bool is_valid_uri = Java_PaymentManifestParser_addUri(
          env, jorigins, base::checked_cast<int>(i),
          base::android::ConvertUTF8ToJavaString(
              env, supported_origins[i].Serialize()));
      DCHECK(is_valid_uri);
    }

    // Can trigger synchronous deletion of PaymentManifestParserAndroid.
    Java_ManifestParseCallback_onPaymentMethodManifestParseSuccess(
        env, jcallback_, juris, jorigins, all_origins_supported);
  }

  // Copies web app manifest into Java.
  void OnWebAppManifestParsed(
      const std::vector<WebAppManifestSection>& manifest) {
    DCHECK_GE(100U, manifest.size());
    JNIEnv* env = base::android::AttachCurrentThread();

    if (manifest.empty()) {
      // Can trigger synchronous deletion of PaymentManifestParserAndroid.
      Java_ManifestParseCallback_onManifestParseFailure(env, jcallback_);
      return;
    }

    base::android::ScopedJavaLocalRef<jobjectArray> jmanifest =
        Java_PaymentManifestParser_createManifest(env, manifest.size());

    for (size_t i = 0; i < manifest.size(); ++i) {
      const WebAppManifestSection& section = manifest[i];
      DCHECK_GE(100U, section.fingerprints.size());

      Java_PaymentManifestParser_addSectionToManifest(
          env, jmanifest, base::checked_cast<int>(i),
          base::android::ConvertUTF8ToJavaString(env, section.id),
          section.min_version,
          base::checked_cast<int>(section.fingerprints.size()));

      for (size_t j = 0; j < section.fingerprints.size(); ++j) {
        const std::vector<uint8_t>& fingerprint = section.fingerprints[j];
        Java_PaymentManifestParser_addFingerprintToSection(
            env, jmanifest, base::checked_cast<int>(i),
            base::checked_cast<int>(j),
            base::android::ToJavaByteArray(env, fingerprint));
      }
    }

    // Can trigger synchronous deletion of PaymentManifestParserAndroid.
    Java_ManifestParseCallback_onWebAppManifestParseSuccess(env, jcallback_,
                                                            jmanifest);
  }

 private:
  base::android::ScopedJavaGlobalRef<jobject> jcallback_;

  DISALLOW_COPY_AND_ASSIGN(ParseCallback);
};

}  // namespace

PaymentManifestParserAndroid::PaymentManifestParserAndroid(
    std::unique_ptr<ErrorLogger> log)
    : parser_(std::move(log)) {}

PaymentManifestParserAndroid::~PaymentManifestParserAndroid() {}

void PaymentManifestParserAndroid::ParsePaymentMethodManifest(
    JNIEnv* env,
    const base::android::JavaParamRef<jobject>& jcaller,
    const base::android::JavaParamRef<jstring>& jcontent,
    const base::android::JavaParamRef<jobject>& jcallback) {
  parser_.ParsePaymentMethodManifest(
      base::android::ConvertJavaStringToUTF8(env, jcontent),
      base::BindOnce(&ParseCallback::OnPaymentMethodManifestParsed,
                     std::make_unique<ParseCallback>(jcallback)));
}

void PaymentManifestParserAndroid::ParseWebAppManifest(
    JNIEnv* env,
    const base::android::JavaParamRef<jobject>& jcaller,
    const base::android::JavaParamRef<jstring>& jcontent,
    const base::android::JavaParamRef<jobject>& jcallback) {
  parser_.ParseWebAppManifest(
      base::android::ConvertJavaStringToUTF8(env, jcontent),
      base::BindOnce(&ParseCallback::OnWebAppManifestParsed,
                     std::make_unique<ParseCallback>(jcallback)));
}

void PaymentManifestParserAndroid::DestroyPaymentManifestParserAndroid(
    JNIEnv* env,
    const base::android::JavaParamRef<jobject>& jcaller) {
  delete this;
}

// Caller owns the result.
jlong JNI_PaymentManifestParser_CreatePaymentManifestParserAndroid(
    JNIEnv* env,
    const base::android::JavaParamRef<jobject>& jweb_contents) {
  content::WebContents* web_contents =
      content::WebContents::FromJavaWebContents(jweb_contents);
  auto log = web_contents
                 ? std::make_unique<DeveloperConsoleLogger>(web_contents)
                 : std::make_unique<ErrorLogger>();
  return reinterpret_cast<jlong>(
      new PaymentManifestParserAndroid(std::move(log)));
}

}  // namespace payments
