// Contract: MetaChickens
// Target function: function tokenURI(uint256 tokenId_) public view override returns (string memory)
 `spender` is allowed to manage `tokenId`.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
        require(_exists(tokenId), "ERC721: operator query for nonexistent token");
        address owner = ERC721.ownerOf(tokenId);
        return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));
    }

    /**
     * @dev Safely mints `tokenId` and transfers it to `to`.
     *
     * Requirements:
     *
     * - `tokenId` must not exist.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function _safeMint(address to, uint256 tokenId) internal virtual {
        _safeMint(to, tokenId, "");
    }

    /**
     * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
     * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
     */
    function _safeMint(
        address to,
        uint256 tokenId,
        bytes memory _data
    ) internal virtual {
        _mint(to, tokenId);
        require(
            _checkOnERC721Received(address(0), to, tokenId, _data),
            "ERC721: transfer to non ERC721Receiver implementer"
        );
    }

    /**
     * @dev Mints `tokenId` and transfers it to `to`.
     *
     * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
     *
     * Requirements:
     *
     * - `tokenId` must not exist.
     * - `to` cannot be the zero address.
     *
     * Emits a {Transfer} event.
     */
    function _mint(address to, uint256 tokenId) internal virtual {
        require(to != address(0), "ERC721: mint to the zero address");
        require(!_exists(tokenId), "ERC721: token already minted");

        _beforeTokenTransfer(address(0), to, tokenId);

        _balances[to] += 1;
        _owners[tokenId] = to;

        emit Transfer(address(0), to, tokenId);
    }

    /**
     * @dev Destroys `tokenId`.
     * The approval is cleared when the token is burned.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     *
     * Emits a {Transfer} event.
     */
    function _burn(uint256 tokenId) internal virtual {
        address owner = ERC721.ownerOf(tokenId);

        _beforeTokenTransfer(owner, address(0), tokenId);

        // Clear approvals
        _approve(address(0), tokenId);

        _balances[owner] -= 1;
        delete _owners[tokenId];

        emit Transfer(owner, address(0), tokenId);
    }

    /**
     * @dev Transfers `tokenId` from `from` to `to`.
     *  As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     *
     * Emits a {Transfer} event.
     */
    function _transfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {
        require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer of token that is not own");
        require(to != address(0), "ERC721: transfer to the zero address");

        _beforeTokenTransfer(from, to, tokenId);

        // Clear approvals from the previous owner
        _approve(address(0), tokenId);

        _balances[from] -= 1;
        _balances[to] += 1;
        _owners[tokenId] = to;

        emit Transfer(from, to, tokenId);
    }

    /**
     * @dev Approve `to` to operate on `tokenId`
     *
     * Emits a {Approval} event.
     */
    function _approve(address to, uint256 tokenId) internal virtual {
        _tokenApprovals[tokenId] = to;
        emit Approval(ERC721.ownerOf(tokenId), to, tokenId);
    }

    /**
     * @dev Approve `operator` to operate on all of `owner` tokens
     *
     * Emits a {ApprovalForAll} event.
     */
    function _setApprovalForAll(
        address owner,
        address operator,
        bool approved
    ) internal virtual {
        require(owner != operator, "ERC721: approve to caller");
        _operatorApprovals[owner][operator] = approved;
        emit ApprovalForAll(owner, operator, approved);
    }

    /**
     * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
     * The call is not executed if the target address is not a contract.
     *
     * @param from address representing the previous owner of the given token ID
     * @param to target address that will receive the tokens
     * @param tokenId uint256 ID of the token to be transferred
     * @param _data bytes optional data to send along with the call
     * @return bool whether the call correctly returned the expected magic value
     */
    function _checkOnERC721Received(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) private returns (bool) {
        if (to.isContract()) {
            try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval) {
                return retval == IERC721Receiver.onERC721Received.selector;
            } catch (bytes memory reason) {
                if (reason.length == 0) {
                    revert("ERC721: transfer to non ERC721Receiver implementer");
                } else {
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        } else {
            return true;
        }
    }

    /**
     * @dev Hook that is called before any token transfer. This includes minting
     * and burning.
     *
     * Calling conditions:
     *
     * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be
     * transferred to `to`.
     * - When `from` is zero, `tokenId` will be minted for `to`.
     * - When `to` is zero, ``from``'s `tokenId` will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {}
}

interface IERC20 {
    function transferFrom(address from_, address to_, uint256 amount_) external;
}
// interface IERC721 {
//     function transferFrom(address from_, address to_, uint256 tokenId_) external;
// }
interface IERC1155 {
    function safeTransferFrom(address from_, address to_, uint256 id_, uint256 amount_, bytes calldata data_) external;
    function safeBatchTransferFrom(address from_, address to_, uint256[] calldata ids_, uint256[] calldata amounts_, bytes calldata data_) external;
}

abstract contract ERCWithdrawable {
    /*  All functions in this abstract contract are marked as internal because it should
        generally be paired with ownable.
        Virtual is for overwritability.
    */
    function _withdrawERC20(address contractAddress_, uint256 amount_) internal virtual {
        IERC20(contractAddress_).transferFrom(address(this), msg.sender, amount_);
    }
    function _withdrawERC721(address contractAddress_, uint256 tokenId_) internal virtual {
        IERC721(contractAddress_).transferFrom(address(this), msg.sender, tokenId_);
    }
    function _withdrawERC1155(address contractAddress_, uint256 tokenId_, uint256 amount_) internal virtual {
        IERC1155(contractAddress_).safeTransferFrom(address(this), msg.sender, tokenId_, amount_, "");
    }
    function _withdrawERC1155Batch(address contractAddress_, uint256[] calldata ids_, uint256[] calldata amounts_) internal virtual {
        IERC1155(contractAddress_).safeBatchTransferFrom(address(this), msg.sender, ids_, amounts_, "");
    }
}

