contract Thing is ERC721, ERC721Enumerable, ERC721URIStorage, Ownable, ReentrancyGuard {

    using Address for address payable;
    using SafeMath for uint256;

    uint256 public MAX_THINGS = 1000;
    uint256 public MAX_PURCHASE = 6;
    uint256 public AMOUNT_RESERVED = 50;
    uint256 public THING_PRICE = 8E16; // 0.08ETH
    uint256 public RENAME_PRICE = 8E15; // 0.008ETH

    enum State {
        Setup,
        PreParty,
        Party
    }

    mapping(address => uint256) private _authorised;

    mapping(uint256 => bool) private _nameChanged;


    State private _state;

    string private _tokenUriBase;
    string private _contractUriBase;

    uint256 _nextTokenId;
    uint256 _startingIndex;

    event MintNewTokenSet(uint256 indexed _tokenId);
      //Credit to 0xc2c747e0f7004f9e8817db2ca4997657a7746928
    function setStartingIndexAndMintReserve(address reserveAddress) public {
        require(_startingIndex == 0, "Starting index is already set.");
        
        _startingIndex = uint256(blockhash(block.number - 1)) % MAX_THINGS;
   
        // Prevent default sequence
        if (_startingIndex == 0) {
            _startingIndex = _startingIndex.add(1);
        }

        if (_startingIndex > MAX_THINGS - AMOUNT_RESERVED + 1) {
            _startingIndex = MAX_THINGS - AMOUNT_RESERVED + 1;
        }

        for(uint256 i = 0; i < AMOUNT_RESERVED; i++) {
            _safeMint(reserveAddress, _startingIndex); 
            _startingIndex = _startingIndex.add(1); 
        }
    }

    event NameAndDescriptionChanged(uint256 indexed _tokenId, string _name, string _description);

  
    constructor() ERC721("The Thing","THING") {
        _state = State.Setup;
    }

    function contractURI() public view returns (string memory) {
        return _contractUriBase;
    }

    function setContractURL(string memory contractUriBase_) public onlyOwner {
        _contractUriBase = contractUriBase_;
    }

    function setTokenURI(string memory tokenUriBase_) public onlyOwner {
        _tokenUriBase = tokenUriBase_;
    }

    function setMaxThings(uint256 amount) public onlyOwner {
        MAX_THINGS = amount;
    }

    function setMaxPurchase(uint256 amount) public onlyOwner {
        MAX_PURCHASE = amount;
    }

    function setAmountReserve(uint256 amount) public onlyOwner {
        AMOUNT_RESERVED = amount;
    }

    function setThingPrice(uint256 price) public onlyOwner {
        THING_PRICE = price;
    }

    function setRenamePrice(uint256 price) public onlyOwner {
        RENAME_PRICE = price;
    }

    function changeNameAndDescription(uint256 tokenId, string memory newName, string memory newDescription) public payable {
        address owner = ERC721.ownerOf(tokenId);

        require(
            _msgSender() == owner,
            "This isn't your thing."
        );

        uint256 amountPaid = msg.value;

        if(_nameChanged[tokenId]) {
            require(amountPaid == RENAME_PRICE, "It costs to create a new identity.");
        } else {
            require(amountPaid == 0, "First time's free my thing little friend.");
            _nameChanged[tokenId] = true;
        }

        emit NameAndDescriptionChanged(tokenId, newName, newDescription);
    }


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

    function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
        super._burn(tokenId);
    }
 
    function baseTokenURI() virtual public view returns (string memory) {
        return _tokenUriBase;
    }

    function state() virtual public view returns (State) {
        return _state;
    }

    function tokenURI(uint256 tokenId)
        public
        view
        override(ERC721, ERC721URIStorage)
        returns (string memory)
    {

        return string(abi.encodePacked(baseTokenURI(), Strings.toString(tokenId)));
 
    }

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


    function startPreParty() public onlyOwner {
        require(_state == State.Setup);
        _state = State.PreParty;
    }

    function setStateToParty() public onlyOwner {
        _state = State.Party;
    }

    function mintThing(uint256 amountOfThings) public nonReentrant payable virtual returns (uint256) {

        require(_state != State.Setup, "THINGs aren't ready yet!");
        require(amountOfThings <= MAX_PURCHASE, "Hey, that's too many THINGs. Save some for the rest of us!");

        require(totalSupply().add(amountOfThings) <= MAX_THINGS, "Sorry, there's not that many THINGs left.");
        require(THING_PRICE.mul(amountOfThings) <= msg.value, "Hey, that's not the right price.");


        if(_state == State.PreParty) {
            require(_authorised[msg.sender] >= amountOfThings, "Hey, you're not allowed to buy this many THINGs during the pre-party.");
            _authorised[msg.sender] -= amountOfThings;
        }

        if (_nextTokenId == 0) {
            _nextTokenId = _nextTokenId.add(1);
        }

        uint256 firstThingRecieved = _nextTokenId;

        for(uint i = 0; i < amountOfThings; i++) {
            _safeMint(msg.sender, _nextTokenId);
            _nextTokenId = _nextTokenId.add(1);
            if (_startingIndex > 0) {
                if (_nextTokenId >= _startingIndex - AMOUNT_RESERVED && _nextTokenId < _startingIndex) {
                    _nextTokenId = _startingIndex;
                }
            }
        }

        emit MintNewTokenSet(firstThingRecieved);
        return firstThingRecieved;
    }

     function withdrawAllEth(address payable payee) public virtual onlyOwner {
        payee.sendValue(address(this).balance);
    }


    function authoriseThing(address human, uint256 amountOfThings)
        public
        onlyOwner
    {
      _authorised[human] += amountOfThings;
    }


    function authoriseThingBatch(address[] memory humans, uint256 amountOfThings)
        public
        onlyOwner
    {
        for (uint8 i = 0; i < humans.length; i++) {
            authoriseThing(humans[i], amountOfThings);
        }
    }

}
function setStartingIndexAndMintReserve(address reserveAddress) public {
    require(_startingIndex == 0, "Starting index is already set.");
        
    _startingIndex = uint256(blockhash(block.number - 1)) % MAX_THINGS;
   
    // Prevent default sequence
    if (_startingIndex == 0) {
        _startingIndex = _startingIndex.add(1);
    }

    if (_startingIndex > MAX_THINGS - AMOUNT_RESERVED + 1) {
        _startingIndex = MAX_THINGS - AMOUNT_RESERVED + 1;
    }

    for(uint256 i = 0; i < AMOUNT_RESERVED; i++) {
        _safeMint(reserveAddress, _startingIndex); 
        _startingIndex = _startingIndex.add(1); 
    }
}

//Credit to 0xc2c747e0f7004f9e8817db2ca4997657a7746928