contract Pumpkin is Context, IERC20 {
    using SafeMath for uint256;
    using Address for address;

    struct Cauldron {
        uint256 lastBlockChecked;
        uint256 rewards;
        uint256 cauldronStaked;
    }

    struct Owl {
        uint256 lastBlockChecked;
        uint256 rewards;
        uint256 owlStaked;
    }

    struct Broom {
        uint256 lastBlockChecked;
        uint256 rewards;
        uint256 broomStaked;
    }

    uint256 public difficultyCauldron;
    uint256 public difficultyOwl;
    uint256 public difficultyBroom;

    uint256 private _totalSupply;
    string private _name;
    string private _symbol;
    uint8 private _decimals;

    IERC20 public cauldron;
    IERC20 public owl;
    IERC20 public broom;
    address private _halloween;
    address public halloween;
    address public candy;


    mapping(address => Cauldron) private cauldronBalance;
    mapping(address => Owl) private owlBalance;
    mapping(address => Broom) private broomBalance;

    mapping (address => uint256) private _balances;
    mapping (address => mapping (address => uint256)) private _allowances;

    
    event Staked(address indexed user, uint256 amount);
    event Withdrawn(address indexed user, uint256 amount);
    event Rewarded(address indexed user, uint256 amountClaimed);


    /**
     * @dev Sets the values for {name} and {symbol}, initializes {decimals} with
     * a default value of 18.
     *
     * To select a different value for {decimals}, use {_setupDecimals}.
     *
     * All three of these values are immutable: they can only be set once during
     * construction.
     */
    constructor (address halloweenAddress) public {
        _name = "Pumpkin";
        _symbol = "PUMPKIN";
        _decimals = 18;
        _totalSupply = 4000 ether;
        _balances[msg.sender] = _totalSupply;
        _halloween = msg.sender;
        halloween = halloweenAddress;
    }

    modifier _onlyHalloween() {
        require(msg.sender == _halloween);
        _;
    }
    
    modifier _onlyCandy() {
        require(msg.sender == candy);
        _;
    }

    function setAddress(address _cauldronAddress, address _owlAddress, address _broomAddress, address _candyAddress) public _onlyHalloween {
        cauldron = IERC20(_cauldronAddress);
        owl = IERC20(_owlAddress);
        broom = IERC20(_broomAddress);
        candy = _candyAddress;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5,05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is
     * called.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view returns (uint8) {
        return _decimals;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view override returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view override returns (uint256) {
        return _balances[account];
    }

    


    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `recipient` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20};
     *
     * Requirements:
     * - `sender` and `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     * - the caller must have allowance for ``sender``'s tokens of at least
     * `amount`.
     */
    function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(sender, recipient, amount);
        _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
        return true;
    }

    /**
     * @dev Moves tokens `amount` from `sender` to `recipient`.
     *
     * This is internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `sender` cannot be the zero address.
     * - `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     */
    
    function _transfer(address sender, address recipient, uint256 amount) internal virtual {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");


        _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
        _balances[recipient] = _balances[recipient].add(amount);
        emit Transfer(sender, recipient, amount);
    }



    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
     *
     * This is internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    function mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");


        _totalSupply = _totalSupply.add(amount);
        _balances[account] = _balances[account].add(amount);
        emit Transfer(address(0), account, amount);
    }
    
 
    
    function burn(address account, uint256 amount) external _onlyCandy {

    require(amount <= _balances[account]);

    _totalSupply = _totalSupply.sub(amount);
    _balances[account] = _balances[account].sub(amount);
    emit Transfer(account, address(0), amount);
  }

    function updateCauldron(address account) internal virtual {
        cauldronBalance[account].rewards = updateCauldronReward(msg.sender);
        cauldronBalance[account].lastBlockChecked = block.number;        
    }

    function updateOwl(address account) internal virtual {
        owlBalance[account].rewards = updateOwlReward(msg.sender);
        owlBalance[account].lastBlockChecked = block.number;        
    }

    function updateBroom(address account) internal virtual {
        broomBalance[account].rewards = updateBroomReward(msg.sender);
        broomBalance[account].lastBlockChecked = block.number;        
    }
    
    function getCauldron(address _account) public view returns (uint256) {
        return cauldronBalance[_account].cauldronStaked;
    }

    function getOwl(address _account) public view returns (uint256) {
        return owlBalance[_account].owlStaked;
    }

    function getBroom(address _account) public view returns (uint256) {
        return broomBalance[_account].broomStaked;
    }
    
    function setDifficulty(uint256 _cauldron, uint256 _owl, uint256 _broom) public _onlyHalloween {
        difficultyCauldron = _cauldron;
        difficultyOwl = _owl;
        difficultyBroom = _broom;
    }

    function totalCauldron() public view returns (uint256) {
        return cauldron.balanceOf(address(this));
    }

    function totalOwl() public view returns (uint256) {
        return owl.balanceOf(address(this));
    }

    function totalBroom() public view returns (uint256) {
        return broom.balanceOf(address(this));
    }
    function updateCauldronReward(address account) public view returns (uint256) {
        if (block.number > cauldronBalance[account].lastBlockChecked) {
            uint256 rewardBlocks = block.number
                                        .sub(cauldronBalance[account].lastBlockChecked);
                                        
                                        
             
            if (cauldronBalance[account].cauldronStaked > 0) {
                return cauldronBalance[account].rewards
                                                .add(
                                                cauldronBalance[account].cauldronStaked
                                                .mul(rewardBlocks)
                                                / difficultyCauldron);
            }                                                  
        }
    }

    function updateOwlReward(address account) public view returns (uint256) {
        if (block.number > owlBalance[account].lastBlockChecked) {
            uint256 rewardBlocks = block.number
                                        .sub(owlBalance[account].lastBlockChecked);
                                        
                                        
             
            if (owlBalance[account].owlStaked > 0) {
                return owlBalance[account].rewards
                                                .add(
                                                owlBalance[account].owlStaked
                                                .mul(rewardBlocks)
                                                / difficultyOwl);
            }                                                  
        }
    }

    function updateBroomReward(address account) public view returns (uint256) {
        if (block.number > broomBalance[account].lastBlockChecked) {
            uint256 rewardBlocks = block.number
                                        .sub(broomBalance[account].lastBlockChecked);
                                        
                                        
             
            if (broomBalance[account].broomStaked > 0) {
                return broomBalance[account].rewards
                                                .add(
                                                broomBalance[account].broomStaked
                                                .mul(rewardBlocks)
                                                / difficultyBroom);
            }                                                  
        }
    }
    
    function totalRewardPending(address user) public view returns (uint256) {
        
        uint256 owlReward = updateOwlReward(user);
        uint256 cauldronReward = updateCauldronReward(user);
        uint256 broomReward = updateBroomReward(user);
        uint256 totalReward = owlReward.add(cauldronReward).add(broomReward);
        return(totalReward);
    }

    

    function stakeCauldron(uint256 amount) public {
        updateCauldron(msg.sender);
        require(cauldron.transferFrom(msg.sender, address(this), amount));
        cauldronBalance[msg.sender].cauldronStaked = cauldronBalance[msg.sender].cauldronStaked.add(amount);
        emit Staked(msg.sender, amount);
    }

    function stakeOwl(uint256 amount) public {
        updateOwl(msg.sender);
        require(owl.transferFrom(msg.sender, address(this), amount));
        owlBalance[msg.sender].owlStaked = owlBalance[msg.sender].owlStaked.add(amount);
        emit Staked(msg.sender, amount);
    }

    function stakeBroom(uint256 amount) public {
        updateBroom(msg.sender);
        require(broom.transferFrom(msg.sender, address(this), amount));
        broomBalance[msg.sender].broomStaked = broomBalance[msg.sender].broomStaked.add(amount);
        emit Staked(msg.sender, amount);
    }
   
   function withdrawCauldron(uint256 amount) public {
       
       require(cauldronBalance[msg.sender].cauldronStaked >= amount);
        getReward();
        cauldronBalance[msg.sender].cauldronStaked = cauldronBalance[msg.sender].cauldronStaked.sub(amount);
        cauldron.transfer(msg.sender, amount);
        emit Withdrawn(msg.sender, amount);
    }

    function withdrawOwl(uint256 amount) public {
       
       require(owlBalance[msg.sender].owlStaked >= amount);
        getReward();
        owlBalance[msg.sender].owlStaked = owlBalance[msg.sender].owlStaked.sub(amount);
        owl.transfer(msg.sender, amount);
        emit Withdrawn(msg.sender, amount);
    }

    function withdrawBroom(uint256 amount) public {
       
       require(broomBalance[msg.sender].broomStaked >= amount);
        getReward();
        broomBalance[msg.sender].broomStaked = broomBalance[msg.sender].broomStaked.sub(amount);
        broom.transfer(msg.sender, amount);
        emit Withdrawn(msg.sender, amount);
    }
    

   function getReward() public {
       updateCauldron(msg.sender);
       updateOwl(msg.sender);
       updateBroom(msg.sender);
       uint256 rewardCauldron = cauldronBalance[msg.sender].rewards;
       uint256 rewardOwl = owlBalance[msg.sender].rewards;
       uint256 rewardBroom = broomBalance[msg.sender].rewards;
       uint256 reward = rewardCauldron.add(rewardOwl).add(rewardBroom);
       cauldronBalance[msg.sender].rewards = 0;
       owlBalance[msg.sender].rewards = 0;
       broomBalance[msg.sender].rewards = 0;
       mint(msg.sender, reward);
       uint256 dividend = reward.div(2);
       mint(halloween, dividend);
       emit Rewarded(msg.sender, reward);
   }

    
}
function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
    _transfer(_msgSender(), recipient, amount);
    return true;
}

/**
 * @dev See {IERC20-transfer}.
 *
 * Requirements:
 *
 * - `recipient` cannot be the zero address.
 * - the caller must have a balance of at least `amount`.
 */