package com.example.eth_token_transactions;

import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import com.samsung.android.sdk.blockchain.CoinType;
import com.samsung.android.sdk.blockchain.ListenableFutureTask;
import com.samsung.android.sdk.blockchain.SBlockchain;
import com.samsung.android.sdk.blockchain.account.Account;
import com.samsung.android.sdk.blockchain.account.AccountManager;
import com.samsung.android.sdk.blockchain.account.ethereum.EthereumAccount;
import com.samsung.android.sdk.blockchain.coinservice.CoinNetworkInfo;
import com.samsung.android.sdk.blockchain.coinservice.CoinService;
import com.samsung.android.sdk.blockchain.coinservice.CoinServiceFactory;
import com.samsung.android.sdk.blockchain.coinservice.FeeInfo;
import com.samsung.android.sdk.blockchain.coinservice.TransactionResult;
import com.samsung.android.sdk.blockchain.coinservice.ethereum.Eip1559FeeInfo;
import com.samsung.android.sdk.blockchain.coinservice.ethereum.EthereumService;
import com.samsung.android.sdk.blockchain.coinservice.ethereum.EthereumTransactionType;
import com.samsung.android.sdk.blockchain.coinservice.ethereum.EthereumUtils;
import com.samsung.android.sdk.blockchain.exception.AvailabilityException;
import com.samsung.android.sdk.blockchain.exception.SsdkUnsupportedException;
import com.samsung.android.sdk.blockchain.network.EthereumNetworkType;
import com.samsung.android.sdk.blockchain.network.NetworkType;
import com.samsung.android.sdk.blockchain.wallet.HardwareWallet;
import com.samsung.android.sdk.blockchain.wallet.HardwareWalletManager;
import com.samsung.android.sdk.blockchain.wallet.HardwareWalletType;
import org.jetbrains.annotations.NotNull;
import org.web3j.abi.FunctionEncoder;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.Address;
import org.web3j.abi.datatypes.Bool;
import org.web3j.abi.datatypes.Function;
import org.web3j.abi.datatypes.Type;
import org.web3j.abi.datatypes.Uint;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;

public class MainActivity extends AppCompatActivity {

    private SBlockchain mSblockchain;
    private HardwareWallet mHardwareWallet;
    private CoinNetworkInfo mCoinNetworkInfo;
    private CoinService mCoinService;
    private EthereumService ethereumService;
    private BigInteger mGasPrice;
    private BigInteger mGasLimit;
    private Eip1559FeeInfo mEthereumFeeInfo;
    private List<Account> accountList;
    private Account mFirstAccount;
    private EthereumAccount mTokenAccount;
    private static final String LOG_TAG = "TestApp";
    private List <String> tokenAddressList = new ArrayList<String>();
    private String addedTokenAddress;

    private Button connectButton;
    private Button createAccountButton;
    private Button addTokenButton;
    private Button gasPriceButton;
    private Button gasLimitButton;
    private Button sendButton;
    private Button getTokenBalanceButton;

    private RadioGroup transactionSpeedGroup;
    private TextView gasLimitText;
    private TextView tokenBalanceText;
    private EditText toAddressEditText;
    private EditText sendAmountEditText;
    private EditText tokenAddressEditText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        transactionSpeedGroup = findViewById(R.id.transaction_speed_radio_group);

        gasLimitText = findViewById(R.id.gas_limit_text_view);
        toAddressEditText = findViewById(R.id.to_address);
        tokenAddressEditText = findViewById(R.id.token_address);
        sendAmountEditText = findViewById(R.id.send_amount);
        tokenBalanceText = findViewById(R.id.balance_token);

