contract OceanWorld is ERC721Enumerable, Ownable {
    using Counters for Counters.Counter;
    using MerkleProof for bytes32[];

    Counters.Counter private _tokenId;

    uint256 public constant MAX_OW = 10000;

    uint256 public price = 150000000000000000; //0.15 Ether
    uint256 public firstPresalePrice = 80000000000000000; //0.08 Ether
    uint256 public secondAndThirdPresalePrice = 100000000000000000; //0.1 Ether

    string baseTokenURI;

    bool public saleOpen = false;
    bool public firstPresaleOpen = false;
    bool public secondPresaleOpen = false;
    bool public thirdPresaleOpen = false;

    bytes32 public merkleRoot;

    event Minted(uint256 totalMinted);

    constructor(string memory baseURI) ERC721("Ocean World", "OW") {
        setBaseURI(baseURI);
    }

    //Get token Ids of all tokens owned by _owner
    function walletOfOwner(address _owner)
        external
        view
        returns (uint256[] memory)
    {
        uint256 tokenCount = balanceOf(_owner);

        uint256[] memory tokensId = new uint256[](tokenCount);
        for (uint256 i = 0; i < tokenCount; i++) {
            tokensId[i] = tokenOfOwnerByIndex(_owner, i);
        }

        return tokensId;
    }

    function setBaseURI(string memory baseURI) public onlyOwner {
        baseTokenURI = baseURI;
    }

    function setMerkleRoot(bytes32 _merkleRoot) external onlyOwner {
        merkleRoot = _merkleRoot;
    }

    function setPrice(uint256 _newPrice) external onlyOwner {
        price = _newPrice;
    }

    function setFirstPresalePrice(uint256 _newFirstPresalePrice)
        external
        onlyOwner
    {
        firstPresalePrice = _newFirstPresalePrice;
    }

    function setSecondAndThirdPresalePrice(
        uint256 _newSecondAndThirdPresalePrice
    ) external onlyOwner {
        secondAndThirdPresalePrice = _newSecondAndThirdPresalePrice;
    }

    //Close sale if open, open sale if closed
    function flipSaleState() external onlyOwner {
        saleOpen = !saleOpen;
    }

    function flipFirstPresaleState() external onlyOwner {
        firstPresaleOpen = !firstPresaleOpen;
    }

    function flipSecondPresaleState() external onlyOwner {
        secondPresaleOpen = !secondPresaleOpen;
    }

    function flipThirdPresaleState() external onlyOwner {
        thirdPresaleOpen = !thirdPresaleOpen;
    }

    function withdrawAll() external onlyOwner {
        (bool success, ) = payable(msg.sender).call{
            value: address(this).balance
        }("");
        require(success, "Transfer failed.");
    }

    function firstPresaleMint(bytes32[] calldata _proof, uint256 _count)
        external
        payable
    {
        address _to = msg.sender;
        require(
            merkleRoot != 0,
            "No address is eligible for presale minting yet"
        );
        require(firstPresaleOpen, "First Presale is not open yet");
        require(
            totalSupply() + _count <= 500,
            "Purchase will exceed NFTs alloted for first presale"
        );
        require(
            MerkleProof.verify(
                _proof,
                merkleRoot,
                keccak256(abi.encodePacked(_to))
            ),
            "Address not eligible for first presale mint"
        );
        require(
            _count > 0 && _count <= 2,
            "Minimum 1 & Maximum 2 Ocean World can be minted per transaction"
        );
        require(
            msg.value >= firstPresalePrice * _count,
            "Ether sent with this transaction is not correct"
        );
        for (uint256 i = 0; i < _count; i++) {
            _mint(_to);
        }
    }

    function secondAndThirdPresaleMint(uint256 _count) external payable {
        require(
            totalSupply() + _count <= 9500,
            "Exceeds maximum supply of Ocean World"
        );
        require(
            _count > 0,
            "Minimum 1 Ocean World has to be minted per transaction"
        );

        require(
            secondPresaleOpen || thirdPresaleOpen,
            "Second or third presale is not live yet"
        );
        require(
            _count <= 2,
            "Maximum 2 Ocean World can be minted per transaction"
        );
        require(
            msg.value >= secondAndThirdPresalePrice * _count,
            "Ether sent with this transaction is not correct"
        );
        if (thirdPresaleOpen) {
            require(
                totalSupply() + _count <= 9500,
                "Exceeds maximum supply of Ocean World"
            );
        } else {
            require(
                totalSupply() + _count <= 5000,
                "Exceeds maximum supply of Ocean World"
            );
        }

        address _to = msg.sender;
        for (uint256 i = 0; i < _count; i++) {
            _mint(_to);
        }
    }

    //public mint
    function mint(uint256 _count) external payable {
        require(
            totalSupply() + _count <= MAX_OW,
            "Exceeds maximum supply of Ocean World"
        );
        require(
            _count > 0,
            "Minimum 1 Ocean World has to be minted per transaction"
        );
        address _to = msg.sender;
        if (_to != owner()) {
            require(saleOpen, "Sale is not open yet");
            require(
                _count <= 2,
                "Maximum 2 Ocean World can be minted per transaction"
            );
            require(
                msg.value >= price * _count,
                "Ether sent with this transaction is not correct"
            );
        }

        for (uint256 i = 0; i < _count; i++) {
            _mint(_to);
        }
    }

    function _mint(address _to) private {
        _tokenId.increment();
        uint256 tokenId = _tokenId.current();
        _safeMint(_to, tokenId);
        emit Minted(tokenId);
    }

    function _baseURI() internal view virtual override returns (string memory) {
        return baseTokenURI;
    }
}
function flipSaleState() external onlyOwner {
    saleOpen = !saleOpen;
}

//Close sale if open, open sale if closed