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

import { INFTSafe, NFTStandard, PaymentToken } from './types';
import { NFTSafeAbi } from './abi/NFTSafeAbi';
import { bigNumberToWei, prepareBatch } from './utils';
import {
  ContractType,
  NetworkConfig,
  SupportedChainIds,
} from './networkConfig';

export class NFTSafe implements INFTSafe {
  readonly signer: Signer;
  protected contract: Contract;

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

  async lend(
    nftStandards: NFTStandard[],
    nftAddresses: string[],
    tokenIds: BigNumber[],
    lendAmounts: BigNumber[],
    maxRentDurations: number[],
    minRentDurations: number[],
    dailyRentPrices: string[],
    paymentOptions: PaymentToken[],
    collateralPrices: string[],
    allowedRenters: string[][][]
  ): 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)),
      lendAmounts: lendAmounts.map(amt => BigNumber.from(amt)),
      maxRentDurations: maxRentDurations.map(x => Number(x)),
      minRentDurations: minRentDurations.map(x => Number(x)),
      dailyRentPrices: dailyRentPrices.map(x =>
        bigNumberToWei(Number(x)).toString()
      ),
      paymentOptions,
      collateralPrices: collateralPrices.map(x =>
        bigNumberToWei(Number(x)).toString()
      ),
      allowedRenters: allowedRenters,
    });

    return await this.contract.lend(
      args.nftStandards,
      args.nftAddresses,
      args.tokenIds,
      args.lendAmounts,
      args.maxRentDurations,
      args.minRentDurations,
      args.dailyRentPrices,
      args.paymentOptions,
      args.collateralPrices,
      args.allowedRenters
    );
  }

  async rent(
    nftStandards: NFTStandard[],
    nftAddresses: string[],
    tokenIds: BigNumber[],
    lendingIds: BigNumber[],
    rentDurations: number[],
    rentAmounts: 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)),
      rentDurations: rentDurations.map(x => Number(x)),
      rentAmounts: rentAmounts.map(amount => BigNumber.from(amount)),
    });

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

  async stopRenting(
    nftStandards: NFTStandard[],
    nftAddresses: string[],
    tokenIds: BigNumber[],
    lendingIds: BigNumber[],
    rentingIds: 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)),
      rentingIds: rentingIds.map(id => BigNumber.from(id)),
    });

    return await this.contract.stopRenting(
      args.nftStandards,
      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 claimRentOrCollateral(
    nftStandards: NFTStandard[],
    nftAddresses: string[],
    tokenIds: BigNumber[],
    lendingIds: BigNumber[],
    rentingIds: 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)),
      rentingIds: rentingIds.map(id => BigNumber.from(id)),
    });

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