contract dNumber is Ownable, ERC721Full {
    
    
    event NewDecPlace(uint id, uint8 digit, uint8 level, address owner);
    event LevelUp(uint id, uint8 digit, uint8 level);

	using SafeMath for uint256;
	using SafeMath8 for uint8;
	using Strings for string;

	uint public levelUpFee = 0.001 ether;
	uint public newDecPlaceFee = 0.001 ether;
    address proxyRegistryAddress; //for opensea
    string baseURI = "https://dnum.xyz/d/";

	struct DecPlace {
		uint8 digit;
		uint8 level;
	}

	DecPlace[] public decPlaces;

	modifier onlyOwnerOf(uint _decPlaceId) {
		require(msg.sender == ownerOf(_decPlaceId));
		_;
	}

    // "0xa5409ec958c83c3f309868babaca7c86dcb077c1"
	constructor(address _proxyRegistryAddress) 
        
        ERC721Full("d̵  Number","DNUM") public {

        proxyRegistryAddress = _proxyRegistryAddress;

	}
	
	// User generates a new decimal place by choosing a digit value on the Web App.
	
	// INTERNALS
	
	function _newDecPlace(uint8 _digit) internal {
		uint id = decPlaces.push(DecPlace(_digit, 1)) - 1;
		_mint(msg.sender, id);
		emit NewDecPlace(id, _digit, 1, msg.sender);
	}
	
	function _giftDecPlace(uint8 _digit, address _address) internal {
		uint id = decPlaces.push(DecPlace(_digit, 1)) - 1;
		_mint(_address, id);
		emit NewDecPlace(id, _digit, 1, _address);
	}
	
	
	// GENERATES NEW DECIMAL PLACE FOR USER
	function newDecPlace(uint8 _digit) external payable {
        require(msg.value==newDecPlaceFee);
		require(_digit <= 9);
		_newDecPlace(_digit);
	}
	
	// Cost calculation = 1 + 2 + 3 + ..... + "dPlace amount" * NewDecPlaceFee
	function newMultiDecPlaces(uint8[] calldata _digitarray) external payable {
        uint _fee = ((_digitarray.length * (_digitarray.length + 1)) / 2 ) * newDecPlaceFee;
        require(msg.value==_fee);
        for (uint i=0; i<_digitarray.length; i++) {
            require(_digitarray[i] <= 9);
            _newDecPlace(_digitarray[i]);
        }
	}
	
	// MINT A NEW DECIMAL PLACE FOR A LUCKY PERSON
	function giftnewDecPlace(uint8 _digit, address _address) external payable {
        require(msg.value==newDecPlaceFee);
		require(_digit <= 9);
		_giftDecPlace(_digit, _address);
	}
	
	function giftMultiDecPlaces(uint8[] calldata _digitarray, address _address) external payable {
        uint _fee = ((_digitarray.length * (_digitarray.length + 1)) / 2 ) * newDecPlaceFee;
        require(msg.value==_fee);
        for (uint i=0; i<_digitarray.length; i++) {
            require(_digitarray[i] <= 9);
            _giftDecPlace(_digitarray[i], _address);
        }
	}
	
	
    
    // User can change the digit of a decimal place they own by leveling it up
	
	function levelUp(uint _decPlaceId, uint8 _digit) external payable onlyOwnerOf(_decPlaceId){
		require(msg.value==decPlaces[_decPlaceId].level * levelUpFee);
		require(_digit <= 9);
		decPlaces[_decPlaceId].digit = _digit;
		decPlaces[_decPlaceId].level = decPlaces[_decPlaceId].level.add(1);
		emit LevelUp(_decPlaceId, decPlaces[_decPlaceId].digit, decPlaces[_decPlaceId].level);
	}
	
	function multiLevelUp(uint _decPlaceId, uint8[] calldata _digitarray) external payable onlyOwnerOf(_decPlaceId){
		uint _fee = ((_digitarray.length * (_digitarray.length + 1)) / 2 ) * newDecPlaceFee;
        require(msg.value==_fee);
        for (uint i=0; i<_digitarray.length; i++) {
		require(_digitarray[i] <= 9);
		    decPlaces[_decPlaceId].digit = _digitarray[i];
		    decPlaces[_decPlaceId].level = decPlaces[_decPlaceId].level.add(1);
		    emit LevelUp(_decPlaceId, decPlaces[_decPlaceId].digit, decPlaces[_decPlaceId].level);
        }
	}
	
    
    //BERK STUFF
    
    function newDecPlaceBerk(uint8 _digit) external onlyOwner {
        _newDecPlace(_digit);
	}
    
	function newDecPlacesBerk(uint8[] calldata _digitarray) external onlyOwner {
        for (uint i=0; i<_digitarray.length; i++) {
            _newDecPlace(_digitarray[i]);
        }
	}
	
	function giftnewDecPlacesBerk(uint8[] calldata _digitarray, address _address) external onlyOwner {
		for (uint i=0; i<_digitarray.length; i++) {
            _giftDecPlace(_digitarray[i], _address);
        }
		
	}
	
	function levelUpBerk(uint _decPlaceId, uint8 _digit) external onlyOwner {
	    decPlaces[_decPlaceId].digit = _digit;
	    decPlaces[_decPlaceId].level = decPlaces[_decPlaceId].level.add(1);
	    emit LevelUp(_decPlaceId, decPlaces[_decPlaceId].digit, decPlaces[_decPlaceId].level);
	}
	
	function multiLevelUpBerk(uint _decPlaceId, uint8[] calldata _digitarray) external onlyOwner {
	    for (uint i=0; i<_digitarray.length; i++) {
		    decPlaces[_decPlaceId].digit = _digitarray[i];
		    decPlaces[_decPlaceId].level = decPlaces[_decPlaceId].level.add(1);
		    emit LevelUp(_decPlaceId, decPlaces[_decPlaceId].digit, decPlaces[_decPlaceId].level);
        }
	}

	// HELPER FUNCTIONS
	
	function setLevelUpFee(uint _fee) external onlyOwner {
		levelUpFee = _fee;
	}
	
	function setNewDecPlaceFee(uint _fee) external onlyOwner {
		newDecPlaceFee = _fee;
	}

    function setNewBaseURI(string calldata _uri) external onlyOwner{
        baseURI = _uri;
    }
	
	function withdraw() external onlyOwner {
        address _ownerAddress = owner();
        address payable _owner = address(uint160(_ownerAddress)); // converting non payable addr to payable
        _owner.transfer(address(this).balance);
    }
    
    function tokensOfOwner(address owner) public view returns (uint256[] memory) {
        return super._tokensOfOwner(owner);
    }


    // TOKENURI SECTION
    
	
    /**
    * @dev Returns an URI for a given token ID
    * Throws if the tokxen ID does not exist. May return an empty string.
    * @param tokenId uint256 ID of the token to query
    */
    function tokenURI(uint256 tokenId) external view returns (string memory) {
        require(_exists(tokenId), "Token does not exist");
        return Strings.Concatenate(
        baseTokenURI(),
        Strings.UintToString(tokenId)
        );
    }
        
    /**
    * @dev Gets the base token URI
    * @return string representing the base token URI
    */
    function baseTokenURI() public view returns (string memory) {
        return baseURI;
    }

    /**
   * Override isApprovedForAll to whitelist user's OpenSea proxy accounts to enable gas-less listings.
   */
    function isApprovedForAll(
        address owner,
        address operator
    )
        public
        view
        returns (bool)
    {
        // Whitelist OpenSea proxy contract for easy trading.
        ProxyRegistry proxyRegistry = ProxyRegistry(proxyRegistryAddress);
        if (address(proxyRegistry.proxies(owner)) == operator) {
            return true;
        }

        return super.isApprovedForAll(owner, operator);
    }


}
// d
// d is a decentralized number, represented as a decimal number built on Ethereum blockchain, 
// consisting of decimal places representing a digit value, which are owned by users as ERC721 tokens
function _newDecPlace(uint8 _digit) internal {
	uint id = decPlaces.push(DecPlace(_digit, 1)) - 1;
	_mint(msg.sender, id);
	emit NewDecPlace(id, _digit, 1, msg.sender);
}

// User generates a new decimal place by choosing a digit value on the Web App.
// INTERNALS