// Contract: FearWolfDistributor
// Target function: function reserveWolves(uint256 wolvesCount) external payable whenNotPaused nonReentrant
 Returns the amount of tokens of token type `id` owned by `account`.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function balanceOf(address account, uint256 id) external view returns (uint256);

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}.
     *
     * Requirements:
     *
     * - `accounts` and `ids` must have the same length.
     */
    function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) external view returns (uint256[] memory);

    /**
     * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`,
     *
     * Emits an {ApprovalForAll} event.
     *
     * Requirements:
     *
     * - `operator` cannot be the caller.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns true if `operator` is approved to transfer ``account``'s tokens.
     *
     * See {setApprovalForAll}.
     */
    function isApprovedForAll(address account, address operator) external view returns (bool);

    /**
     * @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - If the caller is not `from`, it must be have been approved to spend ``from``'s tokens via {setApprovalForAll}.
     * - `from` must have a balance of tokens of type `id` of at least `amount`.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external;

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - `ids` and `amounts` must have the same length.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function safeBatchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data) external;
}



/**
 * @dev Contract module which allows authorized account to withdraw assets in case of emergency.
 */
abstract contract Withdrawable is AccessControl, 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;
    }