contract Orbit is ERC20, Ownable {
    uint256 public rewardGenerationStartTime;

    // Tue Apr 06 2032 05:00:00 GMT+0000
    uint256 public constant END = 1964840400;

    bool public rewardStatus = false;

    mapping(address => uint256) public rewards; //to store user unclaimed rewards
    mapping(address => uint256) public lastUpdate;
    mapping(address => bool) public allowedAddresses;

    event RewardPaid(address indexed user, uint256 reward);

    constructor(address _pmfcAddress) ERC20("Orbits", "ORBITS") {
        allowedAddresses[_pmfcAddress] = true;
    }

    // Admin only functions

    /** 
        Pause reward if unpaused, unpause if paused.
        True = unpaused   
        False = paused
    */
    function flipReward() external onlyOwner {
        rewardStatus = !rewardStatus;
    }

    // To set reward generation start block
    function startRewardGeneration() external onlyOwner {
        rewardGenerationStartTime = block.timestamp;
    }

    // To grant/ revoke access to call 'updateReward' and 'claimReward' function along with burn access
    function setAllowedAddresses(address _address, bool _access)
        external
        onlyOwner
    {
        allowedAddresses[_address] = _access;
    }

    // Reward related functions

    function updateReward(
        address from,
        address to,
        uint256 reward,
        uint256 nftBalanceFrom,
        uint256 nftBalanceTo
    ) external {
        require(allowedAddresses[msg.sender], "Unauthorized caller");

        // Prevent rewards generation until our start is hit.
        if (rewardGenerationStartTime != 0) {
            uint256 time = min(block.timestamp, END);
            if (from != address(0)) {
                rewards[from] += getPendingReward(from, reward, nftBalanceFrom);
                uint256 timerFrom = lastUpdate[from];
                if (timerFrom != END) lastUpdate[from] = time;
            }
            if (to != address(0)) {
                rewards[to] += getPendingReward(to, reward, nftBalanceTo);
                uint256 timerTo = lastUpdate[to];
                if (timerTo != END) lastUpdate[to] = time;
            }
        }
    }

    // Pay out the holder their rewards
    function claimReward(address user) external {
        require(allowedAddresses[msg.sender], "Unauthorized caller");
        require(rewardStatus, "Reward claiming is turned off");

        //Prevent rewards claiming until our start is hit.
        require(
            rewardGenerationStartTime != 0,
            "Reward generation is not activated yet"
        );

        uint256 reward = rewards[user];
        rewards[user] = 0;
        _mint(user, reward);

        emit RewardPaid(user, reward);
    }

    function getTotalClaimable(
        address user,
        uint256 reward,
        uint256 nftBalance
    ) external view returns (uint256) {
        require(allowedAddresses[msg.sender], "Unauthorized caller");
        // Prevent rewards until our start is hit.
        if (rewardGenerationStartTime == 0) {
            return 0;
        } else {
            return rewards[user] + getPendingReward(user, reward, nftBalance);
        }
    }

    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    function getPendingReward(
        address user,
        uint256 reward,
        uint256 nftBalance
    ) private view returns (uint256) {
        uint256 time = min(block.timestamp, END);
        return
            (nftBalance *
                reward *
                (time -
                    (
                        lastUpdate[user] >= rewardGenerationStartTime
                            ? lastUpdate[user]
                            : rewardGenerationStartTime
                    ))) / 86400;
    }

    function burn(address from, uint256 amount) external {
        require(
            allowedAddresses[msg.sender],
            "Address does not have permission to burn"
        );
        _burn(from, amount);
    }
}
/// @author 1st Block Labs
function setAllowedAddresses(address _address, bool _access)
    external
    onlyOwner
{
    allowedAddresses[_address] = _access;
}

// To grant/ revoke access to call 'updateReward' and 'claimReward' function along with burn access