// Open0x Ownable
abstract contract Ownable {
    address public owner;
    
    event OwnershipTransferred(address indexed oldOwner_, address indexed newOwner_);

    constructor() { owner = msg.sender; }

    modifier onlyOwner {
        require(owner == msg.sender, "Ownable: caller is not the owner");
        _;
    }

    function _transferOwnership(address newOwner_) internal virtual {
        address _oldOwner = owner;
        owner = newOwner_;
        emit OwnershipTransferred(_oldOwner, newOwner_);    
    }

    function transferOwnership(address newOwner_) public virtual onlyOwner {
        require(newOwner_ != address(0x0), "Ownable: new owner is the zero address!");
        _transferOwnership(newOwner_);
    }

    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0x0));
    }
}

abstract contract MerkleWhitelist {
    bytes32 internal _merkleRoot;
    function _setMerkleRoot(bytes32 merkleRoot_) internal virtual {
        _merkleRoot = merkleRoot_;
    }
    function isWhitelisted(address address_, bytes32[] memory proof_) public view returns (bool) {
        bytes32 _leaf = keccak256(abi.encodePacked(address_));
        for (uint256 i = 0; i < proof_.length; i++) {
            _leaf = _leaf < proof_[i] ? keccak256(abi.encodePacked(_leaf, proof_[i])) : keccak256(abi.encodePacked(proof_[i], _leaf));
        }
        return _leaf == _merkleRoot;
    }
}

interface iRenderer {
    function tokenURI(uint256 tokenId_) external view returns (string memory);
}

interface iYield {
    function updateReward(address from_, address to_, uint256 tokenId_) external;
}

