// Contract: YAKDAODomes
// Target function: function allAccountPresales(
 override {
        safeTransferFrom(from, to, tokenId, "");
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) public virtual override {
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");
        _safeTransfer(from, to, tokenId, _data);
    }

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC721 protocol to prevent tokens from being forever locked.
     *
     * `_data` is additional data, it has no specified format and it is sent in call to `to`.
     *
     * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
     * implement alternative mechanisms to perform token transfer, such as signature-based.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function _safeTransfer(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) internal virtual {
        _transfer(from, to, tokenId);
        require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer");
    }

    /**
     * @dev Returns whether `tokenId` exists.
     *
     * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
     *
     * Tokens start existing when they are minted (`_mint`),
     * and stop existing when they are burned (`_burn`).
     */
    function _exists(uint256 tokenId) internal view virtual returns (bool) {
        return _owners[tokenId] != address(0);
    }

    /**
     * @dev Returns whether `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 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(to).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 {}
}

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _setOwner(_msgSender());
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
        _;
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _setOwner(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _setOwner(newOwner);
    }

    function _setOwner(address newOwner) private {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

/**
 * @dev YAKDAO Camping and Glamping Properties, NFT Collection.
 */
contract YAKDAODomes is ERC721, Ownable {
    using Strings for uint256;

    address public treasury;
    string public baseTokenURI;

    enum stage { Paused, Presale, Open }
    stage public mintStage = stage.Paused;

    enum asset { MaunaKea, Everest, K2, Sherpa }

    uint16[4] private floor = [1, 534, 4776, 9981];
    mapping(asset => uint256) private index;
    uint16[4] private ceiling = [533, 4775, 9980, 10000];
    
    mapping(asset => mapping(address => uint16)) private presale;
    mapping(asset => uint16) private presaleLeft;

    uint8[4] public mintLimit = [3, 5, 5, 0];
    mapping(address => mapping(asset => uint8)) private mintIndex;

    uint256[4] public mintFee = [2.5 ether, 0.1 ether, 0.05 ether, 0];

    bool private reentrancyLock = false;
    modifier reentrancyGuard {
        if (reentrancyLock) {
            revert();
        }
        reentrancyLock = true;
        _;
        reentrancyLock = false;
    }

    constructor(
    ) ERC721("YAK Domes", "YAKDAO") {
        //initialize indexes to each floor's starting index
        index[asset.MaunaKea] = floor[uint256(asset.MaunaKea)];
        index[asset.Everest] = floor[uint256(asset.Everest)];
        index[asset.K2] = floor[uint256(asset.K2)];
        index[asset.Sherpa] = floor[uint256(asset.Sherpa)];
    }

    /**
     * @dev Add dome `_type` presale whitelist `amount` for `account`.
     */
    function addPresale(
        asset _type,
        address _account,
        uint16 _amount
    ) public onlyOwner {
        require(_account != address(0) && _amount > 0, "YAKDAO: Invalid presale");

        presale[_type][_account] += _amount;
        presaleLeft[_type] += _amount;
    }

    /**
     * @dev Add dome `_type` presale whitelist arrays of `amount` for `account`.
     */
    function addPresales(
        asset _type,
        address[] calldata _account,
        uint16[] calldata _amount
    ) external onlyOwner {
        require(_account.length > 0 && _account.length ==_amount.length, "YAKDAO: Invalid presales");

        for (uint16 i; i < _account.length; i++) {
            if (_account[i] != address(0) && _amount[i] > 0) {
                addPresale(_type, _account[i], _amount[i]);
            }
        }
    }

    /**
     * @dev Set treasury `_account` where mint fees are sent to.
     */
    function setTreasury(
        address _account
    ) external onlyOwner {
        treasury = _account;
    }

    /**
     * @dev Set mint `_stage` as enumeration 'Paused' (0), 'Presale' (1), or 'Open' (2)
     */
    function setMintStage(
        stage _stage
    ) external onlyOwner {
        mintStage = _stage;
    }

    /**
     * @dev Set dome `_type` `_mintFee`
     */
    function setMintFee(
        asset _type,
        uint256 _mintFee
    ) external onlyOwner {
        mintFee[uint256(_type)] = _mintFee;
    }

    /**
     * @dev Set dome `_type` mint `_limit` per account (does not include presale)
     */
    function setMintLimit(
        asset _type,
        uint8 _limit
    ) external onlyOwner {
        mintLimit[uint256(_type)] = _limit;
    }

    /**
     * @dev Set `_baseTokenURI` for collection
     */
    function setBaseURI(
        string memory _baseTokenURI
    ) external onlyOwner {
        baseTokenURI = _baseTokenURI;
    }

    /**
     * @dev Open Mint for dome `_type` for mintFee if within mintLimit
     * Mint Stages: 
     *      Paused - No mnting allowed
     *      Presale - Presale minting only
     *      Open - Open Minting
     */
    function mint(
        asset _type
    ) external payable reentrancyGuard {
        require(mintStage == stage.Open, "YAKDAO: Minting not open.");
        uint256 type_ = uint256(_type);
        require(type_ < 3, "YAKDAO: Invalid type, 0-2.");
        require(//has supply available
            index[_type] + presaleLeft[_type] <= ceiling[type_],
            "YAKDAO: Supply limit reached for type."
        );
        require(mintIndex[_msgSender()][_type] < mintLimit[type_], "YAKDAO: Mint limit reached.");

        uint256 mintFee_ = mintFee[type_];
        require(msg.value >= mintFee_, "YAKDAO: Mint fee required.");
        if (msg.value > mintFee_) {
            refundOverpay(msg.value - mintFee_);
        }
        Address.sendValue(payable(treasury), mintFee_);
        
        _mint(_msgSender(), index[_type]);
        index[_type] += 1;
        mintIndex[_msgSender()][_type] += 1;
    }

    /**
     * @dev Presale only mint function
     */
    function mintPresale(
        asset _type
    ) external reentrancyGuard {
        require(mintStage != stage.Paused, "YAKDAO: Minting paused.");
        uint16 _amount = presale[_type][_msgSender()];
        require(_amount > 0, "YAKDAO: No presales left to mint.");
        
        for (uint16 i = 0; i < _amount; i++) {
            _mint(_msgSender(), index[_type] + i);
        }
        presaleLeft[_type] -= _amount;
        index[_type] += _amount;
        delete presale[_type][_msgSender()];
    }

    /**
     * @dev Owner Sherpa mint function
     */
    function mintSherpa(
    ) external onlyOwner reentrancyGuard {
        uint256 _floor = floor[uint256(asset.Sherpa)];
        uint256 _ceiling = ceiling[uint256(asset.Sherpa)];
        for (uint256 i = _floor; i <= _ceiling; i++) {
            _mint(_msgSender(), i);
        }
        index[asset.Sherpa] = ceiling[uint256(asset.Sherpa)] + 1;
    }

    /**
     * @dev View of presales left for `_type` by `_account`
     */
    function presaleByAccount(
        asset _type,
        address _account
    ) public view returns (uint16) {
        return presale[_type][_account];
    }