// File: @0xsequence/sstore2/contracts/SSTORE2.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./utils/Bytecode.sol";

/**
  @title A key-value storage with auto-generated keys for storing chunks of data with a lower write & read cost.
  @author Agustin Aguilar <aa@horizon.io>

  Readme: https://github.com/0xsequence/sstore2#readme
*/
library SSTORE2 {
  error WriteError();

  /**
    @notice Stores `_data` and returns `pointer` as key for later retrieval
    @dev The pointer is a contract address with `_data` as code
    @param _data to be written
    @return pointer Pointer to the written `_data`
  */
  function write(bytes memory _data) internal returns (address pointer) {
    // Append 00 to _data so contract can't be called
    // Build init code
    bytes memory code = Bytecode.creationCodeFor(
      abi.encodePacked(
        hex'00',
        _data
      )
    );

    // Deploy contract using create
    assembly { pointer := create(0, add(code, 32), mload(code)) }

    // Address MUST be non-zero
    if (pointer == address(0)) revert WriteError();
  }

  /**
    @notice Reads the contents of the `_pointer` code as data, skips the first byte 
    @dev The function is intended for reading pointers generated by `write`
    @param _pointer to be read
    @return data read from `_pointer` contract
  */
  function read(address _pointer) internal view returns (bytes memory) {
    return Bytecode.codeAt(_pointer, 1, type(uint256).max);
  }

  /**
    @notice Reads the contents of the `_pointer` code as data, skips the first byte 
    @dev The function is intended for reading pointers generated by `write`
    @param _pointer to be read
    @param _start number of bytes to skip
    @return data read from `_pointer` contract
  */
  function read(address _pointer, uint256 _start) internal view returns (bytes memory) {
    return Bytecode.codeAt(_pointer, _start + 1, type(uint256).max);
  }

  /**
    @notice Reads the contents of the `_pointer` code as data, skips the first byte 
    @dev The function is intended for reading pointers generated by `write`
    @param _pointer to be read
    @param _start number of bytes to skip
    @param _end index before which to end extraction
    @return data read from `_pointer` contract
  */
  function read(address _pointer, uint256 _start, uint256 _end) internal view returns (bytes memory) {
    return Bytecode.codeAt(_pointer, _start + 1, _end + 1);
  }
}

// File: @0xsequence/sstore2/contracts/utils/Bytecode.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;


library Bytecode {
  error InvalidCodeAtRange(uint256 _size, uint256 _start, uint256 _end);

  /**
    @notice Generate a creation code that results on a contract with `_code` as bytecode
    @param _code The returning value of the resulting `creationCode`
    @return creationCode (constructor) for new contract
  */
  function creationCodeFor(bytes memory _code) internal pure returns (bytes memory) {
    /*
      0x00    0x63         0x63XXXXXX  PUSH4 _code.length  size
      0x01    0x80         0x80        DUP1                size size
      0x02    0x60         0x600e      PUSH1 14            14 size size
      0x03    0x60         0x6000      PUSH1 00            0 14 size size
      0x04    0x39         0x39        CODECOPY            size
      0x05    0x60         0x6000      PUSH1 00            0 size
      0x06    0xf3         0xf3        RETURN
      <CODE>
    */

    return abi.encodePacked(
      hex"63",
      uint32(_code.length),
      hex"80_60_0E_60_00_39_60_00_F3",
      _code
    );
  }

  /**
    @notice Returns the size of the code on a given address
    @param _addr Address that may or may not contain code
    @return size of the code on the given `_addr`
  */
  function codeSize(address _addr) internal view returns (uint256 size) {
    assembly { size := extcodesize(_addr) }
  }

  /**
    @notice Returns the code of a given address
    @dev It will fail if `_end < _start`
    @param _addr Address that may or may not contain code
    @param _start number of bytes of code to skip on read
    @param _end index before which to end extraction
    @return oCode read from `_addr` deployed bytecode

    Forked from: https://gist.github.com/KardanovIR/fe98661df9338c842b4a30306d507fbd
  */
  function codeAt(address _addr, uint256 _start, uint256 _end) internal view returns (bytes memory oCode) {
    uint256 csize = codeSize(_addr);
    if (csize == 0) return bytes("");

    if (_start > csize) return bytes("");
    if (_end < _start) revert InvalidCodeAtRange(csize, _start, _end); 

    unchecked {
      uint256 reqSize = _end - _start;
      uint256 maxSize = csize - _start;

      uint256 size = maxSize < reqSize ? maxSize : reqSize;

      assembly {
        // allocate output byte array - this could also be done without assembly
        // by using o_code = new bytes(size)
        oCode := mload(0x40)
        // new "memory end" including padding
        mstore(0x40, add(oCode, and(add(add(size, 0x20), 0x1f), not(0x1f))))
        // store length in memory
        mstore(oCode, size)
        // actually retrieve the code, this needs assembly
        extcodecopy(_addr, add(oCode, 0x20), _start, size)
      }
    }
  }
}

