contract MakeAWishLeiHakuAloha is ERC721, Ownable, ReentrancyGuard {
    uint256 public constant MAX_SUPPLY = 5000;
    uint256 public constant PER_ORDER_LIMIT = 10;
    uint256 public constant RESERVE_QTY = 50;
    string public constant BASE_URI = "ipfs://QmbPn45u2NwiTooboiS5VjbiqHFmSoR6t3QRLWggzggCpa";

    bool public isActive;
    uint256 public salePrice;

    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    uint256 private _qtyReserved;

    constructor() ERC721("Make a Wish Lei Haku Aloha", "MWLHA") {
        isActive = false;
        _qtyReserved = 0;
        salePrice = 5000000000000000 wei;
    }

    function mint(uint256 qty) public payable nonReentrant {
        require(isActive, "Contract is not active");
        require(qty < PER_ORDER_LIMIT + 1, "Cannot purchase more than the per-order limit");
        require((totalSold() + qty + RESERVE_QTY) < MAX_SUPPLY + 1, "Requested quantity would exceed total supply");
        require(msg.value == (salePrice * qty), "Incorrect payment amount");

        for (uint256 i = 0; i < qty; i++) {
            _tokenIds.increment();
            uint256 mintIndex = totalSold();
            _safeMint(msg.sender, mintIndex);
        }
    }

    function totalSupply() public pure returns (uint256) {
        return MAX_SUPPLY;
    }

    function totalSold() public view returns (uint256) {
        return _tokenIds.current();
    }

    function setActive(bool val) public onlyOwner {
        isActive = val;
    }

    function _baseURI() internal view virtual override returns (string memory) {
        return BASE_URI;
    }

    function tokenURI(uint256 tokenId)
        public
        view
        override
        returns (string memory)
    {
        require(tokenId > 0, "Token Id = 0");

        string memory baseURI = _baseURI();
        return
            bytes(baseURI).length > 0
                ? string(abi.encodePacked(baseURI, "/", uint2str(tokenId), ".json"))
                : "";
    }

    function distributeFromReserve(uint256 qty, address recipient) external onlyOwner nonReentrant {
        require(qty > 0,"Qty must be more than zero");
        require(recipient != address(0), "Recipient cannot be a zero address");
        require(_qtyReserved + qty < RESERVE_QTY + 1, "Qty would exceed reserved quantity");
        require(qty + _tokenIds.current() < MAX_SUPPLY + 1, "Qty would exceed the total supply");

        for (uint256 i = 0; i < qty; i++) {
            _tokenIds.increment();
            uint256 mintIndex = totalSold();
            _qtyReserved += 1;
            _safeMint(recipient, mintIndex);
        }
    }

    function remainingReserve() public view returns(uint256){
        return RESERVE_QTY - _qtyReserved;
    }

    function transferFunds(address recipient, uint256 amt) external onlyOwner nonReentrant {
        require(recipient != address(0), "Recipient cannot be a zero address");
        require(amt < address(this).balance+1,"Amt cannot be more than the current balance");

        payable(recipient).transfer(amt);
    }

    function cashOut() external onlyOwner nonReentrant {
        payable(msg.sender).transfer(address(this).balance);
    }

    // https://stackoverflow.com/a/47137572
    function uint2str(uint256 _i)
        private
        pure
        returns (string memory _uintAsString)
    {
        if (_i == 0) {
            return "0";
        }
        uint256 j = _i;
        uint256 len;
        while (j != 0) {
            len++;
            j /= 10;
        }
        bytes memory bstr = new bytes(len);
        uint256 k = len;
        while (_i != 0) {
            k = k - 1;
            uint8 temp = (48 + uint8(_i - (_i / 10) * 10));
            bytes1 b1 = bytes1(temp);
            bstr[k] = b1;
            _i /= 10;
        }
        return string(bstr);
    }
}
function uint2str(uint256 _i)
    private
    pure
    returns (string memory _uintAsString)
{
    if (_i == 0) {
        return "0";
    }
    uint256 j = _i;
    uint256 len;
    while (j != 0) {
        len++;
        j /= 10;
    }
    bytes memory bstr = new bytes(len);
    uint256 k = len;
    while (_i != 0) {
        k = k - 1;
        uint8 temp = (48 + uint8(_i - (_i / 10) * 10));
        bytes1 b1 = bytes1(temp);
        bstr[k] = b1;
        _i /= 10;
    }
    return string(bstr);
}

// https://stackoverflow.com/a/47137572