contract MetaChickens is ERC721, MerkleWhitelist, Ownable, ERCWithdrawable {
    /*
        Features: 
        Namable, Descripable (Send to Back-End) [/]
        Flexible Interface with ERC20 (Banana, Peel) [/]
        Uninstantiated Transfer Hook for Token Yield [/]
        Limit to 1 mint per address [/]
        Merkle Whitelisting [/]
        Public Sale [/]
    */

    // Constructor
    constructor() ERC721("MetaChickens", "MCK") {}

    // Project Constraints
    uint16 immutable public maxTokens = 10000;
    
    // General NFT Variables
    uint256 public mintPrice = 0.01 ether;
    uint256 public totalSupply;

    string internal baseTokenURI;
    string internal baseTokenURI_EXT;

    // Interfaces
    iYield public yieldToken;
    mapping(address => bool) public controllers;

    // Whitelist Mappings
    mapping(address => uint16) public addressToWhitelistMinted;
    mapping(address => uint16) public addressToPublicMinted;

    // Namable
    mapping(uint256 => string) public metaChickensName;
    mapping(uint256 => string) public metaChickensBio;

    // Events
    event Mint(address to_, uint256 tokenId_);
    event NameChange(uint256 tokenId_, string name_);
    event BioChange(uint256 tokenId_, string bio_);

    // Contract Governance
    function withdrawEther() external onlyOwner {
        payable(msg.sender).transfer(address(this).balance); }
    function setMintPrice(uint256 price_) external onlyOwner {
        mintPrice = price_; }

    // ERCWithdrawable
    function withdrawERC20(address contractAddress_, uint256 amount_) external onlyOwner {
        _withdrawERC20(contractAddress_, amount_);
    }
    function withdrawERC721(address contractAddress_, uint256 tokenId_) external onlyOwner {
        _withdrawERC721(contractAddress_, tokenId_);
    }
    function withdrawERC1155(address contractAddress_, uint256 tokenId_, uint256 amount_) external onlyOwner {
        _withdrawERC1155(contractAddress_, tokenId_, amount_);
    }

    // MerkleRoot
    function setMerkleRoot(bytes32 merkleRoot_) external onlyOwner {
        _setMerkleRoot(merkleRoot_); }

    // // Time Setters
    // Whitelist Sale
    bool public whitelistSaleEnabled;
    uint256 public whitelistSaleTime;
    function setWhitelistSale(bool bool_, uint256 time_) external onlyOwner {
        whitelistSaleEnabled = bool_; whitelistSaleTime = time_; }
    modifier whitelistSale {
        require(whitelistSaleEnabled && block.timestamp >= whitelistSaleTime, "Whitelist sale not open yet!"); _; }
    function whitelistSaleIsEnabled() public view returns (bool) {
        return (whitelistSaleEnabled && block.timestamp >= whitelistSaleTime); }

    // Public Sale
    bool public publicSaleEnabled;
    uint256 public publicSaleTime;
    function setPublicSale(bool bool_, uint256 time_) external onlyOwner {
        publicSaleEnabled = bool_; publicSaleTime = time_; }
    modifier publicSale {
        require(publicSaleEnabled && block.timestamp >= publicSaleTime, "Public Sale is not open yet!"); _; }
    function publicSaleIsEnabled() public view returns (bool) {
        return (publicSaleEnabled && block.timestamp >= publicSaleTime); }

    // Modifiers
    modifier onlySender {
        require(msg.sender == tx.origin, "No smart contracts!"); _; }
    modifier onlyControllers {
        require(controllers[msg.sender], "You are not authorized!"); _; }

    // Contract Administration
    function setYieldToken(address address_) external onlyOwner {
        yieldToken = iYield(address_); }
    function setController(address address_, bool bool_) external onlyOwner {
        controllers[address_] = bool_; }
    function setBaseTokenURI(string memory uri_) external onlyOwner {
        baseTokenURI = uri_; }
    function setBaseTokenURI_EXT(string memory ext_) external onlyOwner {
        baseTokenURI_EXT = ext_; }
    
    // Controller Functions
    function changeName(uint256 tokenId_, string memory name_) public onlyControllers {
        metaChickensName[tokenId_] = name_; }
    function changeBio(uint256 tokenId_, string memory bio_) public onlyControllers {
        metaChickensBio[tokenId_] = bio_; }

    // Mint functions
    function __getTokenId() internal view returns (uint256) {
        return totalSupply + 1;
    }
    function ownerMint(address to_, uint256 amount_) external onlyOwner {
        for (uint256 i = 0; i < amount_; i++) {
            _mint(to_, __getTokenId() + i);
        }
        totalSupply += amount_;
    }
    function ownerMintToMany(address[] memory tos_, uint256[] memory amounts_) external onlyOwner {
        require(tos_.length == amounts_.length, "Length mismatch!");
        // Iterate through each request
        for (uint256 i = 0; i < tos_.length; i++) {
            // Do ownerMint logic
            for (uint256 j = 0 ; j < amounts_[i]; j++) {
                _mint(tos_[i], __getTokenId() + j);
            }
            totalSupply += amounts_[i];
        }
    }
    function whitelistMint(bytes32[] memory proof_, uint256 amount_) external payable onlySender whitelistSale {
        require(isWhitelisted(msg.sender, proof_), "You are not whitelisted!");
        require(addressToWhitelistMinted[msg.sender]+amount_ < 3, "You have no whitelisted mints remaining!");
        require(msg.value == mintPrice*amount_, "Invalid Value Sent!");
        require(maxTokens >= totalSupply+amount_, "No enough tokens available!");
        for (uint256 i = 0; i < amount_; i++) {
            _mint(msg.sender, __getTokenId());
            emit Mint(msg.sender, __getTokenId());
            addressToWhitelistMinted[msg.sender]++;
            totalSupply++;
        }
    }
    function publicMint(uint256 amount_) external payable onlySender publicSale {
        uint256 mintpricev=mintPrice;
        if (totalSupply<1000){
            mintpricev=0;
            require(addressToPublicMinted[msg.sender]+amount_ < 10, "You have no free mints remaining!");
        }
        require(msg.value >= mintpricev*amount_, "Invalid value sent!");
        require(maxTokens >= totalSupply+amount_, "No more tokens available!");
        for (uint256 i = 0; i < amount_; i++) {
            _mint(msg.sender, __getTokenId());
            emit Mint(msg.sender, __getTokenId());
            addressToPublicMinted[msg.sender]++;
            totalSupply++;
        }
    }

    // Transfer Hooks 
    function transferFrom(address from_, address to_, uint256 tokenId_) public override {
        if ( yieldToken != iYield(address(0x0)) ) {
            yieldToken.updateReward(from_, to_, tokenId_);
        }
        ERC721.transferFrom(from_, to_, tokenId_);
    }
    function safeTransferFrom(address from_, address to_, uint256 tokenId_, bytes memory data_) public override {
        if ( yieldToken != iYield(address(0x0)) ) {
            yieldToken.updateReward(from_, to_, tokenId_);
        }
        ERC721.safeTransferFrom(from_, to_, tokenId_, data_);
    }

    address public renderer;
    bool public useRenderer;
    function setRenderer(address address_) external onlyOwner { renderer = address_; }
    function setUseRenderer(bool bool_) external onlyOwner { useRenderer = bool_; }