var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import { Contract } from "ethers";
import IonicFlywheelABI from "../../abis/IonicFlywheel";
import FlywheelStaticRewardsArtifact from "../../artifacts/FlywheelStaticRewards.json";
import IonicFlywheelArtifact from "../../artifacts/IonicFlywheel.json";
export function withFlywheel(Base) {
    return class Flywheel extends Base {
        /** READ */
        getFlywheelMarketRewardsByPools(pools) {
            return __awaiter(this, void 0, void 0, function* () {
                return Promise.all(pools.map((pool) => this.getFlywheelMarketRewardsByPool(pool)));
            });
        }
        getFlywheelMarketRewardsByPool(pool) {
            return __awaiter(this, void 0, void 0, function* () {
                const [flywheelsOfPool, marketsOfPool] = yield Promise.all([
                    this.getFlywheelsByPool(pool),
                    this.createComptroller(pool, this.provider).callStatic.getAllMarkets()
                ]);
                const strategiesOfFlywheels = yield Promise.all(flywheelsOfPool.map((fw) => fw.callStatic.getAllStrategies()));
                const rewardTokens = [];
                const marketRewardsInfo = yield Promise.all(marketsOfPool.map((market) => __awaiter(this, void 0, void 0, function* () {
                    const rewardsInfo = yield Promise.all(flywheelsOfPool
                        // Make sure this market is active in this flywheel
                        .filter((_, fwIndex) => strategiesOfFlywheels[fwIndex].includes(market))
                        // TODO also check marketState?
                        .map((fw) => __awaiter(this, void 0, void 0, function* () {
                        const rewardToken = yield fw.callStatic.rewardToken();
                        rewardTokens.push(rewardToken);
                        return {
                            rewardToken,
                            flywheel: fw.address
                        };
                    })));
                    return {
                        market,
                        rewardsInfo
                    };
                })));
                return marketRewardsInfo;
            });
        }
        getFlywheelsByPool(poolAddress) {
            return __awaiter(this, void 0, void 0, function* () {
                const pool = this.createComptroller(poolAddress, this.provider);
                const allRewardDistributors = yield pool.callStatic.getRewardsDistributors();
                const instances = allRewardDistributors.map((address) => {
                    return new Contract(address, IonicFlywheelABI, this.provider);
                });
                const filterList = yield Promise.all(instances.map((instance) => __awaiter(this, void 0, void 0, function* () {
                    try {
                        return yield instance.callStatic.isFlywheel();
                    }
                    catch (error) {
                        return false;
                    }
                })));
                return instances.filter((_, index) => filterList[index]);
            });
        }
        getFlywheelRewardsInfos(flywheelAddress) {
            return __awaiter(this, void 0, void 0, function* () {
                const flywheelCoreInstance = new Contract(flywheelAddress, IonicFlywheelArtifact.abi, this.provider);
                const [fwStaticAddress, enabledMarkets] = yield Promise.all([
                    flywheelCoreInstance.callStatic.flywheelRewards(),
                    flywheelCoreInstance.callStatic.getAllStrategies()
                ]);
                const fwStatic = new Contract(fwStaticAddress, FlywheelStaticRewardsArtifact.abi, this.provider);
                const rewardsInfos = {};
                yield Promise.all(enabledMarkets.map((m) => __awaiter(this, void 0, void 0, function* () {
                    rewardsInfos[m] = yield fwStatic.callStatic.rewardsInfo(m);
                })));
                return rewardsInfos;
            });
        }
        getFlywheelMarketRewardsByPoolWithAPR(pool) {
            return __awaiter(this, void 0, void 0, function* () {
                const marketRewards = yield this.contracts.IonicFlywheelLensRouter.callStatic.getPoolMarketRewardsInfo(pool);
                const adaptedMarketRewards = marketRewards
                    .map((marketReward) => ({
                    underlyingPrice: marketReward.underlyingPrice,
                    market: marketReward.market,
                    rewardsInfo: marketReward.rewardsInfo.filter((info) => info.rewardSpeedPerSecondPerToken.gt(0))
                }))
                    .filter((marketReward) => marketReward.rewardsInfo.length > 0);
                return adaptedMarketRewards;
            });
        }
        getFlywheelRewardsInfoForMarket(flywheelAddress, marketAddress) {
            return __awaiter(this, void 0, void 0, function* () {
                const fwCoreInstance = this.createIonicFlywheel(flywheelAddress, this.provider);
                const fwRewardsAddress = yield fwCoreInstance.callStatic.flywheelRewards();
                const fwRewardsInstance = this.createFlywheelStaticRewards(fwRewardsAddress, this.provider);
                const [marketState, rewardsInfo] = yield Promise.all([
                    fwCoreInstance.callStatic.marketState(marketAddress),
                    fwRewardsInstance.callStatic.rewardsInfo(marketAddress)
                ]);
                return Object.assign({ enabled: marketState[1] > 0 }, rewardsInfo);
            });
        }
        getFlywheelClaimableRewardsForMarket(poolAddress, market, account) {
            return __awaiter(this, void 0, void 0, function* () {
                const pool = this.createComptroller(poolAddress, this.provider);
                const rewardDistributors = yield pool.callStatic.getRewardsDistributors();
                const fwLensRouter = this.createIonicFlywheelLensRouter();
                const [_flywheels, rewardTokens, rewards] = yield fwLensRouter.callStatic.claimRewardsForMarket(account, market, rewardDistributors, Array.from(rewardDistributors, () => true));
                return _flywheels.map((flywheel, i) => {
                    return {
                        flywheel,
                        rewardToken: rewardTokens[i],
                        amount: rewards[i]
                    };
                });
            });
        }
        getFlywheelClaimableRewardsByMarkets(poolAddress, markets, account) {
            return __awaiter(this, void 0, void 0, function* () {
                const pool = this.createComptroller(poolAddress, this.provider);
                const rewardDistributors = yield pool.callStatic.getRewardsDistributors();
                const fwLensRouter = this.createIonicFlywheelLensRouter();
                const [_flywheels, rewardTokens, rewards] = yield fwLensRouter.callStatic.claimRewardsForMarkets(account, markets, rewardDistributors, Array.from(rewardDistributors, () => true));
                return _flywheels.map((flywheel, i) => {
                    return {
                        flywheel,
                        rewardToken: rewardTokens[i],
                        amount: rewards[i]
                    };
                });
            });
        }
        getFlywheelClaimableRewardsForPool(poolAddress, account) {
            return __awaiter(this, void 0, void 0, function* () {
                const fwLensRouter = this.createIonicFlywheelLensRouter();
                const [_flywheels, rewardTokens, rewards] = yield fwLensRouter.callStatic.claimRewardsForPool(account, poolAddress);
                return _flywheels.map((flywheel, i) => {
                    return {
                        flywheel,
                        rewardToken: rewardTokens[i],
                        amount: rewards[i]
                    };
                });
            });
        }
        getAllFlywheelClaimableRewards(account) {
            return __awaiter(this, void 0, void 0, function* () {
                const fwLensRouter = this.createIonicFlywheelLensRouter();
                const [rewardTokens, rewards] = yield fwLensRouter.callStatic.claimAllRewardTokens(account);
                return rewardTokens.map((rewardToken, i) => {
                    return {
                        rewardToken,
                        amount: rewards[i]
                    };
                });
            });
        }
        /** WRITE */
        getFlywheelEnabledMarkets(flywheelAddress) {
            return this.createIonicFlywheel(flywheelAddress).callStatic.getAllStrategies();
        }
        setStaticRewardInfo(staticRewardsAddress, marketAddress, rewardInfo) {
            const staticRewardsInstance = this.createFlywheelStaticRewards(staticRewardsAddress, this.signer);
            return staticRewardsInstance.functions.setRewardsInfo(marketAddress, rewardInfo);
        }
        setFlywheelRewards(flywheelAddress, rewardsAddress) {
            const flywheelCoreInstance = this.createIonicFlywheel(flywheelAddress, this.signer);
            return flywheelCoreInstance.functions.setFlywheelRewards(rewardsAddress);
        }
        addMarketForRewardsToFlywheelCore(flywheelCoreAddress, marketAddress) {
            return this.addStrategyForRewardsToFlywheelCore(flywheelCoreAddress, marketAddress);
        }
        addStrategyForRewardsToFlywheelCore(flywheelCoreAddress, marketAddress) {
            const flywheelCoreInstance = this.createIonicFlywheel(flywheelCoreAddress, this.signer);
            return flywheelCoreInstance.functions.addStrategyForRewards(marketAddress);
        }
        addFlywheelCoreToComptroller(flywheelCoreAddress, comptrollerAddress) {
            const comptrollerInstance = this.createComptroller(comptrollerAddress, this.signer);
            return comptrollerInstance.functions._addRewardsDistributor(flywheelCoreAddress);
        }
        /**
          @notice claim rewards of single market.
          @param market market cToken address
          @param flywheels available flywheels addresses which market could have
          @return contract transaction
        */
        claimRewardsForMarket(market, flywheels) {
            return __awaiter(this, void 0, void 0, function* () {
                const fwLensRouter = this.createIonicFlywheelLensRouter(this.signer);
                const account = yield this.signer.getAddress();
                const tx = yield fwLensRouter.claimRewardsForMarket(account, market, flywheels, Array.from(flywheels, () => true));
                return tx;
            });
        }
        /**
          @notice claim rewards of multiple markets.
          @param market markets cToken addresses
          @param flywheels available flywheels addresses which markets could have
          @return contract transaction
        */
        claimRewardsForMarkets(markets, flywheels) {
            return __awaiter(this, void 0, void 0, function* () {
                const fwLensRouter = this.createIonicFlywheelLensRouter(this.signer);
                const account = yield this.signer.getAddress();
                const tx = yield fwLensRouter.claimRewardsForMarkets(account, markets, flywheels, Array.from(flywheels, () => true));
                return tx;
            });
        }
        /**
          @notice claim rewards of single pool.
          @param poolAddress pool address
          @return contract transaction
        */
        claimRewardsForPool(poolAddress) {
            return __awaiter(this, void 0, void 0, function* () {
                const fwLensRouter = this.createIonicFlywheelLensRouter(this.signer);
                const account = yield this.signer.getAddress();
                const tx = yield fwLensRouter.claimRewardsForPool(account, poolAddress);
                return tx;
            });
        }
        /**
          @notice claim rewards for specific reward token
          @param rewardToken reward token address
          @return contract transaction
        */
        claimRewardsForRewardToken(rewardToken) {
            return __awaiter(this, void 0, void 0, function* () {
                const fwLensRouter = this.createIonicFlywheelLensRouter(this.signer);
                const account = yield this.signer.getAddress();
                const tx = yield fwLensRouter.claimRewardsOfRewardToken(account, rewardToken);
                return tx;
            });
        }
        /**
          @notice claim all rewards
          @return contract transaction
        */
        claimAllRewards() {
            return __awaiter(this, void 0, void 0, function* () {
                const fwLensRouter = this.createIonicFlywheelLensRouter(this.signer);
                const account = yield this.signer.getAddress();
                const tx = yield fwLensRouter.claimAllRewardTokens(account);
                return tx;
            });
        }
    };
}
