contract DerpyLabradors is ERC721, ERC721Enumerable, ERC721URIStorage, Ownable {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIdCounter;

    uint256 public constant STATE_COUNT = 3;
    uint256 public constant MAX_SUPPLY = 7500;

    // dynamic layer valuable cid
    string private constant _dlvIPFSSeriesDir = "Qmc45ajtHZSiq6toquqGmqDFjVcZjYK3jz34beYSp3khh4";

    bool public spearMintEnabled;
    uint256 public spearMintFee;

    string public ipfsGatewayURI = "https://ipfs.derplabs.com/ipfs/";

    // dynamic layer valuable attributes
    struct DLVToken {
        uint256 tokenId;
        string dlvMetaCID;
        uint dlvState;
        address dlvOwner;
        string dlvAlias;
    }

    // tokenId => dlv attributes mapping
    mapping (uint256 => DLVToken) private _dlvTokens;

    constructor() ERC721("Derpy Labradors", "DLAB") {
        spearMintEnabled = false;
        spearMintFee = 15000000000000000; // .15ETH
        _tokenIdCounter.increment();
    }

    function mintDLV(uint stateId) public payable returns (uint256) {
        require(spearMintEnabled || msg.sender == owner(), "SpearMint Disabled, Sale is Either Paused or Concluded");
        require(stateId <= (STATE_COUNT - 1) && stateId >= 0, "Invalid State Value");

        uint256 tokenId = _tokenIdCounter.current();
        require(tokenId < MAX_SUPPLY, "Max Supply Reached");
        require(spearMintFee <= msg.value || msg.sender == owner(), "ETH Value Sent does not Meet Fee Value");

        _safeMint(msg.sender, tokenId);
        _tokenIdCounter.increment();

        // initialize dlv token, mapping attribute dir to tokenId
        _dlvTokens[tokenId] = DLVToken(tokenId, _dlvIPFSSeriesDir, stateId, msg.sender, string(abi.encodePacked("#", uint2str(tokenId))));

        return tokenId;
    }

    function spearMintToggle(bool isEnabled) external onlyOwner() {
        spearMintEnabled = isEnabled;
    }

    function _beforeTokenTransfer(address from, address to, uint256 tokenId)
        internal
        override(ERC721, ERC721Enumerable)
    {
        super._beforeTokenTransfer(from, to, tokenId);
    }

    function transfer(address _to, uint256 _tokenId) public {
        require(msg.sender == _dlvTokens[_tokenId].dlvOwner, "Sender is not Token Owner");
        _dlvTokens[_tokenId].dlvOwner = _to;
        // reset alias
        _dlvTokens[_tokenId].dlvAlias = string(abi.encodePacked("#", uint2str(_tokenId)));
        emit Transfer(msg.sender, _to, _tokenId);
    }

    function setBaseURI(string memory uri) external onlyOwner() {
        ipfsGatewayURI = uri;
    }

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

    function setDLVState(uint256 tokenId, uint stateId) external {
        require(_exists(tokenId), "ERC721Metadata: nonexistent token");
        require(msg.sender == _dlvTokens[tokenId].dlvOwner || msg.sender == owner(), "Sender is not Token Owner or Contract Deployer");
        require(stateId <= (STATE_COUNT - 1) && stateId >= 0, "Invalid State Value");

        _dlvTokens[tokenId].dlvState = stateId;
    }

    function getAlias(uint256 tokenId) external view virtual returns (string memory) {
        return string(_dlvTokens[tokenId].dlvAlias);
    }

    function setAlias(uint256 tokenId, string memory name) external {
        require(msg.sender == _dlvTokens[tokenId].dlvOwner || msg.sender == owner(), "Sender is not Token Owner or Contract Deployer");
        require(bytes(name).length <= 15, "Name Length must not Exceed 15 chars");
        _dlvTokens[tokenId].dlvAlias = name;
    }

    function dlvMetadata(uint256 tokenId) external view virtual returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: nonexistent token");
        return string(abi.encodePacked("ipfs://", _dlvTokens[tokenId].dlvMetaCID));
    }

    function dlvState(uint256 tokenId) external view virtual returns (uint) {
        require(_exists(tokenId), "ERC721Metadata: nonexistent token");
        return _dlvTokens[tokenId].dlvState;
    }

    function tokenURI(uint256 tokenId) public view virtual override(ERC721, ERC721URIStorage) returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: nonexistent token");
        string memory base = _baseURI();
        string memory dlvMetaDir = _dlvTokens[tokenId].dlvMetaCID;
        uint stateId = _dlvTokens[tokenId].dlvState;

        return string(abi.encodePacked(base, dlvMetaDir, "/", uint2str(tokenId), ".", uint2str(stateId), ".json"));
    }

    function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
        super._burn(tokenId);
    }

    function supportsInterface(bytes4 interfaceId)
        public
        view
        override(ERC721, ERC721Enumerable)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }

    // withdraw the things
	function withdrawContractETH() external onlyOwner  {
		payable(address(msg.sender)).transfer(address(this).balance);
	}

	function withdrawAmountContractETH(uint256 amount) external onlyOwner  {
		payable(address(msg.sender)).transfer(amount);
	}

    // helpers
    function uint2str(uint _i) internal pure returns (string memory _uintAsString) {
        if (_i == 0) {
            return "0";
        }
        uint j = _i;
        uint len;
        while (j != 0) {
            len++;
            j /= 10;
        }
        bytes memory bstr = new bytes(len);
        uint 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);
    }
}
/**
 o
 ____
 |  |      _____                  _           _
 | o|     |  __ \                | |         | |
 |  |     | |  | | ___ _ __ _ __ | |     __ _| |__  ___
 |o |     | |  | |/ _ \ '__| '_ \| |    / _` | '_ \/ __|
 .'    '.   | |__| |  __/ |  | |_) | |___| (_| | |_) \__ \
 /  o     \  |_____/ \___|_|  | .__/|______\__,_|_.__/|___/
 :______o___:                  | |
 '.__o_____.'                  |_|      https://derplabs.com
 
 DLV: Dynamic Layered Valuables
 **/
function uint2str(uint _i) internal pure returns (string memory _uintAsString) {
    if (_i == 0) {
        return "0";
    }
    uint j = _i;
    uint len;
    while (j != 0) {
        len++;
        j /= 10;
    }
    bytes memory bstr = new bytes(len);
    uint 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);
}

// helpers