// Contract: SmartMonkeys
// Target function: function _buildTokenURI(uint256 id) internal view returns (string memory)
 Base64 Encoding/Decoding Table
     */
    string internal constant _TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

    /**
     * @dev Converts a `bytes` to its Bytes64 `string` representation.
     */
    function encode(bytes memory data) internal pure returns (string memory) {
        /**
         * Inspired by Brecht Devos (Brechtpd) implementation - MIT licence
         * https://github.com/Brechtpd/base64/blob/e78d9fd951e7b0977ddca77d92dc85183770daf4/base64.sol
         */
        if (data.length == 0) return "";

        // Loads the table into memory
        string memory table = _TABLE;

        // Encoding takes 3 bytes chunks of binary data from `bytes` data parameter
        // and split into 4 numbers of 6 bits.
        // The final Base64 length should be `bytes` data length multiplied by 4/3 rounded up
        // - `data.length + 2`  -> Round up
        // - `/ 3`              -> Number of 3-bytes chunks
        // - `4 *`              -> 4 characters for each chunk
        string memory result = new string(4 * ((data.length + 2) / 3));

        /// @solidity memory-safe-assembly
        assembly {
            // Prepare the lookup table (skip the first "length" byte)
            let tablePtr := add(table, 1)

            // Prepare result pointer, jump over length
            let resultPtr := add(result, 32)

            // Run over the input, 3 bytes at a time
            for {
                let dataPtr := data
                let endPtr := add(data, mload(data))
            } lt(dataPtr, endPtr) {

            } {
                // Advance 3 bytes
                dataPtr := add(dataPtr, 3)
                let input := mload(dataPtr)

                // To write each character, shift the 3 bytes (18 bits) chunk
                // 4 times in blocks of 6 bits for each character (18, 12, 6, 0)
                // and apply logical AND with 0x3F which is the number of
                // the previous character in the ASCII table prior to the Base64 Table
                // The result is then added to the table to get the character to write,
                // and finally write it in the result pointer but with a left shift
                // of 256 (1 byte) - 8 (1 ASCII char) = 248 bits

                mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance

                mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance

                mstore8(resultPtr, mload(add(tablePtr, and(shr(6, input), 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance

                mstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance
            }

            // When data `bytes` is not exactly 3 bytes long
            // it is padded with `=` characters at the end
            switch mod(mload(data), 3)
            case 1 {
                mstore8(sub(resultPtr, 1), 0x3d)
                mstore8(sub(resultPtr, 2), 0x3d)
            }
            case 2 {
                mstore8(sub(resultPtr, 1), 0x3d)
            }
        }

        return result;
    }
}

/**
 * @title NFT contract with on-chain metadata,
 * making quick and easy to create html/js NFTs, parametric NFTs or any NFT with dynamic metadata.
 * @author Daniel Gonzalez Abalde aka @DGANFT aka DaniGA#9856.
 * @dev The developer is responsible for assigning metadata for the contract (in constructor for instance) 
 * and tokens (in mint function for instance), by inheriting this contract and using _addValue() and _setValue() methods.
 * The tokenURI() and contractURI() methods are responsible to call _createTokenURI() and _createContractURI() methods
 * of this contract, which convert metadata into a Base64-encoded json readable by OpenSea, LooksRare and many other NFT platforms. 
 */
abstract contract OnChainMetadata 
{ 
  struct Metadata
  {
    uint256 keyCount;                           // number of metadata keys
    mapping(bytes32 => bytes[]) data;           // key => values
    mapping(bytes32 => uint256) valueCount;     // key => number of values
  }
   
  Metadata _contractMetadata;                   // metadata for the contract
  mapping(uint256 => Metadata) _tokenMetadata;  // metadata for each token
   
  bytes32 constant key_contract_name = "name";
  bytes32 constant key_contract_description = "description";
  bytes32 constant key_contract_image = "image";
  bytes32 constant key_contract_external_link = "external_link";
  bytes32 constant key_contract_seller_fee_basis_points = "seller_fee_basis_points";
  bytes32 constant key_contract_fee_recipient = "fee_recipient";

  bytes32 constant key_token_name = "name";
  bytes32 constant key_token_description = "description";
  bytes32 constant key_token_image = "image";
  bytes32 constant key_token_animation_url = "animation_url";
  bytes32 constant key_token_external_url = "external_url";
  bytes32 constant key_token_background_color = "background_color";
  bytes32 constant key_token_youtube_url = "youtube_url";
  bytes32 constant key_token_attributes_trait_type = "trait_type";
  bytes32 constant key_token_attributes_trait_value = "trait_value";
  bytes32 constant key_token_attributes_display_type = "trait_display"; 
 
  /**
   * @dev Get the values of a token metadata key.
   * @param tokenId the token identifier.
   * @param key the token metadata key.
   */
  function _getValues(uint256 tokenId, bytes32 key) internal view returns (bytes[] memory){ 
    return _tokenMetadata[tokenId].data[key];
  }
  /**
   * @dev Get the first value of a token metadata key.
   * @param tokenId the token identifier.
   * @param key the token metadata key.
   */
  function _getValue(uint256 tokenId, bytes32 key) internal view returns (bytes memory){ 
    bytes[] memory array = _getValues(tokenId, key);
    if(array.length > 0){
      return array[0];
    }else{
      return "";
    } 
  }
  /**
   * @dev Get the values of a contract metadata key. 
   * @param key the contract metadata key.
   */
  function _getValues(bytes32 key) internal view returns (bytes[] memory){ 
    return _contractMetadata.data[key];
  }
  /**
   * @dev Get the first value of a contract metadata key. 
   * @param key the contract metadata key.
   */
  function _getValue(bytes32 key) internal view returns (bytes memory){ 
    bytes[] memory array = _getValues(key);
    if(array.length > 0){
      return array[0];
    }else{
      return "";
    } 
  }
  /**
   * @dev Set the values on a token metadata key.
   * @param tokenId the token identifier.
   * @param key the token metadata key.
   * @param values the token metadata values.
   */
  function _setValues(uint256 tokenId, bytes32 key, bytes[] memory values) internal {
    Metadata storage meta = _tokenMetadata[tokenId];
    
    if(meta.valueCount[key] == 0){ 
        _tokenMetadata[tokenId].keyCount = meta.keyCount + 1;
    } 
    _tokenMetadata[tokenId].data[key] = values;
    _tokenMetadata[tokenId].valueCount[key] = values.length;
  }
  /**
   * @dev Set a single value on a token metadata key.
   * @param tokenId the token identifier.
   * @param key the token metadata key.
   * @param value the token metadata value.
   */
  function _setValue(uint256 tokenId, bytes32 key, bytes memory value) internal {
    bytes[] memory values = new bytes[](1);
    values[0] = value;
    _setValues(tokenId, key, values);
  }
  /**
   * @dev Set values on a given Metadata instance.
   * @param meta the metadata to modify.
   * @param key the token metadata key.
   * @param values the token metadata values.
   */
  function _addValues(Metadata storage meta, bytes32 key, bytes[] memory values) internal {
      require(meta.valueCount[key] == 0, "Metadata already contains given key");
      meta.keyCount = meta.keyCount + 1;
      meta.data[key] = values;
      meta.valueCount[key] = values.length;
  }
  /**
   * @dev Set a single value on a given Metadata instance.
   * @param meta the metadata to modify.
   * @param key the token metadata key.
   * @param value the token metadata value.
   */
  function _addValue(Metadata storage meta, bytes32 key, bytes memory value) internal { 
      bytes[] memory values = new bytes[](1);
      values[0] = value;
      _addValues(meta, key, values);
  }
 
  function _createTokenURI(uint256 tokenId) internal view virtual returns (string memory)
  { 
    bytes memory attributes;
    bytes[] memory trait_type = _getValues(tokenId, key_token_attributes_trait_type);
    if(trait_type.length > 0){
        attributes = '[';
        bytes[] memory trait_value = _getValues(tokenId, key_token_attributes_trait_value);
        bytes[] memory trait_display = _getValues(tokenId, key_token_attributes_display_type);
        for(uint256 i=0; i<trait_type.length; i++){
            attributes = abi.encodePacked(attributes, i > 0 ? ',' : '', '{',
            bytes(trait_display[i]).length > 0 ? string(abi.encodePacked('"display_type": "' , string(abi.decode(trait_display[i], (string))), '",')) : '', 
            '"trait_type": "' , string(abi.decode(trait_type[i], (string))), '", "value": "' , string(abi.decode(trait_value[i], (string))), '"}');
        }
        attributes = abi.encodePacked(attributes, ']');
    }
   
    string memory name = string(abi.decode(_getValue(tokenId, key_token_name), (string)));
    string memory description = string(abi.decode(_getValue(tokenId, key_token_description), (string))); 
    bytes memory image = _getValue(tokenId, key_token_image); 
    bytes memory animation_url = _getValue(tokenId, key_token_animation_url);
    bytes memory external_url = _getValue(tokenId, key_token_external_url);
    bytes memory background_color = _getValue(tokenId, key_token_background_color);
    bytes memory youtube_url = _getValue(tokenId, key_token_youtube_url); 

    return string(abi.encodePacked('data:application/json;base64,', Base64.encode(abi.encodePacked(
        '{',
            '"name": "', name, '", ',
            '"description": "', description, '"',
            bytes(image).length > 0 ? string(abi.encodePacked(', "image": "', string(abi.decode(image, (string))), '"')) : '',
            bytes(animation_url).length > 0 ? string(abi.encodePacked(', "animation_url": "', string(abi.decode(animation_url, (string))), '"')) : '',
            bytes(external_url).length > 0 ? string(abi.encodePacked(', "external_url": "', string(abi.decode(external_url, (string))), '"')) : '',
            bytes(attributes).length > 0 ? string(abi.encodePacked(', "attributes": ', attributes)) : '',
            bytes(background_color).length > 0 ? string(abi.encodePacked(', "background_color": ', string(abi.decode(background_color, (string))))) : '',
            bytes(youtube_url).length > 0 ? string(abi.encodePacked(', "youtube_url": ', string(abi.decode(youtube_url, (string))))) : '',
        '}'
        ))
    ));
  }

  function _createContractURI() internal view virtual returns (string memory) {
     
        bytes memory name = _getValue(key_contract_name); 
        bytes memory description = _getValue(key_contract_description);
        bytes memory image = _getValue(key_contract_image); 
        bytes memory external_url = _getValue(key_contract_external_link);
        bytes memory seller_fee_basis_points = _getValue(key_contract_seller_fee_basis_points);
        bytes memory fee_recipient = _getValue(key_contract_fee_recipient);

        return string(abi.encodePacked('data:application/json;base64,', Base64.encode(abi.encodePacked(
          '{',
              '"name": "', string(abi.decode(name, (string))), '"', 
              bytes(description).length > 0 ? string(abi.encodePacked(', "description": "', string(abi.decode(description, (string))), '"')) : '',
              bytes(image).length > 0 ? string(abi.encodePacked(', "image": "', string(abi.decode(image, (string))), '"')) : '',
              bytes(external_url).length > 0 ? string(abi.encodePacked(', "external_link": "', string(abi.decode(external_url, (string))), '"')) : '',
              bytes(seller_fee_basis_points).length > 0 ? string(abi.encodePacked(', "seller_fee_basis_points": ', Strings.toString(uint256(abi.decode(seller_fee_basis_points, (uint256)))), '')) : '', 
              bytes(fee_recipient).length > 0 ? string(abi.encodePacked(', "fee_recipient": "', Strings.toHexString(uint256(uint160(address(abi.decode(fee_recipient, (address))))), 20), '"')) : '',
          '}'
      ))));
  }

}




contract SmartMonkeys is ERC721A, ReentrancyGuard, Ownable, DefaultOperatorFilterer, OnChainMetadata {

    uint256 public maxSupply = 1024;

    mapping(uint256 => string) public promotionURLs;
    mapping(uint256 => uint256) public tokenHue;
    mapping(uint256 => uint256) public tokenSat;
    mapping(uint256 => uint256) public tokenLum;
    mapping(uint256 => uint256) public tokenLevel;
    mapping(uint256 => uint256) public allTokensWhoLinkedEver;
    mapping(uint256 => string) public allLinksEver;
    mapping(uint256 => uint256) public tokenMinBlock;
    uint256 public mintPrice = 0.01 ether;
    uint256 public linksEverCount;
 
 
    function setPromoURI(string memory promoURL, uint256 tokenId, uint256 otherId) external payable {
        require(
                    ( (ownerOf(tokenId) == msg.sender) && (msg.value >= (mintPrice/10) ) ) || 
                    ( (tokenLevel[otherId]>tokenLevel[tokenId]) && (msg.value >= ((mintPrice/10) * tokenLevel[tokenId] )) && (ownerOf(otherId) == msg.sender) ) 
            );
        linksEverCount = linksEverCount + 1;
        allTokensWhoLinkedEver[linksEverCount] = tokenId;
        if (ownerOf(tokenId) == msg.sender) {
            require(block.number > tokenMinBlock[tokenId]);
            tokenLevel[tokenId] = tokenLevel[tokenId] + 1;
            promotionURLs[tokenId] = promoURL; 
            allLinksEver[linksEverCount] = promoURL;
        }
        else {
            tokenLevel[tokenId] = tokenLevel[tokenId]/2; 
            allLinksEver[linksEverCount] = string.concat("overridden by ",Strings.toString(otherId)); 
            promotionURLs[tokenId] = string.concat("overridden by ",Strings.toString(otherId));  
            tokenMinBlock[tokenId] = block.number + (tokenLevel[otherId] * 5000);      
        }
    }
    
    function setMintPrice(uint256 _mintPrice) external onlyOwner {
        mintPrice = _mintPrice;
    }

    function setColor(uint256 tokenId,uint256 hue,uint256 sat,uint256 lum) external payable {
        require(ownerOf(tokenId) == msg.sender && msg.value >= (mintPrice/10) );
        // require(hue < 361);
        // require(sat < 101);
        // require(lum < 101);
        tokenLevel[tokenId] = tokenLevel[tokenId] + 1;
        tokenHue[tokenId] = hue;
        tokenSat[tokenId] = sat;
        tokenLum[tokenId] = lum;
    }

    function setApprovalForAll(address operator, bool approved) public override onlyAllowedOperatorApproval(operator) {
        super.setApprovalForAll(operator, approved);
    }

    function approve(address operator, uint256 tokenId) public override onlyAllowedOperatorApproval(operator) {
        super.approve(operator, tokenId);
    }

    function transferFrom(address from, address to, uint256 tokenId) public override onlyAllowedOperator(from) {
        super.transferFrom(from, to, tokenId);
    }

    function safeTransferFrom(address from, address to, uint256 tokenId) public override onlyAllowedOperator(from) {
        super.safeTransferFrom(from, to, tokenId);
    }

    function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data)
        public
        override
        onlyAllowedOperator(from)
    {
        super.safeTransferFrom(from, to, tokenId, data);
    }

    constructor() ERC721A("Smart NFT Monkeys", "SMART_MONKEY") {
     
    }

    function mint(uint256 _count,address _to) external payable nonReentrant {

        require(
            totalSupply() + _count <= maxSupply
        );
        require(
            (mintPrice * _count <= msg.value) || (msg.sender == owner())
        );
                _safeMint(_to, _count);
        }

    function withdraw() external onlyOwner {
        payable(owner()).transfer(address(this).balance);
    }

    function tokenURI(uint256 id) public view override returns (string memory) {
        return _buildTokenURI(id);
    }