contract CryptostampGoldenUnicorn is ERC721SimpleMapsURI {
    using SafeMath for uint256;

    address public createControl;
    address public shippingControl;
    address public tokenAssignmentControl;

    uint256 immutable finalSupply;

    mapping(address => uint256) public signedTransferNonce;

    event CreateControlTransferred(address indexed previousCreateControl, address indexed newCreateControl);
    event ShippingControlTransferred(address indexed previousShippingControl, address indexed newShippingControl);
    event TokenAssignmentControlTransferred(address indexed previousTokenAssignmentControl, address indexed newTokenAssignmentControl);
    event SignedTransfer(address operator, address indexed from, address indexed to, uint256 indexed tokenId, uint256 signedTransferNonce);

    constructor(address _createControl, address _shippingControl, address _tokenAssignmentControl, uint256 _finalSupply)
    ERC721SimpleMapsURI("Crypto stamp Golden Unicorn", "CSGU", "https://test.crypto.post.at/CSGU/meta/")
    public
    {
        createControl = _createControl;
        require(address(createControl) != address(0x0), "You need to provide an actual createControl address.");
        shippingControl = _shippingControl;
        require(address(shippingControl) != address(0x0), "You need to provide an actual shippingControl address.");
        tokenAssignmentControl = _tokenAssignmentControl;
        require(address(tokenAssignmentControl) != address(0x0), "You need to provide an actual tokenAssignmentControl address.");
        finalSupply = _finalSupply;
    }

    modifier onlyCreateControl()
    {
        require(msg.sender == createControl, "createControl key required for this function.");
        _;
    }

    modifier onlyShippingControl() {
        require(msg.sender == shippingControl, "shippingControl key required for this function.");
        _;
    }

    modifier onlyTokenAssignmentControl() {
        require(msg.sender == tokenAssignmentControl, "tokenAssignmentControl key required for this function.");
        _;
    }

    modifier onlyMinter()
    {
        // Both the actual create control key and the shipping control key that delivers the on-chain raffle can mint Golden Unicorns.
        require(msg.sender == createControl || msg.sender == shippingControl, "createControl or shippingControl key required for this function.");
        _;
    }

    modifier requireMinting() {
        require(mintingFinished() == false, "This call only works when minting is not finished.");
        _;
    }

    /*** Enable adjusting variables after deployment ***/

    function transferTokenAssignmentControl(address _newTokenAssignmentControl)
    public
    onlyTokenAssignmentControl
    {
        require(_newTokenAssignmentControl != address(0), "tokenAssignmentControl cannot be the zero address.");
        emit TokenAssignmentControlTransferred(tokenAssignmentControl, _newTokenAssignmentControl);
        tokenAssignmentControl = _newTokenAssignmentControl;
    }

    function transferCreateControl(address _newCreateControl)
    public
    onlyCreateControl
    {
        require(_newCreateControl != address(0), "createControl cannot be the zero address.");
        emit CreateControlTransferred(createControl, _newCreateControl);
        createControl = _newCreateControl;
    }

    function transferShippingControl(address _newShippingControl)
    public
    onlyShippingControl
    {
        require(_newShippingControl != address(0), "shippingControl cannot be the zero address.");
        emit ShippingControlTransferred(shippingControl, _newShippingControl);
        shippingControl = _newShippingControl;
    }

    /*** Base functionality: minting, URI, property getters, etc. ***/

    // Override totalSupply() so it reports the target final supply.
    /**
     * @dev Gets the total amount of tokens stored by the contract.
     * @return uint256 representing the total amount of tokens
     */
    function totalSupply()
    public view override
    returns (uint256) {
        return finalSupply;
    }

    /**
     * @dev Gets the actually minted amount of tokens in the contract.
     * @return uint256 representing the minted amount of tokens
     */
    function mintedSupply()
    public view
    returns (uint256) {
        return super.totalSupply();
    }

    // Issue a new crypto stamp asset, giving it to a specific owner address.
    // As appending the ID into a URI in Solidity is complicated, generate both
    // externally and hand them over to the asset here.
    function create(uint256 _tokenId, address _owner)
    public
    onlyMinter
    requireMinting
    {
        require(_tokenId < finalSupply, "Cannot mint token with too high ID");
        // _mint already ends up checking if owner != 0 and that tokenId doesn't exist yet.
        _mint(_owner, _tokenId);
    }

    // Batch-issue multiple crypto stamps with adjacent IDs.
    function createMulti(uint256 _tokenIdStart, address[] memory _owners)
    public
    onlyMinter
    requireMinting
    {
        uint256 addrcount = _owners.length;
        require(_tokenIdStart + addrcount - 1 < finalSupply, "Cannot mint token with too high ID");
        for (uint256 i = 0; i < addrcount; i++) {
            // Make sure this is in sync with what create() does.
            _mint(_owners[i], _tokenIdStart + i);
        }
    }

    // Determine if the creation/minting process is done.
    function mintingFinished()
    public view
    returns (bool)
    {
        return (super.totalSupply() >= finalSupply);
    }

    // Set new base for the token URI.
    function setBaseURI(string memory _newBaseURI)
    public
    onlyMinter
    {
        super._setBaseURI(_newBaseURI);
    }

    // Returns whether the specified token exists
    function exists(uint256 tokenId)
    public view
    returns (bool) {
        return _exists(tokenId);
    }

    /*** Special properties of Golden Unicorns ***/

    function sourceSale(uint256 _tokenId)
    public view
    returns (bool) {
        require(_tokenId < finalSupply, "Property query for nonexistent token");
        return _tokenId >= finalSupply / 2 - 1 && _tokenId < finalSupply - 1;
    }

    function sourceRaffle(uint256 _tokenId)
    public view
    returns (bool) {
        return !sourceSale(_tokenId);
    }

    function sourceOnChainRaffle(uint256 _tokenId)
    public view
    returns (bool) {
        require(_tokenId < finalSupply, "Property query for nonexistent token");
        return _tokenId <= finalSupply * 3 / 10 - 2;
    }

    function isLastUnicorn(uint256 _tokenId)
    public view
    returns (bool) {
        require(_tokenId < finalSupply, "Property query for nonexistent token");
        return _tokenId == finalSupply - 1;
    }

    /*** Allows any user to initiate a transfer with the signature of the current stamp owner  ***/

    // Outward-facing function for signed transfer: assembles the expected data and then calls the internal function to do the rest.
    // Can called by anyone knowing about the right signature, but can only transfer to the given specific target.
    function signedTransfer(uint256 _tokenId, address _to, bytes memory _signature)
    public
    {
        address currentOwner = ownerOf(_tokenId);
        // The signed data contains the token ID, the transfer target and a nonce.
        bytes32 data = keccak256(abi.encodePacked(address(this), this.signedTransfer.selector, currentOwner, _to, _tokenId, signedTransferNonce[currentOwner]));
        _signedTransferInternal(currentOwner, data, _tokenId, _to, _signature);
    }

    // Outward-facing function for operator-driven signed transfer: assembles the expected data and then calls the internal function to do the rest.
    // Can transfer to any target, but only be called by the trusted operator contained in the signature.
    function signedTransferWithOperator(uint256 _tokenId, address _to, bytes memory _signature)
    public
    {
        address currentOwner = ownerOf(_tokenId);
        // The signed data contains the operator, the token ID, and a nonce. Note that we use the selector of the external function here!
        bytes32 data = keccak256(abi.encodePacked(address(this), this.signedTransferWithOperator.selector, msg.sender, currentOwner, _tokenId, signedTransferNonce[currentOwner]));
        _signedTransferInternal(currentOwner, data, _tokenId, _to, _signature);
    }

    // Actually check the signature and perform a signed transfer.
    function _signedTransferInternal(address _currentOwner, bytes32 _data, uint256 _tokenId, address _to, bytes memory _signature)
    internal
    {
        bytes32 hash = ECDSA.toEthSignedMessageHash(_data);
        address signer = ECDSA.recover(hash, _signature);
        require(signer == _currentOwner, "Signature needs to match parameters, nonce, and current owner.");
        // Now that we checked that the signature is correct, do the actual transfer and increase the nonce.
        emit SignedTransfer(msg.sender, _currentOwner, _to, _tokenId, signedTransferNonce[_currentOwner]);
        signedTransferNonce[_currentOwner] = signedTransferNonce[_currentOwner].add(1);
        _safeTransfer(_currentOwner, _to, _tokenId, "");
    }

    /*** Enable reverse ENS registration ***/

    // Call this with the address of the reverse registrar for the respecitve network and the ENS name to register.
    // The reverse registrar can be found as the owner of 'addr.reverse' in the ENS system.
    // See https://docs.ens.domains/ens-deployments for address of ENS deployments, e.g. Etherscan can be used to look up that owner on those.
    // namehash.hash("addr.reverse") == "0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2"
    // Ropsten: ens.owner(namehash.hash("addr.reverse")) == "0x6F628b68b30Dc3c17f345c9dbBb1E483c2b7aE5c"
    // Mainnet: ens.owner(namehash.hash("addr.reverse")) == "0x084b1c3C81545d370f3634392De611CaaBFf8148"
    function registerReverseENS(address _reverseRegistrarAddress, string calldata _name)
    external
    onlyTokenAssignmentControl
    {
       require(_reverseRegistrarAddress != address(0), "need a valid reverse registrar");
       ENSReverseRegistrarI(_reverseRegistrarAddress).setName(_name);
    }

    /*** Make sure currency doesn't get stranded in this contract ***/

    // If this contract gets a balance in some ERC20 contract after it's finished, then we can rescue it.
    function rescueToken(IERC20 _foreignToken, address _to)
    external
    onlyTokenAssignmentControl
    {
        _foreignToken.transfer(_to, _foreignToken.balanceOf(address(this)));
    }

    // If this contract gets a balance in some ERC721 contract after it's finished, then we can rescue it.
    function approveNFTrescue(IERC721 _foreignNFT, address _to)
    external
    onlyTokenAssignmentControl
    {
        _foreignNFT.setApprovalForAll(_to, true);
    }

    // Make sure this contract cannot receive ETH.
    receive()
    external payable
    {
        revert("The contract cannot receive ETH payments.");
    }
}
/* The inheritance is very much the same as OpenZeppelin's ERC721Full,
 * but using simplified (and cheaper) versions of ERC721Enumerable and ERC721Metadata */
function rescueToken(IERC20 _foreignToken, address _to)
external
onlyTokenAssignmentControl
{
    _foreignToken.transfer(_to, _foreignToken.balanceOf(address(this)));
}

// If this contract gets a balance in some ERC20 contract after it's finished, then we can rescue it.