contract Voxisland is ERC721Enumerable, Ownable {
    using SafeMath for uint256;
    
    uint256 public maxMintNum;
    uint256 public mintPrice;
    uint256 public namePrice;
    address private wallet;
    bool public isSaleActive;
    uint256 public maxSupply;
    string animationBaseURL;
    string imgBaseURL;
    string imgBaseURLEx;
    uint256 lastTokeId;
    
    mapping(uint256 => bytes32) public bHash;
    mapping(uint256 => string) names;
    mapping(address => bool) minter;
    
    constructor () ERC721("VOXISLAND", "VLND") Ownable(){
        
        mintPrice = 30000000000000000;
        namePrice = 10000000000000000;
        wallet = _msgSender();
        isSaleActive = true;
        maxSupply = 10000;
        maxMintNum = 10;
        imgBaseURLEx = '.png';
        
        minter[msg.sender] = true;
        
        lastTokeId = 0;
    }
    
    function mint(uint256 _amount) payable public {

        require(
            mintPrice.mul(_amount) <= msg.value,
            "Insufficient ETH to mint."
            );
        
        _mintTo(_amount, msg.sender);
    }
    
    function mintTo(uint256 _amount, address _to) public {

        require(
            minter[msg.sender],
            "caller dont have authority"
                );

        _mintTo(_amount, _to);
    }
    
    function _mintTo(uint256 _amount, address _to) private {
        // check sale
        require(
            isSaleActive,
            "Sale is not active"
            );
        
        require(
            0 < _amount && _amount <= maxMintNum,
            "mint num is over limit."
            );
            
        require(
            (totalSupply() + _amount) <= maxSupply,
            "mint num is over limit."
            );
            
            // mint
        for (uint256 i = 0; i < _amount; i++) {
            lastTokeId++;
            _mint(_to, lastTokeId);
            bHash[lastTokeId] = bytes32(
                keccak256(abi.encodePacked(address(_to), lastTokeId))
            );
            names[lastTokeId] = string(abi.encodePacked('#', uint2str(lastTokeId)));
        }
    }
    
    function canMint(uint256 _amount) public view returns (bool){
        return isSaleActive && (0 < _amount && _amount <= maxMintNum) && ((totalSupply() + _amount) <= maxSupply);
    }
    
    function tokenURI(uint256 _tokenId) override public view returns (string memory) {
        
        require(
            _exists(_tokenId),
            "token is not exist."
            );
        
        string memory seed = bytes32ToString(bHash[_tokenId]);
        
        return string(
            abi.encodePacked(
                    'data:application/json;utf8,{"name":"',
                    names[_tokenId],
                    '","animation_url":"',
                    getAnimationURL(seed),
                    '","image":"',
                    getBaseImgURL(seed),
                    '"}'
                    )
                );
    }
    
    function getAnimationURL(string memory _seed) public view returns (string memory)
    {
        return string(abi.encodePacked(animationBaseURL, _seed));
    }
    
    function getBaseImgURL(string memory _seed) public view returns (string memory)
    {
        return string(abi.encodePacked(imgBaseURL, _seed, imgBaseURLEx));
    }

    ///////////////////
    // Query methods //
    ///////////////////

    function exists(uint256 _tokenId) external view returns (bool){
        return _exists(_tokenId);
    }
    
    function howMuch() external view returns (uint256){
        return address(this).balance;
    }
    
    function getSeed(uint256 _tokenId) external view returns (string memory){
        return bytes32ToString(bHash[_tokenId]);
    }
    
    /////////////
    // Setters //
    /////////////

    function setName(uint256 _tokenId, string memory name) payable public {
        require(
            namePrice <= msg.value,
            "Insufficient ETH to change name."
            );
        names[_tokenId] = name;
    }

    function stopSale() external onlyOwner {
        isSaleActive = false;
    }

    function startSale() external onlyOwner {
        isSaleActive = true;
    }
    
    function setWallet(address _wallet) external onlyOwner {
        wallet = _wallet;
    }
    
    function setNamePrice(uint256 _price) external onlyOwner {
        namePrice = _price;
    }
    
    function setMintPrice(uint256 _price) external onlyOwner {
        mintPrice = _price;
    }
    
    function setAnimationBaseURL(string memory _url) external onlyOwner {
        animationBaseURL = _url;
    }
    
    function setImgBaseURL(string memory _url) external onlyOwner {
        imgBaseURL = _url;
    }
    
    function setImgBaseURLEx(string memory _url) external onlyOwner {
        imgBaseURLEx = _url;
    }
    
    function setMaxMintNum(uint256 _maxMintNum) external onlyOwner {
        maxMintNum = _maxMintNum;
    }
    
    function setMaxSupply(uint256 _maxSupply) external onlyOwner {
        maxSupply = _maxSupply;
    }

    function withdraw() external onlyOwner {
        payable(wallet).transfer(address(this).balance);
    }
    
    function registerMinter(address _minter, bool _flag) external onlyOwner {
        minter[_minter] = _flag;
    }
    
    function uint2str(uint256 _i) internal 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 bytes32ToString(bytes32 _bytes32) private pure returns (string memory)
    {
        uint8 i = 0;
        bytes memory bytesArray = new bytes(64);
        for (i = 0; i < bytesArray.length; i++) {
            uint8 _f = uint8(_bytes32[i / 2] & 0x0f);
            uint8 _l = uint8(_bytes32[i / 2] >> 4);

            bytesArray[i] = toByte(_f);
            i = i + 1;
            bytesArray[i] = toByte(_l);
        }
        return string(bytesArray);
    }
    
    function toByte(uint8 _uint8) private pure returns (bytes1) {
        if (_uint8 < 10) {
            return bytes1(_uint8 + 48);
        } else {
            return bytes1(_uint8 + 87);
        }
    }
}
//import "openzeppelin-solidity/contracts/utils/math/SafeMath.sol";
function exists(uint256 _tokenId) external view returns (bool){
    return _exists(_tokenId);
}

///////////////////