// Contract: FearWolfDistributor
// Target function: function buyWolves(uint256 wolvesCount) external payable whenNotPaused nonReentrant
Control, RequirementsChecker, ERC721Holder, ERC1155Holder, IWithdrawable {

    constructor () {
        _registerInterface(type(IWithdrawable).interfaceId);
    }

    /**
    * @dev Withdraws all balance of the network main currency.
    * Emits a {Withdrawal} event.
    */
    function withdraw(address payable to, string calldata reason) external onlyRole(ADMIN_ROLE) {
        _requireNonZeroAddress(to, "to");
        _requireStringData(reason, "reason");
        _beforeWithdrawal(to);

        (bool success,) = to.call{value: address(this).balance}("");
        require(success, "Withdraw failed");
        emit Withdrawal(to, reason);
    }

    /**
    * @dev Withdraws all balance of specified ERC20 asset.
    * Emits a {WithdrawalERC20} event.
    */
    function withdrawERC20(address asset, address to, string calldata reason) external onlyRole(ADMIN_ROLE) {
        _requireNonZeroAddress(asset, "asset");
        _requireNonZeroAddress(to, "to");
        _requireStringData(reason, "reason");
        _beforeWithdrawalERC20(asset, to);

        IERC20 token = IERC20(asset);        
        token.transfer(to, token.balanceOf(address(this)));
        emit WithdrawalERC20(asset, to, reason);
    }

    /**
    * @dev Withdraws all of specified ERC721 asset with ids.
    * Emits a {WithdrawalERC721} event.
    */
    function withdrawERC721(address asset, uint256[] calldata ids, address to, string calldata reason) external onlyRole(ADMIN_ROLE) {
        _requireNonZeroAddress(asset, "asset");
        _requireNonZeroAddress(to, "to");
        _requireArrayData(ids, "ids");
        _requireStringData(reason, "reason");
        _beforeWithdrawalERC721(asset, ids, to);

        IERC721 token = IERC721(asset);
        for(uint i = 0; i < ids.length; i++)
            token.safeTransferFrom(address(this), to, ids[i], "");
        emit WithdrawalERC721(asset, ids, to, reason);
    }

    /**
    * @dev Withdraws all balances of specified ERC1155 asset with ids.
    * Emits a {WithdrawalERC1155} event.
    */
    function withdrawERC1155(address asset, uint256[] calldata ids, address to, string calldata reason) external onlyRole(ADMIN_ROLE) {
        _requireNonZeroAddress(asset, "asset");
        _requireNonZeroAddress(to, "to");
        _requireArrayData(ids, "ids");
        _requireStringData(reason, "reason");
        _beforeWithdrawalERC1155(asset, ids, to);

        IERC1155 token = IERC1155(asset);

        address[] memory addresses = new address[](ids.length);
        for(uint i = 0; i < ids.length; i++)
            addresses[i] = address(this); // actually only this one, but multiple times to call balanceOfBatch

        uint256[] memory balances = token.balanceOfBatch(addresses, ids);
        token.safeBatchTransferFrom(address(this), to, ids, balances, "");
        emit WithdrawalERC1155(asset, ids, to, reason);
    }

    /**
    * @dev This function is called before withdrawal takes place.
    * Override to add custom conditions or actions.
    */
    function _beforeWithdrawal(address to) internal virtual {
    }

    /**
    * @dev This function is called before ERC20 withdrawal takes place.
    * Override to add custom conditions or actions.
    */
    function _beforeWithdrawalERC20(address asset, address to) internal virtual {
    }

    /**
    * @dev This function is called before ERC721 withdrawal takes place.
    * Override to add custom conditions or actions.
    */
    function _beforeWithdrawalERC721(address asset, uint256[] calldata ids, address to) internal virtual {
    }

    /**
    * @dev This function is called before ERC1155 withdrawal takes place.
    * Override to add custom conditions or actions.
    */
    function _beforeWithdrawalERC1155(address asset, uint256[] calldata ids, address to) internal virtual {
    }
}




/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        // On the first call to nonReentrant, _notEntered will be true
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;

        _;

        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }
}



/**
 * @dev Partial interface for FearWolf contract (only what we need from it here).
 */
interface IFearWolf is IAccessControl {
    function setInitialOwner(address initialOwner, uint256 wolvesCount) external;
    function DISTRIBUTOR_ROLE() external view returns (bytes32);
}

/**
 * @dev Contract for distribution FEAR Wolf NFT within INO Sale and public sale phases.
 */
