contract ZuniTreasury is Ownable {
    using SafeMath for uint256;
    using Address for address;

    ConfigurableRightsPool public crp;

    IERC20 public zuni;
    IBPool public bPool;

    address public zuniBadge;

    uint256 public constant MAX_VAL = 2**256 - 1;
    uint256 public constant WEI_POINT = 10**18;
    uint256 public constant DEFAULT_ISSURANCE_AMOUNT = WEI_POINT * 100;

    constructor(IERC20 _zuni) public {
        zuni = _zuni;
    }

    modifier onlyZuniBadge() {
        require(msg.sender == zuniBadge, "ZuinTreasury: should be badge sc");
        _;
    }

    function smartPoolAddress() external view returns (address) {
        return address(crp);
    }

    function setZuniBadge(address _zuniBadge) external onlyOwner {
        zuniBadge = _zuniBadge;
    }

    function setCRP(address _crpAddress) external onlyOwner {
        require(address(crp) == address(0x0));

        crp = ConfigurableRightsPool(_crpAddress);
        bPool = IBPool(crp.bPool());
    }

    function setZuniToken(IERC20 _zuni) external onlyOwner {
        zuni = _zuni;
    }

    function _issue(uint256 tokenAmountOut) internal {
        uint256 maxPoolAmountIn = bPool.calcPoolInGivenSingleOut(
            bPool.getBalance(address(zuni)),
            bPool.getDenormalizedWeight(address(zuni)),
            crp.totalSupply(),
            bPool.getTotalDenormalizedWeight(),
            tokenAmountOut,
            bPool.getSwapFee()
        );

        maxPoolAmountIn = maxPoolAmountIn.mul(101).div(100);

        crp.exitswapExternAmountOut(
            address(zuni),
            tokenAmountOut,
            maxPoolAmountIn
        );
    }

    function giveZuniReward(address to, uint256 amount) external onlyZuniBadge {
        if (IERC20(address(zuni)).balanceOf(address(this)) < amount) {
            _issue(amount.add(DEFAULT_ISSURANCE_AMOUNT));
        }

        require(IERC20(address(zuni)).transfer(to, amount));
    }

    function issue(uint256 tokenAmountOut) external onlyOwner {
        _issue(tokenAmountOut);
    }

    function buyback(uint256 tokenAmountIn) external onlyOwner {
        require(
            IERC20(address(zuni)).balanceOf(address(this)) >= tokenAmountIn,
            "ZuniTreasury: zuni balance is not enough"
        );

        uint256 minPoolAmountOut = bPool.calcPoolOutGivenSingleIn(
            bPool.getBalance(address(zuni)),
            bPool.getDenormalizedWeight(address(zuni)),
            crp.totalSupply(),
            bPool.getTotalDenormalizedWeight(),
            tokenAmountIn,
            bPool.getSwapFee()
        );

        minPoolAmountOut = minPoolAmountOut.mul(99).div(100);

        require(IERC20(address(zuni)).approve(address(crp), tokenAmountIn));

        crp.joinswapExternAmountIn(
            address(zuni),
            tokenAmountIn,
            minPoolAmountOut
        );
    }

    function capitalization(address token, uint256 tokenAmountOut)
        external
        onlyOwner
    {
        require(
            token != address(zuni),
            "ZuniTreasury: token address is incorrect"
        );

        uint256 maxPoolAmountIn = bPool.calcPoolInGivenSingleOut(
            bPool.getBalance(address(token)),
            bPool.getDenormalizedWeight(address(token)),
            crp.totalSupply(),
            bPool.getTotalDenormalizedWeight(),
            tokenAmountOut,
            bPool.getSwapFee()
        );

        maxPoolAmountIn = maxPoolAmountIn.mul(101).div(100);

        crp.exitswapExternAmountOut(
            address(token),
            tokenAmountOut,
            maxPoolAmountIn
        );
    }

    function withdrawToken(
        address token,
        address to,
        uint256 amount
    ) external onlyOwner {
        require(
            IERC20(address(token)).balanceOf(address(this)) >= amount,
            "ZuniTreasury: token balance is not enough"
        );

        require(IERC20(address(token)).transfer(to, amount));
    }

    // external functions only can be called by owner to config CRP

    function setSwapFee(uint256 swapFee) external onlyOwner {
        crp.setSwapFee(swapFee);
    }

    function setCap(uint256 newCap) external onlyOwner {
        crp.setCap(newCap);
    }

    function setPublicSwap(bool publicSwap) external onlyOwner {
        crp.setPublicSwap(publicSwap);
    }

    function updateWeight(address token, uint256 newWeight) external onlyOwner {
        crp.updateWeight(token, newWeight);
    }

    function updateWeightsGradually(
        uint256[] calldata newWeights,
        uint256 startBlock,
        uint256 endBlock
    ) external onlyOwner {
        crp.updateWeightsGradually(newWeights, startBlock, endBlock);
    }

    function pokeWeights() external onlyOwner {
        crp.pokeWeights();
    }

    function commitAddToken(
        address token,
        uint256 balance,
        uint256 denormalizedWeight
    ) external onlyOwner {
        crp.commitAddToken(token, balance, denormalizedWeight);
    }

    function applyAddToken() external onlyOwner {
        crp.applyAddToken();
    }

    function removeToken(address token) external onlyOwner {
        crp.removeToken(token);
    }

    function whitelistLiquidityProvider(address provider) external onlyOwner {
        crp.whitelistLiquidityProvider(provider);
    }

    function removeWhitelistedLiquidityProvider(address provider)
        external
        onlyOwner
    {
        crp.removeWhitelistedLiquidityProvider(provider);
    }
}
function setSwapFee(uint256 swapFee) external onlyOwner {
    crp.setSwapFee(swapFee);
}

// external functions only can be called by owner to config CRP