contract Vizmesh is ERC721, Ownable {
    using SafeMath for uint;
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    //STORAGE AND CONSTANTS

    uint16 public constant tokenSupplyLimit = 35144;
    uint16 private constant frmsPerEra = 5000;
    uint256 private _lastMintBlockTimestamp = 0;

    mapping (uint256 => frm) public frms;
    mapping (uint256 => era) public eras;

    //EVENTS

    event logSetFrmPosition(uint256 _tokenId, int16 _x, int16 _y);
    event logSetFrmNft(uint256 _tokenId, address _displayedNftContractAddress, uint256 _displayedNftTokenId);
    event logSetFrmNftUri(uint256 _tokenId, string _tokenUri);
    event logMintFrm(uint256 _tokenId);

    //STRUCT DEFINITIONS

    struct frm
    {
        int16 x;
        int16 y;
        uint256 positionSetBlockTimestamp;
        uint256 nftSetBlockTimestamp;
        address nftContractAddress;
        uint256 nftTokenId;
        string nftUri;
        string frmUri;
        
        bool useNftUri;
        bool isPaused;
    }

    struct era
    {
        string name;
        uint16 tokens100000xPerMinuteRate;
        uint16 addMinutesToLastMintTimestamp;
    }

    //CONSTRUCTOR

    constructor() ERC721("Vizmesh Frames", "FRM")
    {
        eras[0] = era("Impressionist",  486,  206);
        eras[1] = era("Fauvist",        340,  295);
        eras[2] = era("Expressionist",  238,  421);
        eras[3] = era("Cubist",         166,  603);
        eras[4] = era("Futurist",       116,  863);
        eras[5] = era("Constructivist",  81, 1235);
        eras[6] = era("Surrealist",      57, 1755);
    }

    //FRM OWNER FUNCTIONS

    function setFrmPosition(uint256 _tokenId, int16 _x, int16 _y)
        external
    {
        require(_exists(_tokenId), "Token must exists.");
        require(msg.sender == ownerOf(_tokenId) || msg.sender == owner(), "Must be token or contract owner.");
        require(msg.sender == owner() || frms[_tokenId].isPaused == false, "FRM must not be paused.");
        

        frms[_tokenId].positionSetBlockTimestamp = _getBlockTimestamp(); //Set this before position
        frms[_tokenId].x = _x;
        frms[_tokenId].y = _y;

        emit logSetFrmPosition(_tokenId, _x, _y);
    }

    function setFrmNft(uint256 _tokenId, address _nftContractAddress, uint256 _nftTokenId)
        external
    {
        require(_exists(_tokenId), "Token must exists.");
        require(msg.sender == ownerOf(_tokenId) || msg.sender == owner(), "Must be token or contract owner.");
        require(msg.sender == owner() || frms[_tokenId].isPaused == false, "FRM must not be paused.");

        frms[_tokenId].nftSetBlockTimestamp = _getBlockTimestamp();
        frms[_tokenId].nftContractAddress = _nftContractAddress;
        frms[_tokenId].nftTokenId = _nftTokenId;

        frms[_tokenId].useNftUri = false;

        emit logSetFrmNft(_tokenId, _nftContractAddress, _nftTokenId);
    }

    function setFrmNftUri(uint256 _tokenId, string calldata _nftUri)
        external
    {
        require(_exists(_tokenId), "Token must exists.");
        require(msg.sender == ownerOf(_tokenId) || msg.sender == owner(), "Must be token or contract owner.");
        require(msg.sender == owner() || frms[_tokenId].isPaused == false, "FRM must not be paused.");

        frms[_tokenId].nftSetBlockTimestamp = _getBlockTimestamp();
        frms[_tokenId].nftUri = _nftUri;

        frms[_tokenId].useNftUri = true;

        emit logSetFrmNftUri(_tokenId, _nftUri);
    }

    //VIZMESH SMART CONTRACT OWNER FUNCTIONS

    function pauseFrm(uint256 _tokenId) 
        external
        onlyOwner
    {
        require(_exists(_tokenId), "Token must exists.");
        frms[_tokenId].isPaused = true;
    }

    function resumeFrm(uint256 _tokenId)
        external
        onlyOwner
    {
        require(_exists(_tokenId), "Token must exists.");
        frms[_tokenId].isPaused = false;
    }

    function setFrmUri(uint256 _tokenId, string calldata _frmUri)
        external
        onlyOwner
    {
        require(_exists(_tokenId), "Token must exists.");
        frms[_tokenId].frmUri = _frmUri;
    }

    function mintFrm(address _tokenOwner, int16 _x, int16 _y)
        external
        onlyOwner
    {
        uint256 tokenCount = _tokenIds.current();
        require(tokenCount < tokenSupplyLimit, "Can't exceed the tokenSupplyLimit.");

        uint256 eraIndex = _getEraIndex(tokenCount);
        uint256 blockTimestamp = _getBlockTimestamp();
        uint256 secondsPerMinute = 60;

        require(_lastMintBlockTimestamp < blockTimestamp, "Block timestamp must come after the _lastMintBlockTimestamp value.");

        if(tokenCount >= 144){
            //Get minutes since last mint
            uint256 secondsSinceLastMint = blockTimestamp.sub(_lastMintBlockTimestamp);
            uint256 minutesSinceLastMint = secondsSinceLastMint.div(secondsPerMinute);

            //Get tokens to mint 100000x
            uint256 tokens100000xPerMinuteRate = eras[eraIndex].tokens100000xPerMinuteRate;
            uint256 tokens100000x =  minutesSinceLastMint.mul(tokens100000xPerMinuteRate);

            //Require that at least one token can be created
            require(tokens100000x >= 100000, "It's too early to mint a new FRM.");
        }

        //Mint
        _tokenIds.increment();
        uint256 newTokenId = _tokenIds.current();
        frms[newTokenId] = frm(_x, _y, blockTimestamp, blockTimestamp, address(0), 0, '', '', false, false);
        _mint(_tokenOwner, newTokenId); //This token goes to the CONTRACT owner and NOT the caller of this function.

        //Update timestamp
        if(tokenCount >= 144) {
          uint256 addMinutesToLastMintTimestamp = eras[eraIndex].addMinutesToLastMintTimestamp;
          _lastMintBlockTimestamp += addMinutesToLastMintTimestamp * secondsPerMinute;
        } else {
          _lastMintBlockTimestamp = blockTimestamp;
        }

        emit logMintFrm(newTokenId);
    }

    //READ FUNCTIONS

    function tokenURI(uint256 _tokenId)
        public 
        view
        override
        returns(string memory)
    {
        require(_exists(_tokenId), "Token must exists.");
        return frms[_tokenId].frmUri;
    }

    function _getEraIndex(uint256 tokenCount)
        internal
        pure
        returns(uint256)
    {
        if(tokenCount <= 144){
            return 0;
        }
        uint256 eraIndex = (tokenCount.sub(144)).div(frmsPerEra);
        return eraIndex;
    }

    function getTokenIdsCurrent()
        external
        view
        returns(uint256)
    {
        return _tokenIds.current();
    }

    function _getBlockTimestamp() //have this function return the block.timestamp
        internal
        view
        returns(uint256)
    {
        return block.timestamp;
    }

    function renounceOwnership()
        public
        override
        onlyOwner
    {
        //Do nothing
    }
}
function setFrmPosition(uint256 _tokenId, int16 _x, int16 _y)
    external
{
    require(_exists(_tokenId), "Token must exists.");
    require(msg.sender == ownerOf(_tokenId) || msg.sender == owner(), "Must be token or contract owner.");
    require(msg.sender == owner() || frms[_tokenId].isPaused == false, "FRM must not be paused.");
        

    frms[_tokenId].positionSetBlockTimestamp = _getBlockTimestamp(); //Set this before position
    frms[_tokenId].x = _x;
    frms[_tokenId].y = _y;

    emit logSetFrmPosition(_tokenId, _x, _y);
}

//FRM OWNER FUNCTIONS