        getTokenBalanceButton = findViewById(R.id.get_token_balance);
        connectButton = findViewById(R.id.connect_button);
        createAccountButton = findViewById(R.id.create_account);
        addTokenButton = findViewById(R.id.add_token_button);
        gasPriceButton = findViewById(R.id.gas_price_button);
        gasLimitButton = findViewById(R.id.gas_limit_button);
        sendButton = findViewById(R.id.send_button);
    }

    /***************************************
    ************ Object Creation ***********
    ***************************************/

    public void OnClickObjectCreate(View view) {

        try {
            mSblockchain = new SBlockchain();
            mSblockchain.initialize(getApplicationContext());
            connectButton.setEnabled(true);
            Log.i(LOG_TAG, "Object Created.");
            Toast.makeText(getApplicationContext(), "Object has been created.", Toast.LENGTH_SHORT).show();
        } catch (SsdkUnsupportedException e) {
            switch (e.getErrorType()) {
                case SsdkUnsupportedException.DEVICE_NOT_SUPPORTED:
                    Log.e(LOG_TAG, "Device is not Supported");
                    break;
                case SsdkUnsupportedException.LIBRARY_NOT_INSTALLED:
                    Log.e(LOG_TAG, "Platform SDK Library is not Installed");
                    break;
                case SsdkUnsupportedException.LIBRARY_UPDATE_IS_REQUIRED:
                    Log.e(LOG_TAG, "Platform SDK Library Update is Needed");
                    break;
                case SsdkUnsupportedException.LIBRARY_UPDATE_IS_RECOMMENDED:
                    Log.e(LOG_TAG, "Platform SDK Library Update is Recommended");
                    break;
                case SsdkUnsupportedException.VENDOR_NOT_SUPPORTED:
                    Log.e(LOG_TAG, "Platform SDK is not supported on Non-Samsung Devices");
                    break;
                default:
                    Log.e(LOG_TAG, "Unknown Platform SDK Unsupported Error");
            }
        }
    }

    /***************************************
     ***** Connect to Hardware Wallet ******
     ***************************************/

    public void OnClickHardwareWalletConnect(View view) {

        // Connect to Hardware Wallet
        HardwareWalletType mHardwareWalletType = HardwareWalletType.SAMSUNG;
        HardwareWalletManager mHardwareWalletManager = mSblockchain.getHardwareWalletManager();

        mHardwareWalletManager.connect(mHardwareWalletType, true).setCallback(new ListenableFutureTask.Callback<HardwareWallet>() {
            @Override
            public void onSuccess(HardwareWallet hardwareWallet) {
                mHardwareWallet = hardwareWallet;
                Log.i(LOG_TAG, "Wallet is connected successfully.");
                Log.i(LOG_TAG, "Object Created." + mSblockchain.getHardwareWalletManager().getConnectedHardwareWallet().getWalletId());
                runOnUiThread(() -> {
                            createAccountButton.setEnabled(true);
                            Toast.makeText(getApplicationContext(), "Wallet ID: " + hardwareWallet.getWalletId(), Toast.LENGTH_SHORT).show();
                        }
                );
            }

            @Override
            public void onFailure(@NotNull ExecutionException e) {
                Log.e(LOG_TAG, "Wallet connection failed.");
            }

            @Override
            public void onCancelled(@NotNull InterruptedException e) {
                Log.e(LOG_TAG, "Wallet connection canceled.");
            }
        });
    }

    /***************************************
     *************** Account ***************
     ***************************************/

    public void OnClickAccountCreate(View view) {
        AccountManager mAccountManager = mSblockchain.getAccountManager();
        CoinType coinType = CoinType.ETH;
        NetworkType networkType = EthereumNetworkType.GOERLI;
        String rpc = "https://goerli.infura.io/v3/26ac6a90f77a49cb8967c43524729536";

        mCoinNetworkInfo = new CoinNetworkInfo(coinType, networkType, rpc);
        mCoinService = CoinServiceFactory.getCoinService(getApplicationContext(), mCoinNetworkInfo);
        ethereumService = (EthereumService) mCoinService;

        //Check empty account list.
        getAccount();

        if (accountList.isEmpty()) {

            // Restore Account
            // Restore first
            // If keystore has already an unused account, generating new account will be failed.
            // As new account can not be generated unless all accounts are used at least once.
            mAccountManager.restoreAccounts(mHardwareWallet, true, mCoinNetworkInfo, "m/44'/60'/0'/0").setCallback(new ListenableFutureTask.Callback<Boolean>() {
                @Override
                public void onSuccess(Boolean aBoolean) {
                    if (accountList.isEmpty()) {
                        // After restoring if no account found.
                        // Then generate a new account.
                        accountGenerate(mAccountManager);
                    } else{
                        Log.i(LOG_TAG, "Account is restored.");
                        runOnUiThread(() ->
                                getAccount()
                        );
                    }
                }

                @Override
                public void onFailure(@NotNull ExecutionException e) {
                    Log.e(LOG_TAG, "Restore account has failed.");
                    Log.e(LOG_TAG, "" + e.getMessage());
                }

                @Override
                public void onCancelled(@NotNull InterruptedException e) {
                    Log.e(LOG_TAG, "Restore account has been canceled.");
                }
            });
        }
    }

    protected void accountGenerate(AccountManager mAccountManager) {
        // Create New Account
        mAccountManager.generateNewAccount(mHardwareWallet, mCoinNetworkInfo).setCallback(new ListenableFutureTask.Callback<Account>() {
            @Override
            public void onSuccess(Account account) {
                Log.i(LOG_TAG, "Account is created.");
                Log.i(LOG_TAG, "Account: " + account.getAddress());
                runOnUiThread(() ->
                        getAccount()
                );
            }

            @Override
            public void onFailure(@NotNull ExecutionException e) {
                Log.e(LOG_TAG, "Account creation has failed.");
                Log.e(LOG_TAG, "" + e.getMessage());
            }

            @Override
            public void onCancelled(@NotNull InterruptedException e) {
                Log.e(LOG_TAG, "Account creation has been canceled.");
            }
        });
    }


    private void getAccount() {
        accountList = mSblockchain.getAccountManager()
                .getAccounts(mHardwareWallet.getWalletId(), mCoinNetworkInfo.getCoinType(), mCoinNetworkInfo.getNetworkType());

        if (accountList.size() != 0) {
            mFirstAccount = accountList.get(0);
            TextView accountTextView = findViewById(R.id.show_account);
            runOnUiThread(()->{
                accountTextView.setText(mFirstAccount.getAddress());
            });
            Log.d(LOG_TAG, "First account is: " + mFirstAccount.getAddress());
            addTokenButton.setEnabled(true);
        }
    }

    /***************************************
     ********** Add Token Account **********
     ***************************************/

    private boolean checkExistingTokenAccount(){
        for (Account currentAccount : accountList) {
            if (currentAccount.getAddress().equals(mFirstAccount.getAddress())) {
                if (addedTokenAddress.equals(((EthereumAccount) currentAccount).getTokenAddress())) {
                    return true;
                }
            }
        }
        return false;
    }

    public void onClickAddTokenAccount(View view){
        if (TextUtils.isEmpty(tokenAddressEditText.getText().toString()))
        {
            Toast.makeText(MainActivity.this, "Please add a token address", Toast.LENGTH_SHORT).show();
            return;
        }
        addedTokenAddress = tokenAddressEditText.getText().toString();
        boolean check = checkExistingTokenAccount();
        if(check){
            Toast.makeText(MainActivity.this, "Token already added", Toast.LENGTH_SHORT).show();
            getTokenBalanceButton.setEnabled(true);
            return;
        }
        ethereumService.addTokenAddress((EthereumAccount) mFirstAccount, addedTokenAddress).setCallback(new ListenableFutureTask.Callback<EthereumAccount>() {
            @Override
            public void onSuccess(final EthereumAccount account) {
                Log.d(LOG_TAG, "onSuccess: addTokenAccount " + account);
                runOnUiThread(()->{
                    getTokenBalanceButton.setEnabled(true);
                });
                getAccount();
            }

            @Override
            public void onFailure(
                    @NonNull final ExecutionException exception) {
                Log.e(LOG_TAG, "onFailure: addTokenAccount " + exception);
            }

            @Override
            public void onCancelled(@NotNull InterruptedException exception) {
                Log.e(LOG_TAG, "onFailure: addTokenAccount " + exception);
            }
        });

    }

    /***************************************
     ********** Get Token Balance **********
     ***************************************/

    private EthereumAccount getTokenAccount(){
        EthereumAccount ethereumAccount = null;
        for (Account currentAccount : accountList) {
            if (currentAccount.getAddress().equals(mFirstAccount.getAddress())) {
                if (addedTokenAddress.equals(((EthereumAccount) currentAccount).getTokenAddress())) {
                    ethereumAccount = (EthereumAccount) currentAccount;
                }
            }
        }
        return ethereumAccount;
    }

    public void onClickGetTokenbalance(View view){
        mTokenAccount = getTokenAccount();
        if(mTokenAccount == null){
            Log.e(LOG_TAG, "Token Account is null");
        }
        ethereumService.getTokenBalance(mTokenAccount).setCallback(new ListenableFutureTask.Callback<BigInteger>() {
            @Override
            public void onSuccess(BigInteger tokenBalance) {
                runOnUiThread(()->{
                    tokenBalanceText.setEnabled(true);
                    gasPriceButton.setEnabled(true);
                    tokenBalanceText.setText(tokenBalance.toString());
                });
            }

            @Override
            public void onFailure(@NonNull ExecutionException e) {
                Log.e(LOG_TAG, "Fetching balance is failed.");
                Log.e(LOG_TAG, "" + e.getMessage());
            }

            @Override
            public void onCancelled(@NonNull InterruptedException e) {
                Log.e(LOG_TAG, "Fetching balance is canceled.");
            }
        });
    }


    /***************************************
     ************ Get Gas Price ************
     ***************************************/


    public void OnClickGasPrice(View view) {

        ethereumService.getFeeInfo(EthereumTransactionType.EIP1559).setCallback(new ListenableFutureTask.Callback<FeeInfo>() {
            @Override
            public void onSuccess(FeeInfo feeInfo) {
                mEthereumFeeInfo = (Eip1559FeeInfo) feeInfo;;
                Log.i(LOG_TAG, "Fee info is fetched successfully.");
                runOnUiThread(() -> {
                            gasLimitButton.setEnabled(true);
                            Toast.makeText(getApplicationContext(), "Fee info is fetched successfully.", Toast.LENGTH_SHORT).show();
                        }
                );
            }

            @Override
            public void onFailure(@NotNull ExecutionException e) {
                Log.e(LOG_TAG, "Fetching Fee info is failed.");
                Log.e(LOG_TAG, "" + e.getMessage());
            }

            @Override
            public void onCancelled(@NotNull InterruptedException e) {
                Log.e(LOG_TAG, "Fetching Fee info is canceled.");
            }
        });
    }

    /***************************************
     ************ Get Gas Limit ************
     ***************************************/


    public void onClickGasLimit(View view) {

        String toAddress = toAddressEditText.getText().toString();
        String amount = sendAmountEditText.getText().toString();

        if (toAddress.isEmpty() || amount.isEmpty()) {
            Toast.makeText(getApplicationContext(), "Fill Send Address and Amount Field", Toast.LENGTH_SHORT).show();
        } else if(!ethereumService.isValidAddress(toAddress)){
            Toast.makeText(getApplicationContext(), "Invalid Address.", Toast.LENGTH_SHORT).show();
        } else {
            BigDecimal sendAmount = new BigDecimal(amount);
            BigInteger convertedSendAmount = EthereumUtils.convertEthToWei(sendAmount);

            List<Type> inParams = Arrays.asList(new Address(toAddress), new Uint(convertedSendAmount));
            List outParams = Arrays.asList(new TypeReference<Bool>() {});
            String encodedFunction = FunctionEncoder.encode(new Function("transfer", inParams, outParams));

            ethereumService.estimateGasLimit((EthereumAccount) mFirstAccount, toAddress, null, encodedFunction).setCallback(new ListenableFutureTask.Callback<BigInteger>() {
                @Override
                public void onSuccess(BigInteger bigInteger) {
                    mGasLimit = bigInteger;
                    Log.i(LOG_TAG, "Gas limit is fetched successfully.");
                    Log.i(LOG_TAG, "Gas limit is:" + bigInteger.toString());
                    runOnUiThread(() -> {
                        sendButton.setEnabled(true);
                        gasLimitText.setText(bigInteger.toString());
                    });
                }

                @Override
                public void onFailure(@NotNull ExecutionException e) {
                    Log.e(LOG_TAG, "Fetching Gas limit has failed.");
                    Log.e(LOG_TAG, "" + e.getMessage());
                }

                @Override
                public void onCancelled(@NotNull InterruptedException e) {
                    Log.e(LOG_TAG, "Fetching Gas limit has been canceled.");
                }
            });
        }

    }

    /***************************************
     ********** Transfer Balance ***********
     ***************************************/

    public void onClickSendToken(View view) {

        if (TextUtils.isEmpty(toAddressEditText.getText().toString()))
        {
            Toast.makeText(MainActivity.this, "to Address field must be filled!", Toast.LENGTH_SHORT).show();
            return;
        }

        if (TextUtils.isEmpty(sendAmountEditText.getText().toString()))
        {
            Toast.makeText(MainActivity.this, "send amount field must be filled!", Toast.LENGTH_SHORT).show();
            return;
        }

        String toAddress = toAddressEditText.getText().toString();
        BigInteger sendAmount = new BigInteger(sendAmountEditText.getText().toString());

        //Getting Gas Price
        int transactionSpeedID = transactionSpeedGroup.getCheckedRadioButtonId();
        if (transactionSpeedID == -1) {
            Toast.makeText(getApplicationContext(), "Select a Transaction Speed.", Toast.LENGTH_SHORT).show();
        } else {
            switch (transactionSpeedID) {
                case R.id.transaction_speed_slow:
                    mGasPrice = mEthereumFeeInfo.getSlowPriorityFee();
                    Log.d(LOG_TAG, "GasPrice: " + mGasPrice);
                    break;
                case R.id.transaction_speed_normal:
                    mGasPrice = mEthereumFeeInfo.getNormalPriorityFee();
                    Log.d(LOG_TAG, "GasPrice: " + mGasPrice);
                    break;
                case R.id.transaction_speed_fast:
                    mGasPrice = mEthereumFeeInfo.getFastPriorityFee();
                    Log.d(LOG_TAG, "GasPrice: " + mGasPrice);
                    break;
            }
        }

        if(transactionSpeedID != -1) {
            try {
                ethereumService.sendTokenTransaction(
                        mHardwareWallet,
                        (EthereumAccount) mTokenAccount,
                        toAddress,
                        addedTokenAddress,
                        mGasPrice,
                        mEthereumFeeInfo.getEstimatedBaseFee().add(mGasPrice),
                        mGasLimit,
                        sendAmount,
                        null
                    ).setCallback(new ListenableFutureTask.Callback<TransactionResult>() {
                        @Override
                        public void onSuccess(TransactionResult transactionResult) {
                            Log.d(LOG_TAG, "Transaction Hash: " + transactionResult.getHash());
                            runOnUiThread(() ->
                                    Toast.makeText(getApplicationContext(), "Transaction Hash: " + transactionResult.getHash(), Toast.LENGTH_SHORT).show()
                            );
                        }

                        @Override
                        public void onFailure(@NotNull ExecutionException e) {
                            Log.e(LOG_TAG, "Transaction failed.");
                            Log.e(LOG_TAG, "" + e.getMessage());
                        }

                        @Override
                        public void onCancelled(@NotNull InterruptedException e) {
                            Log.e(LOG_TAG, "Transaction canceled.");
                        }
                    });
            } catch (AvailabilityException e) {
                Log.e(LOG_TAG, "Error in sending: " + e);
            }
        }

    }
}