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



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

pragma solidity ^0.8.20;

import {Context} from "../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.
 *
 * The initial owner is set to the address provided by the deployer. 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;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

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

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @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 {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling 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 {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _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 (last updated v5.0.0) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @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: contracts/HoodySign.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/access/Ownable.sol";
import "./IHoody.sol";

contract HoodySign is Ownable, IHoody {
    mapping(address => uint256) public nonces;
    mapping(address => bool) public isAccess;
    address public signer;

    constructor() Ownable(msg.sender) {}

    function getMessageHashForMigrate(
        address _holder,
        uint256 _tokenId,
        string memory _uri
    ) public view returns (bytes32) {
        return keccak256(abi.encodePacked(_tokenId, _uri, nonces[_holder]));
    }

    function concat(string[] memory _words) private pure returns (string memory) {
        bytes memory output;

        for (uint256 i = 0; i < _words.length; i++) {
            output = abi.encodePacked(output, _words[i]);
        }

        return string(output);
    }

    function getMessageHashForTraits(
        address _minter,
        string memory _msg,
        uint16[] memory _traits
    ) public view returns (bytes32) {
        return keccak256(abi.encodePacked(_msg, _traits, nonces[_minter]));
    }

    function getMessageHashForStake(
        address _minter,
        uint256[] memory _tokenIds,
        HoodyGangRarity[] memory _rarities
    ) public view returns (bytes32) {
        return
            keccak256(
                abi.encodePacked(_minter, _tokenIds, _rarities, nonces[_minter])
            );
    }

    function getEthSignedMessageHash(
        bytes32 _messageHash
    ) public pure returns (bytes32) {
        /*
        Signature is produced by signing a keccak256 hash with the following format:
        "\x19Ethereum Signed Message\n" + len(msg) + msg
        */
        return
            keccak256(
                abi.encodePacked(
                    "\x19Ethereum Signed Message:\n32",
                    _messageHash
                )
            );
    }

    function verifyForMigrate(
        address _holder,
        uint256 _tokenId,
        string memory _uri,
        bytes memory _signature
    ) public view returns (bool) {
        bytes32 messageHash = getMessageHashForMigrate(
            _holder,
            _tokenId,
            _uri
        );
        bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);

        return recoverSigner(ethSignedMessageHash, _signature) == signer;
    }

    function verifyForTraits(
        address _minter,
        string memory _msg,
        uint16[] memory _traits,
        bytes memory _signature
    ) public view returns (bool) {
        bytes32 messageHash = getMessageHashForTraits(_minter, _msg, _traits);
        bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);

        return recoverSigner(ethSignedMessageHash, _signature) == signer;
    }

    function verifyForStake(
        address _minter,
        uint256[] memory _tokenIds,
        HoodyGangRarity[] memory _rarities,
        bytes memory _signature
    ) public view returns (bool) {
        bytes32 messageHash = getMessageHashForStake(
            _minter,
            _tokenIds,
            _rarities
        );
        bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);

        return recoverSigner(ethSignedMessageHash, _signature) == signer;
    }

    function recoverSigner(
        bytes32 _ethSignedMessageHash,
        bytes memory _signature
    ) public pure returns (address) {
        (bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature);

        return ecrecover(_ethSignedMessageHash, v, r, s);
    }

    function splitSignature(
        bytes memory sig
    ) public pure returns (bytes32 r, bytes32 s, uint8 v) {
        require(sig.length == 65, "invalid signature length");

        assembly {
            /*
            First 32 bytes stores the length of the signature

            add(sig, 32) = pointer of sig + 32
            effectively, skips first 32 bytes of signature

            mload(p) loads next 32 bytes starting at the memory address p into memory
            */

            // first 32 bytes, after the length prefix
            r := mload(add(sig, 32))
            // second 32 bytes
            s := mload(add(sig, 64))
            // final byte (first byte of the next 32 bytes)
            v := byte(0, mload(add(sig, 96)))
        }

        // implicitly return (r, s, v)
    }

    function setSigner(address _signer) public onlyOwner {
        signer = _signer;
    }

    function setAccess(address _account) public onlyOwner {
        isAccess[_account] = true;
    }

    function increaseNonce(address _minter) external {
        require(isAccess[msg.sender], "You can't call this function.");
        nonces[_minter]++;
    }
}

// File: contracts/IHoody.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

interface IHoody {
    enum HoodyGangRarity {
        Common,
        Rare,
        Legendary
    }

    enum HoodyTraitRarity {
        Common,
        Rare,
        Legendary
    }
}