content: package com.alphawallet.app.viewmodel;
  
  import static com.alphawallet.app.C.Key.WALLET;
  import static com.alphawallet.app.repository.SharedPreferenceRepository.DEVELOPER_OVERRIDE;
  import static com.alphawallet.app.util.Utils.isValidUrl;
  
  import android.app.Activity;
  import android.content.Context;
  import android.content.Intent;
  import android.net.Uri;
  import android.webkit.WebView;
  import android.widget.Toast;
  
  import androidx.annotation.NonNull;
  import androidx.annotation.Nullable;
  import androidx.lifecycle.LiveData;
  import androidx.lifecycle.MutableLiveData;
  import androidx.preference.PreferenceManager;
  
  import com.alphawallet.app.C;
  import com.alphawallet.app.R;
  import com.alphawallet.app.analytics.Analytics;
  import com.alphawallet.app.entity.DApp;
  import com.alphawallet.app.entity.GasEstimate;
  import com.alphawallet.app.entity.NetworkInfo;
  import com.alphawallet.app.entity.Operation;
  import com.alphawallet.app.entity.QRResult;
  import com.alphawallet.app.entity.SignAuthenticationCallback;
  import com.alphawallet.app.entity.TransactionReturn;
  import com.alphawallet.app.entity.Wallet;
  import com.alphawallet.app.entity.analytics.QrScanSource;
  import com.alphawallet.app.interact.CreateTransactionInteract;
  import com.alphawallet.app.interact.GenericWalletInteract;
  import com.alphawallet.app.repository.EthereumNetworkRepositoryType;
  import com.alphawallet.app.service.AnalyticsServiceType;
  import com.alphawallet.app.service.AssetDefinitionService;
  import com.alphawallet.app.service.GasService;
  import com.alphawallet.app.service.KeyService;
  import com.alphawallet.app.service.TokensService;
  import com.alphawallet.app.service.TransactionSendHandlerInterface;
  import com.alphawallet.app.ui.AddEditDappActivity;
  import com.alphawallet.app.ui.HomeActivity;
  import com.alphawallet.app.ui.ImportTokenActivity;
  import com.alphawallet.app.ui.MyAddressActivity;
  import com.alphawallet.app.ui.QRScanning.QRScannerActivity;
  import com.alphawallet.app.ui.SendActivity;
  import com.alphawallet.app.ui.WalletConnectV2Activity;
  import com.alphawallet.app.util.DappBrowserUtils;
  import com.alphawallet.app.walletconnect.util.WalletConnectHelper;
  import com.alphawallet.app.web3.entity.WalletAddEthereumChainObject;
  import com.alphawallet.app.web3.entity.Web3Transaction;
  import com.alphawallet.hardware.SignatureFromKey;
  
  import org.web3j.utils.Numeric;
  
  import java.math.BigInteger;
  import java.util.List;
  import java.util.concurrent.TimeUnit;
  
  import javax.inject.Inject;
  
  import dagger.hilt.android.lifecycle.HiltViewModel;
  import io.reactivex.Observable;
  import io.reactivex.Single;
  import io.reactivex.disposables.Disposable;
  import io.reactivex.schedulers.Schedulers;
  import io.realm.Realm;
  
  @HiltViewModel
  public class DappBrowserViewModel extends BaseViewModel implements TransactionSendHandlerInterface
  {
      private static final int BALANCE_CHECK_INTERVAL_SECONDS = 20;
  
      private final MutableLiveData<NetworkInfo> activeNetwork = new MutableLiveData<>();
      private final MutableLiveData<Wallet> defaultWallet = new MutableLiveData<>();
      private final MutableLiveData<TransactionReturn> transactionFinalised = new MutableLiveData<>();
      private final MutableLiveData<TransactionReturn> transactionError = new MutableLiveData<>();
      private final MutableLiveData<TransactionReturn> transactionSigned = new MutableLiveData<>();
      private final GenericWalletInteract genericWalletInteract;
      private final AssetDefinitionService assetDefinitionService;
      private final CreateTransactionInteract createTransactionInteract;
      private final TokensService tokensService;
      private final EthereumNetworkRepositoryType ethereumNetworkRepository;
      private final KeyService keyService;
      private final GasService gasService;
  
      @Nullable
      private Disposable balanceTimerDisposable;
  
      @Inject
      DappBrowserViewModel(
              GenericWalletInteract genericWalletInteract,
              AssetDefinitionService assetDefinitionService,
              CreateTransactionInteract createTransactionInteract,
              TokensService tokensService,
              EthereumNetworkRepositoryType ethereumNetworkRepository,
              KeyService keyService,
              GasService gasService,
              AnalyticsServiceType analyticsService)
      {
          this.genericWalletInteract = genericWalletInteract;
          this.assetDefinitionService = assetDefinitionService;
          this.createTransactionInteract = createTransactionInteract;
          this.tokensService = tokensService;
          this.ethereumNetworkRepository = ethereumNetworkRepository;
          this.keyService = keyService;
          this.gasService = gasService;
          setAnalyticsService(analyticsService);
      }
  
      public AssetDefinitionService getAssetDefinitionService()
      {
          return assetDefinitionService;
      }
  
      public LiveData<NetworkInfo> activeNetwork()
      {
          return activeNetwork;
      }
  
      public LiveData<Wallet> defaultWallet()
      {
          return defaultWallet;
      }
  
      public MutableLiveData<TransactionReturn> transactionFinalised()
      {
          return transactionFinalised;
      }
  
      public MutableLiveData<TransactionReturn> transactionSigned()
      {
          return transactionSigned;
      }
  
      public MutableLiveData<TransactionReturn> transactionError()
      {
          return transactionError;
      }
  
      public void findWallet()
      {
          disposable = genericWalletInteract
                  .find()
                  .subscribe(this::onDefaultWallet, this::onError);
      }
  
      public NetworkInfo getActiveNetwork()
      {
          return ethereumNetworkRepository.getActiveBrowserNetwork();
      }
  
      public void checkForNetworkChanges()
      {
          activeNetwork.postValue(ethereumNetworkRepository.getActiveBrowserNetwork());
      }
  
      private void onDefaultWallet(final Wallet wallet)
      {
          defaultWallet.setValue(wallet);
      }
  
      private void checkBalance(final Wallet wallet)
      {
          final NetworkInfo info = getActiveNetwork();
          if (info != null && wallet != null)
          {
              disposable = tokensService.getChainBalance(wallet.address.toLowerCase(), info.chainId)
                      .subscribeOn(Schedulers.io())
                      .observeOn(Schedulers.io())
                      .subscribe(w -> {}, e -> {});
          }
      }
  
      public void setLastUrl(Context context, String url)
      {
          PreferenceManager.getDefaultSharedPreferences(context)
                  .edit().putString(C.DAPP_LASTURL_KEY, url).apply();
      }
  
      public void setHomePage(Context context, String url)
      {
          PreferenceManager.getDefaultSharedPreferences(context)
                  .edit().putString(C.DAPP_HOMEPAGE_KEY, url).apply();
      }
  
      public String getHomePage(Context context)
      {
          return PreferenceManager.getDefaultSharedPreferences(context).getString(C.DAPP_HOMEPAGE_KEY, null);
      }
  
      public void addToMyDapps(Context context, String title, String url)
      {
          Intent intent = new Intent(context, AddEditDappActivity.class);
          DApp dapp = new DApp(title, url);
          intent.putExtra(AddEditDappActivity.KEY_DAPP, dapp);
          intent.putExtra(AddEditDappActivity.KEY_MODE, AddEditDappActivity.MODE_ADD);
          context.startActivity(intent);
      }
  
      public void share(Context context, String url)
      {
          track(Analytics.Action.SHARE_URL);
          Intent intent = new Intent();
          intent.setAction(Intent.ACTION_SEND);
          intent.putExtra(Intent.EXTRA_TEXT, url);
          intent.setType("text/plain");
          context.startActivity(intent);
      }
  
      public void onClearBrowserCacheClicked(Context context)
      {
          WebView webView = new WebView(context);
          webView.clearCache(true);
          Toast.makeText(context, context.getString(R.string.toast_browser_cache_cleared),
                  Toast.LENGTH_SHORT).show();
          track(Analytics.Action.CLEAR_BROWSER_CACHE);
      }
  
      public void startScan(Activity activity)
      {
          Intent intent = new Intent(activity, QRScannerActivity.class);
          intent.putExtra(QrScanSource.KEY, QrScanSource.BROWSER_SCREEN.getValue());
          activity.startActivityForResult(intent, HomeActivity.DAPP_BARCODE_READER_REQUEST_CODE);
      }
  
      public List<DApp> getDappsMasterList(Context context)
      {
          return DappBrowserUtils.getDappsList(context);
      }
  
      public void setNetwork(long chainId)
      {
          NetworkInfo info = ethereumNetworkRepository.getNetworkByChain(chainId);
          if (info != null)
          {
              ethereumNetworkRepository.setActiveBrowserNetwork(info);
              gasService.startGasPriceCycle(chainId);
          }
      }
  
      public void showImportLink(Context ctx, String qrCode)
      {
          Intent intent = new Intent(ctx, ImportTokenActivity.class);
          intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
          intent.putExtra(C.IMPORT_STRING, qrCode);
          ctx.startActivity(intent);
      }
  
      public void getAuthorisation(Wallet wallet, Activity activity, SignAuthenticationCallback callback)
      {
          keyService.getAuthenticationForSignature(wallet, activity, callback);
      }
  
      public void resetSignDialog()
      {
          keyService.resetSigningDialog();
      }
  
      public void completeAuthentication(Operation signData)
      {
          keyService.completeAuthentication(signData);
      }
  
      public void failedAuthentication(Operation signData)
      {
          keyService.failedAuthentication(signData);
      }
  
      public void showSend(Context ctx, QRResult result)
      {
          Intent intent = new Intent(ctx, SendActivity.class);
          boolean sendingTokens = (result.getFunction() != null && result.getFunction().length() > 0);
          String address = defaultWallet.getValue().address;
          int decimals = 18;
  
          intent.putExtra(C.EXTRA_SENDING_TOKENS, sendingTokens);
          intent.putExtra(C.EXTRA_CONTRACT_ADDRESS, address);
          intent.putExtra(C.EXTRA_NETWORKID, result.chainId);
          intent.putExtra(C.EXTRA_SYMBOL, ethereumNetworkRepository.getNetworkByChain(result.chainId).symbol);
          intent.putExtra(C.EXTRA_DECIMALS, decimals);
          intent.putExtra(C.Key.WALLET, defaultWallet.getValue());
          intent.putExtra(C.EXTRA_AMOUNT, result);
          intent.setFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
          ctx.startActivity(intent);
      }
  
      public void requestSignature(Web3Transaction finalTx, Wallet wallet, long chainId)
      {
          createTransactionInteract.requestSignature(finalTx, wallet, chainId, this);
      }
  
      public void requestSignatureOnly(Web3Transaction finalTx, Wallet wallet, long chainId)
      {
          createTransactionInteract.requestSignTransaction(finalTx, wallet, chainId, this);
      }
  
      public void sendTransaction(Wallet wallet, long chainId, Web3Transaction w3Tx, SignatureFromKey signatureFromKey)
      {
          createTransactionInteract.sendTransaction(wallet, chainId, w3Tx, signatureFromKey);
      }
  
      public void signTransaction(long chainId, Web3Transaction tx, SignatureFromKey signatureFromKey)
      {
          createTransactionInteract.signTransaction(chainId, tx, signatureFromKey);
      }
  
      @Override
      public void transactionFinalised(TransactionReturn txData)
      {
          transactionFinalised.postValue(txData);
      }
  
      @Override
      public void transactionError(TransactionReturn rtn)
      {
          transactionError.postValue(rtn);
      }
  
      @Override
      public void transactionSigned(SignatureFromKey sigData, Web3Transaction w3Tx)
      {
          transactionSigned.postValue(new TransactionReturn(Numeric.toHexString(sigData.signature), w3Tx));
      }
  
      public void showMyAddress(Context ctx)
      {
          Intent intent = new Intent(ctx, MyAddressActivity.class);
          intent.putExtra(WALLET, defaultWallet.getValue());
          ctx.startActivity(intent);
      }
  
      public void onDestroy()
      {
          if (balanceTimerDisposable != null && !balanceTimerDisposable.isDisposed())
              balanceTimerDisposable.dispose();
          gasService.stopGasPriceCycle();
      }
  
      public void updateGasPrice(long chainId)
      {
          gasService.startGasPriceCycle(chainId);
      }
  
      public Realm getRealmInstance(Wallet wallet)
      {
          return tokensService.getRealmInstance(wallet);
      }
  
      public void startBalanceUpdate()
      {
          if (balanceTimerDisposable == null || balanceTimerDisposable.isDisposed())
          {
              balanceTimerDisposable = Observable.interval(0, BALANCE_CHECK_INTERVAL_SECONDS, TimeUnit.SECONDS)
                      .doOnNext(l -> checkBalance(defaultWallet.getValue())).subscribe();
          }
      }
  
      public void stopBalanceUpdate()
      {
          if (balanceTimerDisposable != null && !balanceTimerDisposable.isDisposed())
              balanceTimerDisposable.dispose();
          balanceTimerDisposable = null;
          gasService.stopGasPriceCycle();
      }
  
      public void handleWalletConnect(Context context, String url, NetworkInfo activeNetwork)
      {
          Intent intent = getIntentOfWalletConnectV2(context, url);
          context.startActivity(intent);
      }
  
      @NonNull
      private Intent getIntentOfWalletConnectV2(Context context, String url)
      {
          Intent intent = new Intent(context, WalletConnectV2Activity.class);
          intent.putExtra("url", url);
          return intent;
      }
  
      public TokensService getTokenService()
      {
          return tokensService;
      }
  
      public Single<GasEstimate> calculateGasEstimate(Wallet wallet, Web3Transaction transaction, long chainId)
      {
          if (transaction.isBaseTransfer())
          {
              return Single.fromCallable(() -> new GasEstimate(BigInteger.valueOf(C.GAS_LIMIT_MIN)));
          }
          else
          {
              return gasService.calculateGasEstimate(Numeric.hexStringToByteArray(transaction.payload), chainId,
                      transaction.recipient.toString(), transaction.value, wallet, transaction.gasLimit);
          }
      }
  
      public NetworkInfo getNetworkInfo(long chainId)
      {
          return ethereumNetworkRepository.getNetworkByChain(chainId);
      }
  
      public String getSessionId(String url)
      {
          String uriString = url.replace("wc:", "wc://");
          return Uri.parse(uriString).getUserInfo();
      }
  
      public boolean addCustomChain(WalletAddEthereumChainObject chainObject)
      {
          String rpc = extractRpc(chainObject);
          if (rpc == null) return false;
  
          this.ethereumNetworkRepository.saveCustomRPCNetwork(chainObject.chainName, rpc, chainObject.getChainId(),
                  chainObject.nativeCurrency.symbol, extractBlockExplorer(chainObject), "", false, -1L);
  
          tokensService.createBaseToken(chainObject.getChainId())
                  .subscribeOn(Schedulers.io())
                  .observeOn(Schedulers.io())
                  .subscribe(w -> {}, e -> {})
                  .isDisposed();
  
          return true;
      }
  
      private String extractBlockExplorer(WalletAddEthereumChainObject chainObject)
      {
          for (String thisRpc : chainObject.blockExplorerUrls)
          {
              if (isValidUrl(thisRpc)) //ensure RPC doesn't contain malicious code
              {
                  String retRpc = thisRpc;
                  if (thisRpc.endsWith("/tx"))
                  {
                      retRpc = thisRpc + "/";
                  }
                  else if (!thisRpc.endsWith("/tx/"))
                  {
                      if (!thisRpc.endsWith("/"))
                      {
                          retRpc = thisRpc + "/";
                      }
  
                      retRpc = retRpc + "tx/";
                  }
  
                  return retRpc;
              }
          }
  
          return "";
      }
  
      //NB Chain descriptions can contain WSS socket defs, which might come first.
      private String extractRpc(WalletAddEthereumChainObject chainObject)
      {
          for (String thisRpc : chainObject.rpcUrls)
          {
              if (isValidUrl(thisRpc)) //ensure RPC doesn't contain malicious code
              {
                  return thisRpc;
              }
          }
  
          return null;
      }
  
      public void addNetworkToFilters(NetworkInfo info)
      {
          List<Long> filters = ethereumNetworkRepository.getFilterNetworkList();
          if (!filters.contains(info.chainId))
          {
              filters.add(info.chainId);
              ethereumNetworkRepository.setFilterNetworkList(filters.toArray(new Long[0]));
          }
  
          tokensService.setupFilter(true);
      }
  
      public GasService getGasService()
      {
          return gasService;
      }
  
      public boolean getDeveloperOverrideState(Context context)
      {
          return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(DEVELOPER_OVERRIDE, false);
      }
  }
  