contract FearWolfDistributor is AccessControl, Pausable, Withdrawable, ReentrancyGuard {
    using SafeMath for uint256;
    using Strings for uint256;

    /**
    * @dev Emitted when number of wolves an account can reserve on INO Sale is changed.
    */
    event PresaleWolvesPerAccountLimitChanged(uint256 presaleWolvesPerAccountLimit);
    /**
    * @dev Emitted when number of wolves possible to mint in one call is changed.
    */
    event BatchMintingLimitChanged(uint256 batchMintingLimit);
    /**
    * @dev Emitted when INO Sale / Public Sale start timestamps are changed.
    */
    event DatesChanged(uint256 whitelistedPresaleStart, uint256 publicPresaleStart, uint256 saleStart, uint256 claimingStart);
    /**
    * @dev Emitted when prices are changed.
    */
    event PricesChanged(uint256 presalePrice, uint256 salePriceMax, uint256 salePriceMin);
    /**
    * @dev Emitted when dutch auction parameters are changed.
    */
    event DutchAuctionParametersChanged(uint256 dutchAuctionStep, uint256 dutchAuctionStepDuration);
    /**
    * @dev Emitted when wolves count is changed.
    */
    event WolvesCountChanged(uint256 presaleWolvesCount, uint256 saleWolvesCount, uint256 giftWolvesCount);
    /**
    * @dev Emitted when FEAR Wolf contract address is set.
    */
    event FearWolfContractAddressSet(address fearWolfContractAddress);
    /**
    * @dev Emitted when FEAR Wolf is reserved.
    */
    event WolvesReserved(address indexed account, uint256 amount);
    /**
    * @dev Emitted when FEAR Wolf is claimed.
    */
    event WolvesClaimed(address indexed account, uint256 amount);
    /**
    * @dev Emitted when FEAR Wolf is bought.
    */
    event WolvesBought(address indexed account, uint256 price, uint256 amount);
    /**
    * @dev Emitted when FEAR Wolf is gifted.
    */
    event WolvesGifted(address indexed account, uint256 amount);


    /**
    * @dev Number of wolves an account can reserve on INO Sale.
    */
    uint256 public presaleWolvesPerAccountLimit = 5;

    /**
    * @dev Number of wolves available for minting at once.
    */
    uint256 public batchMintingLimit = 10;

    /**
    * @dev Total numbers of wolves reserved during INO Sale.
    */
    uint256 public totalWolvesReserved;
    /**
    * @dev Total numbers of wolves bought during Public Sale.
    */
    uint256 public totalWolvesBought;

    // Timestamps
    uint256 public whitelistedPresaleStart;
    uint256 public publicPresaleStart;
    uint256 public saleStart;
    uint256 public claimingStart;

    // Prices in ETH
    uint256 public presalePrice = 1 * 10 ** 17; // 0.10 ETH
    uint256 public salePriceMax = 2 * 10 ** 17; // 0.20 ETH
    uint256 public salePriceMin = 18 * 10 ** 16; // 0.18 ETH

    // Dutch Auction - 0.001 ETH price drop every 2 hours, 40 hours totally
    uint256 public dutchAuctionStep = 1 * 10 ** 15;
    uint256 public dutchAuctionStepDuration = 2 hours;

    // Wolves Counts
    uint256 public initialPresaleWolvesCount = 2000;
    uint256 public initialSaleWolvesCount = 4000;
    uint256 private presaleWolvesCount = 2000;
    uint256 private saleWolvesCount = 4000;
    uint256 private giftWolvesCount = 66;

    /**
    * @dev FEAR Wolf contract address.
    */
    address public fearWolfContractAddress;
    IFearWolf private fearWolfContract;

    // address => wolves reserved on INO Sale
    mapping (address => uint256) private wolvesReserved;

    // address => wolves bought on Public Sale
    mapping (address => uint256) private wolvesBought;

    // address => true for already claimed reserved wolves
    mapping (address => bool) private claimed;

    // address => true for whitelisted
    mapping (address => bool) private whitelisted;


    constructor() {
        whitelistedPresaleStart = block.timestamp + 7 days;
        publicPresaleStart = whitelistedPresaleStart + 4 days;
        saleStart = whitelistedPresaleStart + 7 days;
        claimingStart = whitelistedPresaleStart + 14 days;
    }

    function _beforeUnpause() internal view virtual override {
        require(fearWolfContractAddress != address(0), "FEAR Wolf contract must be set before unpausing");
        require(fearWolfContract.hasRole(fearWolfContract.DISTRIBUTOR_ROLE(), address(this)), "Distributor role missing");
    }

    /**
    * @dev Returns whitelisted status for the caller.
    * @return True if the caller is whitelisted, false - otherwise.
    */
    function getMyWhitelistedStatus() external view returns (bool) {
        return whitelisted[_msgSender()];
    }

    /**
    * @dev Returns number of FEAR Wolves reserved for the caller.
    * @return Number of FEAR Wolves.
    */
    function getMyNumberOfWolvesReserved() external view returns (uint256) {
        return wolvesReserved[_msgSender()];
    }

    /**
    * @dev Returns number of FEAR Wolves claimed by the caller.
    * @return Number of FEAR Wolves.
    */
    function getMyNumberOfWolvesClaimed() external view returns (uint256) {
        return claimed[_msgSender()] ? wolvesReserved[_msgSender()] : 0;
    }

    /**
    * @dev Returns number of FEAR Wolves bought by the caller.
    * @return Number of FEAR Wolves.
    */
    function getMyNumberOfWolvesBought() external view returns (uint256) {
        return wolvesBought[_msgSender()];
    }

    /**
    * @dev Returns number of FEAR Wolves left available for reservation on INO Sale.
    * @return Number of FEAR Wolves.
    */
    function getPresaleWolvesAvailable() external view returns (uint256) {
        if (block.timestamp < saleStart)
            return presaleWolvesCount;
        else
            return 0;
    }

    /**
    * @dev Returns number of FEAR Wolves left available for buying on Public Sale.
    * @return Number of FEAR Wolves.
    */
    function getPublicSaleWolvesAvailable() external view returns (uint256) {
        if (block.timestamp < saleStart)
            return saleWolvesCount;
        else
            return presaleWolvesCount + saleWolvesCount; // unsold INO Sale wolves are added to wolves available for sale
    }

    /**
    * @dev Returns number of FEAR Wolves left available for gifting.
    * @return Number of FEAR Wolves.
    */
    function getGiftWolvesAvailable() external view returns (uint256) {
        return giftWolvesCount;
    }

    /**
    * @dev Returns current FEAR Wolf price in ETH for Public Sale.
    * @return Amount of ETH.
    */
    function getCurrentPrice() public view returns (uint256) {
        if (block.timestamp < saleStart)
            return salePriceMax;

        uint256 stepsCount = (block.timestamp - saleStart) / dutchAuctionStepDuration;
        if (stepsCount < (salePriceMax - salePriceMin) / dutchAuctionStep)
            return salePriceMax - stepsCount * dutchAuctionStep;
        else
            return salePriceMin;
    }


    /**
    * @dev Allows caller to reserve FEAR Wolf by paying ETH at INO Sale's price.
    * @param wolvesCount Number of wolves to reserve.
    * Requirements:
    * - Contract must not be paused;
    * - INO Sale phase must be active;
    * - Unless Public INO Sale is active, user must be whitelisted to reserve a wolf;
    * - Caller must be a wallet address and not a contract;
    * - Number of wolves to reserve must be greater than zero;
    * - Number of wolves to reserve must be less than or equal to wolves left available for reservation;
    * - The sum of wolves to reserve and wolves reserved by the caller already must be less or equal to maximum allowed number of reserved wolves per account;
    * - Sum of ETH sent must be exactly equal to INO Sale's price multiplied by number of wolves to reserve.
    * Emits {WolvesReserved} event on success.
    */
    function reserveWolves(uint256 wolvesCount) external payable whenNotPaused nonReentrant {
        address caller = _msgSender();

        require(block.timestamp >= whitelistedPresaleStart, "INO phase is not active yet");
        require(block.timestamp < saleStart, "INO phase is over");
        require(block.timestamp >= publicPresaleStart || whitelisted[caller], "Only whitelisted users can reserve a wolf now");

        require(tx.origin == caller, "Contracts are not allowed");
        require(wolvesCount > 0, "Cannot reserve zero wolves");
        require(presaleWolvesCount >= wolvesCount, "Not enough wolves available for reservation");
        require(wolvesReserved[caller] + wolvesCount <= presaleWolvesPerAccountLimit, "Amount of wolves to reserve exceeds INO wolves per account limit");

        require(msg.value >= wolvesCount.mul(presalePrice), "Insufficient ETH sent");
        require(msg.value <= wolvesCount.mul(presalePrice), "Too much ETH sent");
        
        presaleWolvesCount -= wolvesCount;
        totalWolvesReserved += wolvesCount;
        wolvesReserved[caller] += wolvesCount;

        emit WolvesReserved(caller, wolvesCount);
    }

    /**
    * @dev Allows caller to claim reserved FEAR Wolf.
    * Requirements:
    * - Contract must not be paused;
    * - Claiming phase must already started;
    * - Caller must have at least one FEAR wolf reserved;
    * - Caller have not claimed their FEAR wolves yet.
    * Emits {WolvesClaimed} event on success.
    */
    function claimWolves() external whenNotPaused nonReentrant {
        address caller = _msgSender();
        uint256 wolvesCount = wolvesReserved[caller];

        require(block.timestamp >= claimingStart, "Claiming is not active yet");
        require(wolvesCount > 0, "You don't have any wolves reserved");
        require(!claimed[caller], "You have claimed your wolves already");
        
        claimed[caller] = true;
        fearWolfContract.setInitialOwner(caller, wolvesCount);
        emit WolvesClaimed(caller, wolvesCount);
    }