contract Vitaliks is ERC721, Ownable {
    using SafeMath for uint256;
    using Strings for uint256;

    string baseURI;
    string public baseExtension = ".json";
    uint256 public MAX_VITALIKS;
    uint256 public latestNewVitalikForSale;

    struct Vitalik {
        address owner;
        bool currentlyForSale;
        uint price;
        uint timesSold;
    }

    mapping (uint => Vitalik) public vitaliks;
    mapping (address => uint[]) public vitalikOwnersHistory;
    
    mapping (uint256 => address) public idToOwner;
    mapping (uint256 => address) public idToApproval;
    mapping (address => uint256) public ownerToNFTokenCount;
    mapping (address => mapping (address => bool)) public ownerToOperators;

    constructor(string memory name, string memory symbol, string memory initBaseURI, uint256 maxNFTSupply) ERC721(name, symbol) {
        setBaseURI(initBaseURI);
        MAX_VITALIKS = maxNFTSupply;
        latestNewVitalikForSale = 1;
        vitaliks[1].currentlyForSale = true;
        vitaliks[1].price = (10**16)*1;
    }

    // Internal Functions
    function _baseURI() internal view virtual override returns (string memory) {
        return baseURI;
    }

    function _tokenExists(uint256 tokenId) internal view virtual returns (bool) {
        return idToOwner[tokenId] != address(0);
    }

    modifier canTransfer(uint256 _tokenId) {
        address tokenOwner = idToOwner[_tokenId];
        require(
            tokenOwner == msg.sender
            || idToApproval[_tokenId] == msg.sender
            || ownerToOperators[tokenOwner][msg.sender]
        );
        _;
    }

    modifier validNFToken(uint256 _tokenId) {
        address tokenOwner = getVitalikOwner(_tokenId);
        require(tokenOwner != address(0));
        _;
    }

    function _addNFToken(address _to, uint256 _tokenId) internal virtual {
        require(idToOwner[_tokenId] == address(0));

        idToOwner[_tokenId] = _to;
        ownerToNFTokenCount[_to]++;
    }

    function _mint(address _to, uint256 _tokenId) internal virtual override {
        require(_to != address(0));
        require(idToOwner[_tokenId] == address(0));

        _addNFToken(_to, _tokenId);

        emit Transfer(address(0), _to, _tokenId);
    }

    function _clearApproval(uint256 _tokenId) private {
        if (idToApproval[_tokenId] != address(0)) {
            delete idToApproval[_tokenId];
        }
    }

    function _removeNFToken(address _from, uint256 _tokenId) internal virtual {
        require(idToOwner[_tokenId] == _from);
        ownerToNFTokenCount[_from] = ownerToNFTokenCount[_from]--;
        delete idToOwner[_tokenId];
    }

    function _transfer(address _to, uint256 _tokenId) internal {
        address from = idToOwner[_tokenId];
        _clearApproval(_tokenId);

        _removeNFToken(from, _tokenId);
        _addNFToken(_to, _tokenId);
        
        vitaliks[_tokenId].owner = _to;

        emit Transfer(from, _to, _tokenId);
    }

    function _safeTransferFrom(address _from, address _to, uint256 _tokenId) private canTransfer(_tokenId) validNFToken(_tokenId) {
        address tokenOwner = getVitalikOwner(_tokenId);
        require(tokenOwner == _from);
        require(_to != address(0));

        _transfer(_to, _tokenId);
    }

    // Public Functions
    function getVitalikInfo (uint vitalikNumber) public view returns (address, bool, uint, uint) {
        return (vitaliks[vitalikNumber].owner, vitaliks[vitalikNumber].currentlyForSale, vitaliks[vitalikNumber].price, vitaliks[vitalikNumber].timesSold);
    }
    
    function getVitalikOwner (uint vitalikNumber) public view returns (address) {
        return (vitaliks[vitalikNumber].owner);
    }
    
    function getVitalikOwnerHistory (address _address) public view returns (uint[] memory ownersHistory) {
        ownersHistory = vitalikOwnersHistory[_address];
    }

    function buyVitalik (uint vitalikNumber) public payable {
        require(vitaliks[vitalikNumber].currentlyForSale == true);
        require(msg.value == vitaliks[vitalikNumber].price);
        require(vitalikNumber <= MAX_VITALIKS);

        vitaliks[vitalikNumber].timesSold++;
        vitalikOwnersHistory[msg.sender].push(vitalikNumber);
        vitaliks[vitalikNumber].currentlyForSale = false;

        if (vitalikNumber != latestNewVitalikForSale) {
            address currentVitalikOwner = getVitalikOwner(vitalikNumber);
            payable(currentVitalikOwner).transfer(vitaliks[vitalikNumber].price);
            idToApproval[vitalikNumber] = msg.sender;
            _safeTransferFrom(currentVitalikOwner, msg.sender, vitalikNumber);
        } else {
            vitaliks[vitalikNumber].owner = msg.sender;
            
            if (latestNewVitalikForSale < MAX_VITALIKS) {
                latestNewVitalikForSale++;
                vitaliks[latestNewVitalikForSale].price = SafeMath.add(SafeMath.div(SafeMath.mul(SafeMath.mul(vitaliks[vitalikNumber].price, 10**6), 3624), 10**12), vitaliks[vitalikNumber].price);
                vitaliks[latestNewVitalikForSale].currentlyForSale = true;
            }

            _mint(msg.sender, vitalikNumber);
        }
    }

    function sellVitalik (uint vitalikNumber, uint price) public {
        require(msg.sender == vitaliks[vitalikNumber].owner);
        require(price > 0);
        
        vitaliks[vitalikNumber].price = price;
        vitaliks[vitalikNumber].currentlyForSale = true;
    }

    function giftVitalik (uint vitalikNumber, address receiver) public {
        require(msg.sender == vitaliks[vitalikNumber].owner);
        require(receiver != address(0));

        vitalikOwnersHistory[receiver].push(vitalikNumber);
        _safeTransferFrom(msg.sender, receiver, vitalikNumber);
    }
    
    function dontSellVitalik (uint vitalikNumber) public {
        require(msg.sender == vitaliks[vitalikNumber].owner);
        
        vitaliks[vitalikNumber].currentlyForSale = false;
    }
    
    function tokenURI(uint256 tokenId)
        public
        view
        virtual
        override
        returns (string memory) {
            require(
                _tokenExists(tokenId),
                "ERC721Metadata: URI query for nonexistent token"
            );

            string memory currentBaseURI = _baseURI();
            return bytes(currentBaseURI).length > 0
                ? string(abi.encodePacked(currentBaseURI, tokenId.toString(), baseExtension))
                : "";
        }

    // Owner Functions
    function setBaseURI(string memory _newBaseURI) public onlyOwner {
        baseURI = _newBaseURI;
    }

    function withdraw() public onlyOwner {
        uint balance = address(this).balance;
        payable(msg.sender).transfer(balance);
    }

    function checkBalance() public view onlyOwner returns (uint) {
        uint balance = address(this).balance;
        return balance;
    }
}
function getVitalikInfo (uint vitalikNumber) public view returns (address, bool, uint, uint) {
    return (vitaliks[vitalikNumber].owner, vitaliks[vitalikNumber].currentlyForSale, vitaliks[vitalikNumber].price, vitaliks[vitalikNumber].timesSold);
}

// Public Functions