import { Contract, BigNumber, ContractTransaction, Signer } from 'ethers';

import { RevenueSharedNFTRentalsAbi } from './abi/RevenueSharedNFTRentalsAbi';
import { NetworkConfig, SupportedChainIds } from './networkConfig';
import { IRevenueSharedNFTRentals, NFTStandard } from './types';
import { prepareBatch, prepareRevenueShareBatch } from './utils';

export class RevenueSharedNFTRentals implements IRevenueSharedNFTRentals {
  readonly signer: Signer;
  protected contract: Contract;

  constructor(_signer: Signer, chainId: SupportedChainIds) {
    this.signer = _signer;
    var _address = undefined;
    if (
      NetworkConfig[chainId] === undefined ||
      NetworkConfig[chainId].revenueSharedNFTRentalsContractAddresses ===
        undefined ||
      NetworkConfig[chainId].revenueSharedNFTRentalsContractAddresses.length <=
        0
    ) {
      throw new Error('Requested for unsupported chain');
    } else {
      _address =
        NetworkConfig[chainId].revenueSharedNFTRentalsContractAddresses[0];
      this.contract = new Contract(
        _address,
        RevenueSharedNFTRentalsAbi,
        this.signer
      );
    }
  }

  async lend(
    nftStandards: NFTStandard[],
    nftAddresses: string[],
    tokenIds: BigNumber[],
    lendAmounts: BigNumber[],
    upfrontFees: BigNumber[],
    maxRentDurations: number[],
    revenueShareInfos: [string[], number[]][],
    allowedRenters: string[][][],
    paymentOptions: number[]
  ): Promise<ContractTransaction> {
    const args = prepareRevenueShareBatch({
      nftStandards: nftStandards.map(nft => Number(nft)),
      nftAddresses: nftAddresses.map(nft => String(nft).toLowerCase()),
      tokenIds: tokenIds.map(id => BigNumber.from(id)),
      lendAmounts: lendAmounts.map(amt => BigNumber.from(amt)),
      upfrontFees: upfrontFees.map(value => BigNumber.from(value)),
      maxRentDurations: maxRentDurations.map(value => Number(value)),
      revenueShareInfos,
      allowedRenters,
      paymentOptions,
    });

    const tupleFomattedData = args.nftAddresses.map((address, index) => [
      args.nftStandards?.[index],
      address,
      args.tokenIds[index],
      args.lendAmounts?.[index],
      args.upfrontFees?.[index],
      args.maxRentDurations?.[index],
      args.revenueShareInfos?.[index],
      args.allowedRenters?.[index],
      args.paymentOptions?.[index],
    ]);
    return await this.contract.lend(tupleFomattedData);
  }

  async rent(
    nftAddresses: string[],
    tokenIds: BigNumber[],
    lendingIds: BigNumber[],
    rentDurations: number[],
    rentAmounts: BigNumber[]
  ): Promise<ContractTransaction> {
    const args = prepareRevenueShareBatch({
      nftAddresses: nftAddresses.map(nft => String(nft).toLowerCase()),
      tokenIds: tokenIds.map(id => BigNumber.from(id)),
      lendingIds: lendingIds.map(id => BigNumber.from(id)),
      rentDurations: rentDurations.map(value => Number(value)),
      rentAmounts: rentAmounts.map(amount => BigNumber.from(amount)),
    });

    return await this.contract.rent(
      args.nftAddresses,
      args.tokenIds,
      args.lendingIds,
      args.rentDurations,
      args.rentAmounts
    );
  }

  async stopRenting(
    nftAddresses: string[],
    tokenIds: BigNumber[],
    lendingIds: BigNumber[],
    rentingIds: BigNumber[]
  ): Promise<ContractTransaction> {
    const args = prepareRevenueShareBatch({
      nftAddresses: nftAddresses.map(nft => String(nft).toLowerCase()),
      tokenIds: tokenIds.map(id => BigNumber.from(id)),
      lendingIds: lendingIds.map(id => BigNumber.from(id)),
      rentingIds: rentingIds.map(id => BigNumber.from(id)),
    });

    return await this.contract.stopRenting(
      args.nftAddresses,
      args.tokenIds,
      args.lendingIds,
      args.rentingIds
    );
  }

  async stopLending(
    nftStandards: NFTStandard[],
    nftAddresses: string[],
    tokenIds: BigNumber[],
    lendingIds: BigNumber[]
  ): Promise<ContractTransaction> {
    const args = prepareBatch({
      nftStandards: nftStandards.map(nft => Number(nft)),
      nftAddresses: nftAddresses.map(nft => String(nft).toLowerCase()),
      tokenIds: tokenIds.map(id => BigNumber.from(id)),
      lendingIds: lendingIds.map(id => BigNumber.from(id)),
    });

    return await this.contract.stopLending(
      args.nftStandards,
      args.nftAddresses,
      args.tokenIds,
      args.lendingIds
    );
  }

  async shareRevenue(
    nftAddresses: string[],
    tokenIds: BigNumber[],
    lendingIds: BigNumber[],
    revenueAmounts: BigNumber[],
    renters: string[],
    revenueTokenAddress: string[]
  ): Promise<ContractTransaction> {
    const args = prepareRevenueShareBatch({
      nftAddresses: nftAddresses.map(nft => String(nft).toLowerCase()),
      tokenIds: tokenIds.map(id => BigNumber.from(id)),
      lendingIds: lendingIds.map(id => BigNumber.from(id)),
      revenueAmounts: revenueAmounts.map(amount => BigNumber.from(amount)),
      renters: renters.map(address => String(address).toLowerCase()),
      revenueTokenAddress: revenueTokenAddress.map(address =>
        String(address).toLowerCase()
      ),
    });

    return await this.contract.shareRevenue(
      args.nftAddresses,
      args.tokenIds,
      args.lendingIds,
      args.revenueAmounts,
      args.renters,
      args.revenueTokenAddress
    );
  }
}
