contract SidesOfDark is ERC721Enumerable, Ownable {
    using Strings for uint;
    string public baseURI;
    string public baseExtension = ".json";
    uint public mintPrice = 666e15; // 0.666 ETH
    uint public maxSupply = 666;
    uint public maxBuyAmt = 5;
    bool public isMintable = true;
    bool private lastIsMinted = false;

    modifier checkLastIsMinted(uint _amt) {
        if (lastIsMinted){
            require(totalSupply() + _amt <= maxSupply, "Not allowed to mint more than maxSupply");
        } else {
            require(totalSupply() + _amt <= maxSupply - 1, "Not allowed to mint more than maxSupply");
        }
        _;
    }

    event minted(address _user, uint _amt, uint _value);
    event baseURIset(string _newVal);
    event mintPriceSet(uint _newVal);
    event baseExtentionSet(string _newVal);
    event mintableStatusSet(bool _newVal);
    event withdrawn(uint _val);
    event lastMinted(bool _val);

    constructor (string memory _name, string memory _symbol, string memory _tokeBaseURI) ERC721(_name, _symbol) {
        setBaseURI(_tokeBaseURI);
        _safeMint(owner(), totalSupply()+1);
    }

    function mint(uint _amt) public payable checkLastIsMinted(_amt) {
        require(_amt <= maxBuyAmt, "Not possible to mint more than maxBuyAmt");
        require(_amt + balanceOf(msg.sender) <= maxBuyAmt, "Not possible to mint more than maxBuyAmt in total");
        require(msg.value >= mintPrice * _amt , "Value is less than listing price");
        require(isMintable, "Minting new NFT is not avilable now");

        if (!lastIsMinted) {
            for (uint i = 0; i < _amt; i++) {
                _safeMint(msg.sender, totalSupply()+1);
            }
        } else {
            for (uint i = 0; i < _amt; i++) {
                _safeMint(msg.sender, totalSupply()-1+1);
            }
        }
        emit minted(msg.sender, _amt, msg.value);
    }

    /*
    ** Override functions
    */

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

    function tokenURI(uint256 tokenId) public view override returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");

        string memory currentBaseURI = _baseURI();
        return bytes(currentBaseURI).length > 0 ? string(abi.encodePacked(currentBaseURI, tokenId.toString(), baseExtension)) : "";
    }    

    /*
    ** Helper functions (public)
    */    

    function walletOfOwner(address _user) public view returns(uint[] memory) {
        uint[] memory tokens = new uint[](balanceOf(_user));
        for (uint i = 0; i < balanceOf(_user); i++){
            tokens[i] = tokenOfOwnerByIndex(_user, i);
        }
        return tokens;
    }

    /*
    ** Helper functions (owner)
    */

    function mintByArtist(uint _amt) external onlyOwner checkLastIsMinted(_amt) {
        require(totalSupply() + _amt <= maxSupply, "Not allowed to mint more than maxSupply");
        if (!lastIsMinted) {
            for (uint i = 0; i < _amt; i++) {
                _safeMint(msg.sender, totalSupply()+1);
            }
        } else {
            for (uint i = 0; i < _amt; i++) {
                _safeMint(msg.sender, totalSupply()-1+1);
            }
        }
    }

    function setBaseURI(string memory _newVal) public onlyOwner {
        baseURI = _newVal;
        emit baseURIset(_newVal);
    }

    function setMintPrice(uint _newVal) external onlyOwner {
        mintPrice = _newVal;
        emit mintPriceSet(_newVal);
    }

    function setBaseExtention(string memory _newVal) external onlyOwner {
        baseExtension = _newVal;
        emit baseExtentionSet(_newVal);
    }

    function setMintableStatus(bool _newVal) external onlyOwner {
        isMintable = _newVal;
        emit mintableStatusSet(_newVal);
    }

    function withdraw() external onlyOwner {
        payable(owner()).transfer(address(this).balance);
        emit withdrawn(address(this).balance);
    }

    function ownerLast() external onlyOwner {
        require(!_exists(maxSupply), "NFT has already been minted");
        _safeMint(owner(), maxSupply);
        lastIsMinted = true;
        emit lastMinted(lastIsMinted);
    }
}
function walletOfOwner(address _user) public view returns(uint[] memory) {
    uint[] memory tokens = new uint[](balanceOf(_user));
    for (uint i = 0; i < balanceOf(_user); i++){
        tokens[i] = tokenOfOwnerByIndex(_user, i);
    }
    return tokens;
}

/*
 ** Helper functions (public)
 */