import BigNumber from 'bignumber.js';
import { getLatestABI } from '../../services/AbiService';
import Promisify from '../../utils/Promisify';
import { WithdrawService } from '../../services/WithdrawService';
import Mutex from '../../utils/MutexChecker';
import Services from '../../Services';
import { Derivative } from '../Derivative';
import { DerivativeStatus, DerivativeProviders } from '../../interfaces/Enums';

export class SellTokensOnCloseService extends Services {

  private derivativeFunctions: Derivative;
  private productAddress;

  constructor(productAddress: string) {
    super();
    this.productAddress = productAddress;
    this.derivativeFunctions = new Derivative(productAddress);
  }

  public async canSellTokensOnClose(): Promise<boolean> {
    const productContract = await this.rpcService.contract(await getLatestABI('FUND_ABI'), this.productAddress);

    const mutex = new Mutex(this.productAddress);
    const productStatusType = mutex.getProductStatusType();

    const mutexResult = await mutex.checkMutex(productStatusType.SELLING_TOKENS);

    if (!mutexResult.flag) {
      return false;
    }

    // Check whether or not the fund is closed. If it isn't, this function cannot be used yet.
    const status = (await Promisify<BigNumber>(async (cb) => productContract.status(cb))).toNumber();
    if (status !== DerivativeStatus.Closed) {
      return false;
    }

    const assetsValue = (await Promisify<BigNumber>
      (async (cb) => productContract.getAssetsValue(cb))).toNumber();
    // Even if assets value is 0, we need to make sure there is nothing pending to withdraw before close
    if (assetsValue === 0) {
      const withdrawAddress = await this.derivativeFunctions.getComponentAddress(DerivativeProviders.WITHDRAW);

      const needToWithdraw = await new WithdrawService(this.productAddress).
        needToWithdraw(this.productAddress, withdrawAddress);

      if (needToWithdraw) {
        return false;
      }
      // If we don't need to withdraw either, function never needs to be called again
      return false;
    }
    return true;
  }

  public async isForeverFinished() {
    const productContract = await this.rpcService.contract(await getLatestABI('FUND_ABI'), this.productAddress);
    const assetsValue = (await Promisify<BigNumber>
      (async (cb) => productContract.getAssetsValue(cb))).toNumber();

    if (assetsValue !== 0) {
      return false;
    } else {
      // Even if assets value is 0, we need to make sure there is nothing pending to withdraw before close
      const withdrawAddress = await this.derivativeFunctions.getComponentAddress(DerivativeProviders.WITHDRAW);

      const needToWithdraw = await new WithdrawService(this.productAddress).
        needToWithdraw(this.productAddress, withdrawAddress);

      if (needToWithdraw) {
        return false;
      }
      // If we don't need to withdraw either, function never needs to be called again
      return true;
    }
  }

  public async getSellTokensOnCloseTxData() {
    return (await this.rpcService.contract(await getLatestABI('FUND_ABI'), this.productAddress))
      .sellAllTokensOnClosedFund.getData();
  }

}
