// File: contracts/HUB/HUBHelper.sol



// SPDX-License-Identifier: MIT LICENSE

pragma solidity 0.8.17;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { IHUB } from "./interfaces/IHUB.sol";
import { IGameContract } from "./interfaces/IGameContract.sol";

contract MetatopiaHubHelper is Ownable {

    IHUB public HUB = IHUB(0x1FbeA078ad9f0f52FD39Fc8AD7494732D65309Fb);
    mapping(uint8 => IGameContract) public gameContracts; // 1 = BullRun, 2 = MoonForce, 3 = Doge World, 4 = PYE Market, 5 = Wastelands
    mapping(address => mapping(uint8 => bool)) public userUnstaked;
    mapping(uint16 => bool) public alphaUnstaked; 

    event AlphaReturned (address indexed _originalOwner, uint16 _id);

    constructor() {
        gameContracts[1] = IGameContract(0xBe2440C38Cdb7920Ed8D77df6c97112779957286);
        gameContracts[2] = IGameContract(0x7fF98F4140074D371e40cE81180D7E0e5d459271);
        gameContracts[3] = IGameContract(0x7Bfd981d7AdB787c48C9B95931DAABAE0270Ad21);
        gameContracts[4] = IGameContract(0x24d5eEed253782D3ADbccB0E1d1a7Ef33F609713);
        gameContracts[5] = IGameContract(0x4c26a956997A103c52F77303efaaB0b591cA3728);
    }

    function emergencyRescueAlphasByIds(uint16[] calldata _ids, uint8 gameId) external {
        uint16[] memory userStaked = HUB.getUserStakedAlphaGame(msg.sender, gameId);
        bool isAlphaOwner;
        for (uint i = 0; i < _ids.length; i++) {
            require(
                (
                    gameId == 5 ? 
                    gameContracts[gameId].getTopiaPerAlpha(_ids[i]) :
                    gameContracts[gameId].getUnclaimedAlpha(_ids[i])
                ) == 0, "Must claim earnings first"
            );
            require(_getUnclaimedAlpha(_ids[i], gameId) == 0, "Must claim earnings first");
            isAlphaOwner = false;
            for (uint s = 0; s < userStaked.length; s++) {
                if (_ids[i] == userStaked[s]) {
                    isAlphaOwner = true;
                    break;
                } else {
                    continue;
                }
            }
            require(isAlphaOwner, "Caller is not owner of Alpha");

            HUB.emergencyRescueAlpha(_ids[i], msg.sender);
            emit AlphaReturned(msg.sender, _ids[i]);
        }
        userUnstaked[msg.sender][gameId] = true;
    }

    function emergencyRescueAlphas(uint8 gameId) external {
        require(!userUnstaked[msg.sender][gameId], "User has already unstaked");
        require(HUB.devRescueEnabled(), "Rescue not enabled");
        uint16[] memory _ids = HUB.getUserStakedAlphaGame(msg.sender, gameId);
        for (uint i = 0; i < _ids.length; i++) {
            require(_getUnclaimedAlpha(_ids[i], gameId) == 0, "Must claim earnings first");

            HUB.emergencyRescueAlpha(_ids[i], msg.sender);
            if (gameId == 1) {
                alphaUnstaked[_ids[i]] = true;
            }
            emit AlphaReturned(msg.sender, _ids[i]);
        }
        userUnstaked[msg.sender][gameId] = true;
    }

    function _setGenesis(address genesis_) external onlyOwner {
        HUB.setGenesis(genesis_);
    }

    function _setTopia(address topia_) external onlyOwner {
        HUB.setTopia(topia_);
    }

    function _batchSetGenesisIdentifier(uint16[] calldata idNumbers_, uint8[] calldata types_) external onlyOwner {
        HUB.batchSetGenesisIdentifier(idNumbers_, types_);
    }

    function _setRescueEnabled(bool flag_) external onlyOwner {
        HUB.setRescueEnabled(flag_);
    }

    function _setDevRescueEnabled(bool flag_) external onlyOwner {
        HUB.setDevRescueEnabled(flag_);
    }

    function _setGameContract(address contract_, bool flag_) external onlyOwner {
        HUB.setGameContract(contract_, flag_);
    }

    function _transferHUBOwnership(address _newOwner) external onlyOwner {
        HUB.transferOwnership(_newOwner);
    }

    function _getUnclaimedAlpha(uint16 tokenId, uint8 gameId) public view returns (uint256 owed) {
        if (alphaUnstaked[tokenId]) {
            return 0;
        }
        owed = gameId == 5 ? gameContracts[gameId].getTopiaPerAlpha(tokenId) : gameContracts[gameId].getUnclaimedAlpha(tokenId);
        if (owed <= 10**17) {
            return 0;
        }
    }

    function _getUserStakedAlphaGame(address owner, uint8 gameId) external view returns (uint16[] memory stakedAlphas) {
        if (userUnstaked[owner][gameId]) {
            return stakedAlphas;
        } else {
            return HUB.getUserStakedAlphaGame(owner, gameId);
        }
    }

    function _getUserStakedAlphas(address owner) external view returns (uint16[] memory stakedAlphas) {
        uint16[] memory stakedIds1 = HUB.getUserStakedAlphaGame(owner, 1);
        uint16[] memory stakedIds2 = HUB.getUserStakedAlphaGame(owner, 2);
        uint16[] memory stakedIds3 = HUB.getUserStakedAlphaGame(owner, 3);
        uint16[] memory stakedIds4 = HUB.getUserStakedAlphaGame(owner, 4);
        uint16[] memory stakedIds5 = HUB.getUserStakedAlphaGame(owner, 5);

        uint256 length;
        if (!userUnstaked[owner][1]) {
            length += stakedIds1.length;
        }
        if (!userUnstaked[owner][2]) {
            length += stakedIds2.length;
        }
        if (!userUnstaked[owner][3]) {
            length += stakedIds3.length;
        }
        if (!userUnstaked[owner][4]) {
            length += stakedIds4.length;
        }
        if (!userUnstaked[owner][5]) {
            length += stakedIds5.length;
        }

        if (length > 0) {
            stakedAlphas = new uint16[](length);
            uint y = 0;
            uint L = 0;
            if (!userUnstaked[owner][1]) {
                L = stakedIds1.length;
                for(uint z = 0; z < L; z++) {
                    stakedAlphas[y] = stakedIds1[z];
                    y++;
                }
            }
            if (!userUnstaked[owner][2]) {
                L = stakedIds2.length;
                for(uint z = 0; z < L; z++) {
                    stakedAlphas[y] = stakedIds2[z];
                    y++;
                }
            }
            if (!userUnstaked[owner][3]) {
                L = stakedIds3.length;
                for(uint z = 0; z < L; z++) {
                    stakedAlphas[y] = stakedIds3[z];
                    y++;
                }
            }
            if (!userUnstaked[owner][4]) {
                L = stakedIds4.length;
                for(uint z = 0; z < L; z++) {
                    stakedAlphas[y] = stakedIds4[z];
                    y++;
                }
            }
            if (!userUnstaked[owner][5]) {
                L = stakedIds5.length;
                for(uint z = 0; z < L; z++) {
                    stakedAlphas[y] = stakedIds5[z];
                    y++;
                }
            }
        }
    }

}
// File: contracts/HUB/interfaces/IGameContract.sol



// SPDX-License-Identifier: MIT LICENSE

pragma solidity ^0.8.0;

interface IGameContract {
    
    function getUnclaimedAlpha(uint16 tokenId) external view returns (uint256);

    function getTopiaPerAlpha(uint16 _id) external view returns (uint256);

}
// File: contracts/HUB/interfaces/IHUB.sol



// SPDX-License-Identifier: MIT LICENSE

pragma solidity ^0.8.0;

interface IHUB {
    
    function emergencyRescueAlpha(uint16 _id, address _account) external;

    function setGenesis(address _genesis) external;

    function setTopia(address _topia) external;

    function batchSetGenesisIdentifier(uint16[] calldata _idNumbers, uint8[] calldata _types) external;

    function setRescueEnabled(bool _flag) external;

    function setDevRescueEnabled(bool _flag) external;

    function setGameContract(address _contract, bool flag) external;

    function transferOwnership(address newOwner) external;

    function getUserStakedAlphaGame(address owner, uint8 game) external view returns (uint16[] memory stakedAlphas);

    function devRescueEnabled() external view returns (bool);

}
// 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;
    }
}