// File: contracts/upgradeability/OwnedUpgradeabilityProxy.sol



// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "../interfaces/IIBGEvents.sol";

/**
 * @title OwnedUpgradeabilityProxy
 * @dev This contract combines an upgradeability proxy with basic authorization control functionalities
 */
contract OwnedUpgradeabilityProxy is IIBGEvents {
    /**
     * @dev Event to show ownership has been transferred
     * @param previousOwner representing the address of the previous owner
     * @param newOwner representing the address of the new owner
     */
    event ProxyOwnershipTransferred(address previousOwner, address newOwner);

    /**
     * @dev This event will be emitted every time the implementation gets upgraded
     * @param implementation representing the address of the upgraded implementation
     */
    event Upgraded(address indexed implementation);

    // Storage position of the address of the maintenance boolean
    bytes32 private constant maintenancePosition = keccak256("com.ibg.proxy.maintenance");
    // Storage position of the address of the current implementation
    bytes32 private constant implementationPosition = keccak256("com.ibg.proxy.implementation");
    // Storage position of the owner of the contract
    bytes32 private constant proxyOwnerPosition = keccak256("com.ibg.proxy.owner");

    /**
     * @dev the constructor sets the original owner of the contract to the sender account.
     */
    constructor() {
        setUpgradeabilityOwner(msg.sender);
    }

    /**
     * @dev Tells if contract is on maintenance
     * @return _maintenance if contract is on maintenance
     */
    function maintenance() public view returns (bool _maintenance) {
        bytes32 position = maintenancePosition;
        assembly {
            _maintenance := sload(position)
        }
    }

    /**
     * @dev Sets if contract is on maintenance
     */
    function setMaintenance(bool _maintenance) external onlyProxyOwner {
        bytes32 position = maintenancePosition;
        assembly {
            sstore(position, _maintenance)
        }
    }

    /**
     * @dev Tells the address of the owner
     * @return owner the address of the owner
     */
    function proxyOwner() public view returns (address owner) {
        bytes32 position = proxyOwnerPosition;
        assembly {
            owner := sload(position)
        }
    }

    /**
     * @dev Sets the address of the owner
     */
    function setUpgradeabilityOwner(address newProxyOwner) internal {
        bytes32 position = proxyOwnerPosition;
        assembly {
            sstore(position, newProxyOwner)
        }
    }

    /**
     * @dev Allows the current owner to transfer control of the contract to a newOwner.
     * @param newOwner The address to transfer ownership to.
     */
    function transferProxyOwnership(address newOwner) public onlyProxyOwner {
        require(newOwner != address(0), 'OwnedUpgradeabilityProxy: INVALID');
        emit ProxyOwnershipTransferred(proxyOwner(), newOwner);
        setUpgradeabilityOwner(newOwner);
    }

    /**
     * @dev Allows the proxy owner to upgrade the current version of the proxy.
     * @param implementation representing the address of the new implementation to be set.
     */
    function upgradeTo(address implementation) public onlyProxyOwner {
        _upgradeTo(implementation);
    }

    /**
     * @dev Allows the proxy owner to upgrade the current version of the proxy and call the new implementation
     * to initialize whatever is needed through a low level call.
     * @param implementation representing the address of the new implementation to be set.
     * @param data represents the msg.data to bet sent in the low level call. This parameter may include the function
     * signature of the implementation to be called with the needed payload
     */
    function upgradeToAndCall(address implementation, bytes memory data) payable public onlyProxyOwner {
        upgradeTo(implementation);
        (bool success, ) = address(this).call{ value: msg.value }(data);
        require(success, "OwnedUpgradeabilityProxy: INVALID");
    }

    /**
     * @dev Fallback function allowing to perform a delegatecall to the given implementation.
     * This function will return whatever the implementation call returns
     */
    fallback() external payable {
        _fallback();
    }

    receive () external payable {
        _fallback();
    }

    /**
     * @dev Tells the address of the current implementation
     * @return impl address of the current implementation
     */
    function implementation() public view returns (address impl) {
        bytes32 position = implementationPosition;
        assembly {
            impl := sload(position)
        }
    }

    /**
     * @dev Sets the address of the current implementation
     * @param newImplementation address representing the new implementation to be set
     */
    function setImplementation(address newImplementation) internal {
        bytes32 position = implementationPosition;
        assembly {
            sstore(position, newImplementation)
        }
    }

    /**
     * @dev Upgrades the implementation address
     * @param newImplementation representing the address of the new implementation to be set
     */
    function _upgradeTo(address newImplementation) internal {
        address currentImplementation = implementation();
        require(currentImplementation != newImplementation, 'OwnedUpgradeabilityProxy: INVALID');
        setImplementation(newImplementation);
        emit Upgraded(newImplementation);
    }

    function _fallback() internal {
        if (maintenance()) {
            require(msg.sender == proxyOwner(), 'OwnedUpgradeabilityProxy: FORBIDDEN');
        }
        address _impl = implementation();
        require(_impl != address(0), 'OwnedUpgradeabilityProxy: INVALID');
        assembly {
            let ptr := mload(0x40)
            calldatacopy(ptr, 0, calldatasize())
            let result := delegatecall(gas(), _impl, ptr, calldatasize(), 0, 0)
            let size := returndatasize()
            returndatacopy(ptr, 0, size)

            switch result
            case 0 { revert(ptr, size) }
            default { return(ptr, size) }
        }
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyProxyOwner() {
        require(msg.sender == proxyOwner(), 'OwnedUpgradeabilityProxy: FORBIDDEN');
        _;
    }
}
// File: contracts/interfaces/IIBGEvents.sol



// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

interface IIBGEvents {

    event Registration(address indexed _user, address indexed referrer, uint registrationTime);
    event PackPurchased(address indexed _user, uint indexed _pack, uint _plan, uint currentPackAmount, uint time);
    event StakedToken(address indexed stakedBy, uint amountStaked, uint plan, uint time, uint stakingPeriod);
    event DirectReferralIncome(address indexed _from, address indexed receiver, uint incomeRecieved, uint indexed level, uint time);
    event LostIncome(address indexed _from, address indexed reciever, uint incomeLost, uint indexed level, uint time);
    event YieldIncome(address indexed user, uint yieldRecieved, uint time);
    event YieldMatchingIncome(address indexed _from, address indexed receiver, uint incomeRecieved, uint indexed level, uint time);
    event YieldMatchingLostIncome(address indexed _from, address indexed reciever, uint matchingLostIncome, uint indexed level, uint time);
}