// Contract: POWNFT
// Target function: function tokenURI(uint256 _tokenId) public view returns (string memory)
// SPDX-License-Identifier: This smart contract is guarded by an angry ghost
pragma solidity ^0.8.0;

contract POWNFT{


    constructor(){
        supportedInterfaces[0x80ac58cd] = true; //ERC721
        supportedInterfaces[0x5b5e139f] = true; //ERC721Metadata
        supportedInterfaces[0x780e9d63] = true; //ERC721Enumerable
        supportedInterfaces[0x01ffc9a7] = true; //ERC165

        //Issue token 0 to creator
        mint(1,bytes32(0));
    }


    //////===721 Standard
    event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
    event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);
    event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);

    //////===721 Implementation
    mapping(address => uint256) internal BALANCES;
    mapping (uint256 => address) internal ALLOWANCE;
    mapping (address => mapping (address => bool)) internal AUTHORISED;

    bytes32[] TOKENS;  //Array of all tokens [hash,hash,...]
    mapping(uint256 => address) OWNERS;  //Mapping of owners


    mapping(uint256 => uint256) WITHDRAWALS;

    //    METADATA VARS
    string private __name = "POW NFT";
    string private __symbol = "POW";
    bytes private __uriBase = bytes("https://www.pownftmetadata.com/d/");


    //    ENUMERABLE VARS
    mapping(address => uint[]) internal OWNER_INDEX_TO_ID;
    mapping(uint => uint) internal OWNER_ID_TO_INDEX;
    mapping(uint => uint) internal ID_TO_INDEX;


    //      MINING VARS
    uint BASE_COST = 0.00003 ether;
    uint BASE_DIFFICULTY = uint(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)/uint(2);
    uint DIFFICULTY_RAMP = 5;


    //      MINING EVENTS
    event Mined(uint indexed _tokenId, bytes32 hash);
    event Withdraw(uint indexed _tokenId, uint value);

    //      MINING FUNCTIONS
    function generationOf(uint _tokenId) private pure returns(uint generation){
        for(generation = 0; _tokenId > 0; generation++){
            _tokenId /= 2;
        }
        return generation - 1;
    }
    function hashOf(uint _tokenId) public view returns(bytes32){
        require(isValidToken(_tokenId),"invalid");
        return TOKENS[_tokenId - 1];
    }

    function withdraw(uint _tokenId) public{
        require(msg.sender == ownerOf(_tokenId),'owner');

        uint generation = generationOf(_tokenId);


        uint last = 2**(generation+1)-1;
        uint payout = 0;
        for(uint i = TOKENS.length; i > last && i > WITHDRAWALS[_tokenId]; i--){
            payout += BASE_COST;
        }
        WITHDRAWALS[_tokenId] = TOKENS.length;
        emit Withdraw(_tokenId,payout);
        payable(msg.sender).transfer(payout);

    }
    function withdrawMultiple(uint[] calldata _tokenIds) public{
        for(uint i = 0; i < _tokenIds.length; i++){
            withdraw(_tokenIds[i]);
        }
    }

    function mine(uint nonce) external payable{
        uint tokenId = TOKENS.length + 1;
        uint generation = generationOf(tokenId);

        uint difficulty = BASE_DIFFICULTY / (DIFFICULTY_RAMP**generation);
        if(generation > 13){
            difficulty /= (tokenId - 2**14 + 1);
        }


        uint cost = (2**generation - 1)* BASE_COST;


        bytes32 hash = keccak256(abi.encodePacked(msg.sender,TOKENS[tokenId-2],nonce));

        require(uint(hash) < difficulty,"difficulty");
        require(msg.value ==cost,"cost");

        mint(tokenId,keccak256(abi.encodePacked(hash,block.timestamp)));
    }

    function mint(uint tokenId, bytes32 hash) private{
        OWNERS[tokenId] = msg.sender;
        BALANCES[msg.sender]++;
        OWNER_ID_TO_INDEX[tokenId] = OWNER_INDEX_TO_ID[msg.sender].length;
        OWNER_INDEX_TO_ID[msg.sender].push(tokenId);

        ID_TO_INDEX[tokenId] = TOKENS.length;
        TOKENS.push(hash);

        emit Mined(tokenId,hash);
        emit Transfer(address(0),msg.sender,tokenId);
    }


    function isValidToken(uint256 _tokenId) internal view returns(bool){
        return OWNERS[_tokenId] != address(0);
    }

    function balanceOf(address _owner) external view returns (uint256){
        return BALANCES[_owner];
    }

    function ownerOf(uint256 _tokenId) public view returns(address){
        require(isValidToken(_tokenId),"invalid");
        return OWNERS[_tokenId];
    }


    function approve(address _approved, uint256 _tokenId)  external{
        address owner = ownerOf(_tokenId);
        require( owner == msg.sender                    //Require Sender Owns Token
            || AUTHORISED[owner][msg.sender]                //  or is approved for all.
        ,"permission");
        emit Approval(owner, _approved, _tokenId);
        ALLOWANCE[_tokenId] = _approved;
    }

    function getApproved(uint256 _tokenId) external view returns (address) {
        require(isValidToken(_tokenId),"invalid");
        return ALLOWANCE[_tokenId];
    }

    function isApprovedForAll(address _owner, address _operator) external view returns (bool) {
        return AUTHORISED[_owner][_operator];
    }


    function setApprovalForAll(address _operator, bool _approved) external {
        emit ApprovalForAll(msg.sender,_operator, _approved);
        AUTHORISED[msg.sender][_operator] = _approved;
    }


    function transferFrom(address _from, address _to, uint256 _tokenId) public {

        //Check Transferable
        //There is a token validity check in ownerOf
        address owner = ownerOf(_tokenId);

        require ( owner == msg.sender             //Require sender owns token
        //Doing the two below manually instead of referring to the external methods saves gas
        || ALLOWANCE[_tokenId] == msg.sender      //or is approved for this token
            || AUTHORISED[owner][msg.sender]          //or is approved for all
        ,"permission");
        require(owner == _from,"owner");
        require(_to != address(0),"zero");

        emit Transfer(_from, _to, _tokenId);


        OWNERS[_tokenId] =_to;

        BALANCES[_from]--;
        BALANCES[_to]++;

        //Reset approved if there is one
        if(ALLOWANCE[_tokenId] != address(0)){
            delete ALLOWANCE[_tokenId];
        }

        //Enumerable Additions
        uint oldIndex = OWNER_ID_TO_INDEX[_tokenId];
        //If the token isn't the last one in the owner's index
        if(oldIndex != OWNER_INDEX_TO_ID[_from].length - 1){
            //Move the old one in the index list
            OWNER_INDEX_TO_ID[_from][oldIndex] = OWNER_INDEX_TO_ID[_from][OWNER_INDEX_TO_ID[_from].length - 1];
            //Update the token's reference to its place in the index list
            OWNER_ID_TO_INDEX[OWNER_INDEX_TO_ID[_from][oldIndex]] = oldIndex;
        }
        //OWNER_INDEX_TO_ID[_from].length--;
        OWNER_INDEX_TO_ID[_from].pop();

        OWNER_ID_TO_INDEX[_tokenId] = OWNER_INDEX_TO_ID[_to].length;
        OWNER_INDEX_TO_ID[_to].push(_tokenId);

    }

    function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes memory data) public {
        transferFrom(_from, _to, _tokenId);

        //Get size of "_to" address, if 0 it's a wallet
        uint32 size;
        assembly {
            size := extcodesize(_to)
        }
        if(size > 0){
            ERC721TokenReceiver receiver = ERC721TokenReceiver(_to);
            require(receiver.onERC721Received(msg.sender,_from,_tokenId,data) == bytes4(keccak256("onERC721Received(address,address,uint256,bytes)")),"receiver");
        }

    }

    function safeTransferFrom(address _from, address _to, uint256 _tokenId) external {
        safeTransferFrom(_from,_to,_tokenId,"");
    }