package cy.agorise.bitsybitshareswallet.repositories import android.content.Context import android.content.SharedPreferences import android.os.AsyncTask import android.preference.PreferenceManager import android.util.Log import androidx.lifecycle.LiveData import cy.agorise.bitsybitshareswallet.database.BitsyDatabase import cy.agorise.bitsybitshareswallet.database.daos.EquivalentValueDao import cy.agorise.bitsybitshareswallet.database.daos.TransferDao import cy.agorise.bitsybitshareswallet.database.entities.EquivalentValue import cy.agorise.bitsybitshareswallet.database.entities.Transfer import cy.agorise.bitsybitshareswallet.network.CoingeckoService import cy.agorise.bitsybitshareswallet.network.ServiceGenerator import cy.agorise.bitsybitshareswallet.utils.Constants import io.reactivex.Single import io.reactivex.disposables.CompositeDisposable import java.text.SimpleDateFormat import java.util.* class TransferRepository internal constructor(context: Context) { companion object { private const val TAG = "TransferRepository" } private val mTransferDao: TransferDao private val mEquivalentValuesDao: EquivalentValueDao private val compositeDisposable = CompositeDisposable() private val mPreferences: SharedPreferences init { val db = BitsyDatabase.getDatabase(context) mTransferDao = db!!.transferDao() mEquivalentValuesDao = db.equivalentValueDao() mPreferences = PreferenceManager.getDefaultSharedPreferences(context) } fun insertAll(transfers: List) { insertAllAsyncTask(mTransferDao).execute(transfers) } fun update(transfer: Transfer){ mTransferDao.insert(transfer) } fun setBlockTime(blockNumber: Long, timestamp: Long) { setBlockTimeAsyncTask(mTransferDao).execute(Pair(blockNumber, timestamp)) } fun getAll(): LiveData> { return mTransferDao.getAll() } fun getCount(): Single { return mTransferDao.getCount() } fun getTransferBlockNumberWithMissingTime(): LiveData { return mTransferDao.getTransferBlockNumberWithMissingTime() } fun getTransfersWithMissingBtsValue(): LiveData { return mTransferDao.getTransfersWithMissingBtsValue() } fun deleteAll() { deleteAllAsyncTask(mTransferDao).execute() } /** * Creates a subscription to the transfers table which will listen & process equivalent values. * * This function will create a subscription that will listen for missing equivalent values. This will * automatically trigger a procedure designed to calculate the fiat equivalent value of any entry * of the 'transactions' table that stil doesn't have a corresponding entry in the 'equivalent_values' * table for that specific fiat currency. * * @param symbol The 3 letters symbol of the desired fiat currency. */ fun observeMissingEquivalentValuesIn(symbol: String) { compositeDisposable.add(mTransferDao.getTransfersWithMissingValueIn(symbol) .map { transfer -> obtainFiatValue(transfer, symbol) } .subscribe({ if(it.value >= 0) mEquivalentValuesDao.insert(it) },{ Log.e(TAG,"Error while trying to create a new equivalent value. Msg: ${it.message}") for(element in it.stackTrace){ Log.e(TAG,"${element.className}#${element.methodName}:${element.lineNumber}") } }) ) } /** * Creates an equivalent value for a given transaction. * * Function used to perform a request to the Coingecko's price API trying to obtain the * equivalent value of a specific [Transfer]. * * @param transfer The transfer whose equivalent value we want to obtain * @param symbol The symbol of the fiat that the equivalent value should be calculated in * @return An instance of the [EquivalentValue] class, ready to be inserted into the database. */ private fun obtainFiatValue(transfer: Transfer, symbol: String): EquivalentValue { val sg = ServiceGenerator(Constants.COINGECKO_URL) val dateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.ROOT) val date = Date(transfer.timestamp * 1000) val response = sg.getService(CoingeckoService::class.java) ?.getHistoricalValueSync("bitshares", dateFormat.format(date), false) ?.execute() var equivalentFiatValue = -1L if(response?.isSuccessful == true){ val price: Double = response.body()?.market_data?.current_price?.get(symbol.toLowerCase()) ?: -1.0 if(price > 0){ // The equivalent value is obtained by: // 1- Dividing the base value by 100000 (BTS native precision) // 2- Multiplying that BTS value by the unit price in the chosen fiat // 3- Multiplying the resulting value by 100 in order to express it in cents equivalentFiatValue = Math.round(transfer.btsValue?.toFloat()?.div(1e5)?.times(price)?.times(100) ?: -1.0) } }else{ Log.w(TAG,"Request was not successful. code: ${response?.code()}") } return EquivalentValue(transfer.id, equivalentFiatValue, symbol) } private class insertAllAsyncTask internal constructor(private val mAsyncTaskDao: TransferDao) : AsyncTask, Void, Void>() { override fun doInBackground(vararg transfers: List): Void? { mAsyncTaskDao.insertAll(transfers[0]) return null } } private class setBlockTimeAsyncTask internal constructor(private val mAsyncTaskDao: TransferDao) : AsyncTask, Void, Void>() { override fun doInBackground(vararg pair: Pair): Void? { mAsyncTaskDao.setBlockTime(pair[0].first, pair[0].second) return null } } private class deleteAllAsyncTask internal constructor(private val mAsyncTaskDao: TransferDao) : AsyncTask() { override fun doInBackground(vararg params: Void?): Void? { mAsyncTaskDao.deleteAll() return null } } /** * Called whenever the disposables have to be cleared. * * Since this repository manages a subscription it is necessary to clear the disposable after we're done with it. * The parent ViewModel will let us know when that subscription is no longer necessary and the resources can * be cleared. */ fun onCleared() { if(!compositeDisposable.isDisposed) compositeDisposable.clear() } }