/*
 * Copyright 2019 ACINQ SAS
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package fr.acinq.eclair.wallet.models;

import android.net.Uri;
import androidx.annotation.NonNull;
import com.google.common.base.Strings;
import fr.acinq.bitcoin.Satoshi;
import fr.acinq.eclair.CoinUtils;
import fr.acinq.eclair.package$;
import fr.acinq.eclair.payment.PaymentRequest;
import fr.acinq.eclair.wallet.utils.Constants;
import fr.acinq.eclair.wallet.utils.EclairException;
import fr.acinq.eclair.wallet.utils.WalletUtils;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.List;

public class BitcoinURI {
  private final static List<String> BITCOIN_PREFIXES = Arrays.asList("bitcoin://", "bitcoin:");

  @NonNull
  public final String address;
  @Nullable
  public final String message;
  @Nullable
  public final String label;
  @Nullable
  public final PaymentRequest lightning;
  @Nullable
  public final Satoshi amount;

  public BitcoinURI(final String input) throws EclairException.BitcoinURIParseException {

    if (input == null) {
      throw new EclairException.BitcoinURIParseException("input is null");
    }

    final Uri uri = Uri.parse(stripScheme(input));

    // -- should have no scheme (bitcoin: and bitcoin:// are stripped)
    if (uri.getScheme() != null) {
      throw new EclairException.BitcoinURIParseException("scheme is not valid");
    }

    // -- read and validate address
    final String path = uri.getPath();
    if (Strings.isNullOrEmpty(path)) {
      throw new EclairException.BitcoinURIParseException("address is missing");
    }
    try {
      package$.MODULE$.addressToPublicKeyScript(path, WalletUtils.getChainHash());
    } catch (IllegalArgumentException e) {
      throw new EclairException.BitcoinURIParseException(e.getLocalizedMessage(), e);
    }
    this.address = path;

    // -- read label/message field parameter
    this.label = uri.getQueryParameter("label");
    this.message = uri.getQueryParameter("message");

    // -- read and validate lightning payment request field
    final String lightningParam = uri.getQueryParameter("lightning");
    if (Strings.isNullOrEmpty(lightningParam)) {
      this.lightning = null;
    } else {
      this.lightning = PaymentRequest.read(lightningParam);
    }

    // -- read and validate amount field parameter. Amount is in BTC in the URI, and is converted to Satoshi,
    final String amountParam = uri.getQueryParameter("amount");
    if (Strings.isNullOrEmpty(amountParam)) {
      this.amount = null;
    } else {
      this.amount = CoinUtils.convertStringAmountToSat(amountParam, Constants.BTC_CODE);
    }

    // -- check required (yet unused) parameters
    // see https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki
    for (final String p : uri.getQueryParameterNames()) {
      if (p.startsWith("req-")) {
        throw new EclairException.BitcoinURIParseException("unhandled required param: " + p);
      }
    }
  }

  private String stripScheme(@Nonnull final String uri) {
    for (String prefix : BITCOIN_PREFIXES) {
      if (uri.toLowerCase().startsWith(prefix)) {
        return uri.substring(prefix.length());
      }
    }
    return uri;
  }

  @NonNull
  @Override
  public String toString() {
    return new StringBuilder()
      .append(getClass().getSimpleName()).append("[ ")
      .append("address: ").append(this.address).append(", ")
      .append("amount: ").append(this.amount).append(", ")
      .append("label: ").append(this.label).append(", ")
      .append("message: ").append(this.message).append(", ")
      .append("lightning: ").append(this.lightning).append(" ]").toString();
  }
}
