import EmptyClass from '../utils/EmptyClass';
import { Derivative } from './Derivative';
import { getLatestABI } from '../services/AbiService';
import { IndexBuyTokenService } from './partials/IndexBuyTokens';
import { RebalanceService } from './partials/Rebalance';
import { SellTokensOnCloseService } from './partials/SellTokensOnClose';
import { DerivativeWithdrawService } from './partials/Withdraw';
import { updateDefaultOptions } from '../config';
import { ERC20DerivativeService } from './partials/ERC20Derivative';

const extend = <T, U>(first: T, second: U): T & U => {
  const result = {} as T & U;
  // tslint:disable-next-line:forin
  for (const id in first) {
    (result as any)[id] = (first as any)[id];
  }
  for (const id in second) {
    if (!result.hasOwnProperty(id)) {
      (result as any)[id] = (second as any)[id];
    }
  }
  return result;
};
// Not working returns | instead of & for the types
// const extendAll = <T>(arr: T[]) => {
//   let r = arr[0];
//   for (let i = 1; i < arr.length; i++) {
//     r = extend(r, arr[i]);
//   }
//   return r;
// };
export class DerivativeFactory {
  private static instance: DerivativeFactory;

  private constructor(web3Url: string, walletAddress: string, privateKey: string) {
    updateDefaultOptions({
      web3Url,
      privateKey,
      walletAddress,
    });
  }

  public static get(web3Url?: string, walletAddress?: string, privateKey?: string): DerivativeFactory {
    if (!this.instance) {
      if (!web3Url) {
        throw new Error('Web3URL is needed to interact with the blockchain in order to work with derivatives');
      }
      this.instance = new DerivativeFactory(web3Url, walletAddress, privateKey);
    }
    return this.instance;
  }

  public async getOlympusIndex(address: string) {
    return extend(
      extend(
        extend(
          extend(
            extend(
              new Derivative(address, await getLatestABI('INDEX_ABI')),
              new ERC20DerivativeService(address)),
            new IndexBuyTokenService(address)),
          new RebalanceService(address)),
        new SellTokensOnCloseService(address)),
      new DerivativeWithdrawService(address),
    );
  }

  public async getOlympusFund(address: string) {
    return extend(
      extend(
        extend(
          new Derivative(address, await getLatestABI('FUND_ABI')),
          new ERC20DerivativeService(address)),
        new SellTokensOnCloseService(address)),
      new DerivativeWithdrawService(address),
    );
  }

  // Implement when needed
  // public static async getBasicIndex(address: string) {
  //   return _.extend(
  //     new Derivative(address, await getLatestABI('INDEX_ABI')),
  //   );
  // }

  // public static async getBasicFund(address: string) {
  //   return _.extend(
  //     new Derivative(address, await getLatestABI('FUND_ABI')),
  //   );
  // }

  public async getOrExtendCustomDerivative<T>(customBuildingBlocks: T[]) {
    let full = extend(new EmptyClass(), customBuildingBlocks[0]);
    let i;
    for (i = 1; i < customBuildingBlocks.length; i++) {
      full = extend(full, customBuildingBlocks[i]);
    }
    return full;
  }
}