// File: @openzeppelin/contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// File: @openzeppelin/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

// File: src/TributeBrand/ITokenDNAStorage.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

interface ITokenDNAStorage {
	function getTokenDNA(
		uint256 tokenId,
		uint256 entropy
	) external view returns (bytes16);
}

// File: src/TributeBrand/TokenDNAStorage.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@0xsequence/sstore2/contracts/SSTORE2.sol";
import "./ITokenDNAStorage.sol";

contract TokenDNAStorage is Ownable, ITokenDNAStorage {
	event DNAResolved(uint256 indexed fromTokenId, uint256 indexed toTokenId);

	// DNA for a token is 16 bytes of data.
	uint8 constant TOKEN_DNA_LENGTH = 16;

	uint256 constant MAX_DNA_PER_BUCKET = 500; // must be at most 1536

	uint256 public MAX_SUPPLY;

	struct DNABucket {
		address pointer; // An address can at most reference ~24kB (1536 * 16 bytes), which is why we need to split DNA into buckets.
		uint256 pieces;
	}

	DNABucket[] public _DNABuckets;

	uint256 public resolved;
	mapping(uint16 => uint16) private tokenIdToDNAPosition;

	bool public locked;

	function lock() external onlyOwner {
		locked = true;
	}

	function getDNABucket(
		uint256 idx,
		uint256 dnaPosition
	)
		external
		view
		onlyOwner
		returns (
			uint256 total_buckets,
			DNABucket memory bucket,
			bytes memory dna
		)
	{
		if (!locked) {
			total_buckets = _DNABuckets.length;
			bucket = _DNABuckets[idx];
			dna = SSTORE2.read(bucket.pointer, dnaPosition * TOKEN_DNA_LENGTH);
		}
	}

	// idx = -1 for append.
	function setDNABucket(int idx, bytes calldata newDNA) external onlyOwner {
		require(!locked, "DNA is finalized."); // If the lock isn't on we can still modify the DNA.
		require(idx < int(_DNABuckets.length), "IDX out of bounds");
		require(
			idx >= 0 ||
				_DNABuckets.length == 0 ||
				_DNABuckets[_DNABuckets.length - 1].pieces ==
				MAX_DNA_PER_BUCKET,
			"can't append new bucket after unfilled previous one"
		);

		uint256 pieces = newDNA.length / 16;

		require(pieces <= MAX_DNA_PER_BUCKET, "exceeded bucket width");
		require(newDNA.length % 16 == 0, "dna length not multiple of 16");

		if (idx < 0) {
			_DNABuckets.push(DNABucket(SSTORE2.write(newDNA), pieces));
		} else {
			delete _DNABuckets[uint(idx)].pointer;
			MAX_SUPPLY -= _DNABuckets[uint(idx)].pieces;
			_DNABuckets[uint(idx)].pointer = SSTORE2.write(newDNA);
			_DNABuckets[uint(idx)].pieces = pieces;
		}

		MAX_SUPPLY += pieces;
	}

	function getTokenDNA(
		uint256 tokenId,
		uint256 entropy
	) external view returns (bytes16) {
		// This is just a permutation function that works as long as tokenId < 10007 (10007 is the closest prime after 10000).
		// note to self: need >= 10007 supplied DNA pieces.
		uint256 DNAPosition = (entropy + tokenId * 131) % 10007;
		uint256 bucketIdx = DNAPosition / MAX_DNA_PER_BUCKET;
		if (bucketIdx >= _DNABuckets.length)
			return bytes16(uint128(DNAPosition * entropy));
		uint256 DNAIdx = DNAPosition % MAX_DNA_PER_BUCKET;
		return
			bytes16(
				SSTORE2.read(
					_DNABuckets[bucketIdx].pointer,
					DNAIdx * TOKEN_DNA_LENGTH
				)
			);
	}
}