contract Daisy is Context, ERC20, Ownable {
    using SafeMath for uint256;
    using Address for address;

    uint16 public _burnFee;
    uint16 public _rewardFee;
    uint16 public _blackholeMonitor;
    uint16 public _reserveFee;
    uint16 public _totalFee;

    address public _blackholeWallet;    // Blackhole wallet = reserver wallet with 0%
    address public _reserveWallet;      // Holds reserve's wallet address

    address[] public _stakeholders;     // Holds all stakeholders addresses

    // Events
    event StakeHolderAdded(address stakeholder);
    event StakeHolderRemoved(address stakeholder);
    event TransactionFeeDistributed(uint256 totalFeesDistributed);
    event BlackholeWalletChanged(address fromAddress, address toAddress);
    event ReserveWalletChanged(address fromAddress, address toAddress);
    event RewardDistributedToAccount(address toAddress, uint256 totalReward, uint256 amount);
    event Debug(string str, address addr, uint256 num);

    /**
     * @notice constructor
     *
     * Mints 1,000,000,000,000 tokens and sends to deployer's address
     * Sets fees percents
     * Sets wallet address for dev and reserve
     *
     * Requirements -
     * - blackhole and reserveWallet cannot be 0 address
     */
    constructor(
        address blackholeWallet,
        address reserveWallet
    )
        ERC20("Daisy", "$wick")
    {
        _mint(_msgSender(), 1000000000000E18);    // Mint 1,000,000,000,000 tokens

        _rewardFee = 100;                   // 1 percent
        _burnFee = 100;                     // 1 percent
        _blackholeMonitor = 0;                // 0 percent
        _reserveFee = 200;                  // 1 percent
        _totalFee = _rewardFee + _burnFee + _blackholeMonitor + _reserveFee;

        require(blackholeWallet != address(0), "Blackhole wallet address cannot be 0 address");
        require(reserveWallet != address(0), "Reserve wallet address cannot be 0 address");

        _blackholeWallet = blackholeWallet;
        _reserveWallet = reserveWallet;
    }

    /**
     * @notice Allow owner of contract to mint new tokens to given account
     * of given amount.
     *
     * Requirements -
     * - account address cannot be 0 address
     * - amount cannot be 0
     */
    function mint(address account, uint256 amount)
        public
        onlyOwner
    {
        _mint(account, amount);
    }

    /**
     * @notice Burn given amount of tokens from senders account
     * This reduces the total supply of tokens
     *
     * Requirements -
     * - amount cannot be 0
     */
    function burn(uint256 amount) public {
        _burn(_msgSender(), amount);
    }

    /**
     * @notice Returns the number of tokens in circulation
     */
    function circulationSupply() public view returns (uint256) {
        return totalSupply().sub(balanceOf(owner()));
    }

    /**
     * @notice returns the total supply staked by the _stakeholders combined
     */
    function stakedSupply() public view returns (uint256) {
        uint256 total = 0;
        for (uint256 i = 0; i < _stakeholders.length; i++) {
            total = total.add(balanceOf(_stakeholders[i]));
        }
        return total;
    }

    /**
     * @notice Transfers the amount to recipient.
     * - Add or checks stakeholders array for recipient address
     * - calls for fees distribution
     *
     * 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)
    {
        require(balanceOf(_msgSender()) >= amount, "Not enough token balance.");

        if (_checkWithoutFee()) {
            _transfer(_msgSender(), recipient, amount);
        } else {
            // check if funds available in supply for fees distribution
            uint256 fundsRequired = amount.mul(uint256(_totalFee)).div(10000);
            require(balanceOf(owner()) >= fundsRequired, "Not enough supply available.");

            // Run fees distribution functionality
            _feesDistribution(amount);

            // transfer all amount to recipient
            _transfer(_msgSender(), recipient, amount);
        }

        // Add recipient to stakeholders if not exist
        // Admin will not get added
        _addStakeholder(recipient);

        return true;
    }

    /**
     * @notice Transfers the amount of token to recipient from sender's account.
     * - Add or checks stakeholders array for recipient address
     * - calls for fees distribution
     * - Reduces the allowance of caller
     *
     * Requirements:
     *
     * - sender cannot be the zero address.
     * - recipient cannot be the zero address.
     * - the sender must have a balance of at least amount.
     */
    function transferFrom(address sender, address recipient, uint256 amount)
        public
        virtual
        override
        returns (bool)
    {
        require(balanceOf(sender) >= amount, "Not enough token balance.");
        require(allowance(sender, _msgSender()) >= amount, "ERC20: transfer amount exceeds allowance");

        if (_checkWithoutFee()) {
            _transfer(sender, recipient, amount);
        } else {
            // check if funds available in supply for fees distribution
            uint256 fundsRequired = amount.mul(uint256(_totalFee)).div(10000);
            require(balanceOf(owner()) >= fundsRequired, "Not enough supply available.");

            // Run fees distribution functionality
            _feesDistribution(amount);

            // transfer all amount to recipient
            _transfer(sender, recipient, amount);
        }

        // Reduce the allowance of caller
        _approve(
            sender,
            _msgSender(),
            allowance(sender, _msgSender()).sub(
                amount,
                "ERC20: transfer amount exceeds allowance"
            )
        );

        // Add recipient to stakeholders if not exist
        // Admin will not get added
        _addStakeholder(recipient);

        return true;
    }

    /**
    * @notice Returns whether _address is stakeholder or not
    * also returns the position of the address in _stakeholders array
    *
    * Requirements -
    * - _address cannot be zero
    */
    function isStakeholder(address _address)
        public
        view
        returns(bool, uint256)
    {
        for (uint256 s = 0; s < _stakeholders.length; s += 1){
            if (_address == _stakeholders[s]) return (true, s);
        }
        return (false, 0);
    }

    /**
     * @notice Distributes all types of fees during the transaction
     * The tokens are distributed from owners account
     * Calculates the fee amounts on amount of transaction
     *
     * Reward fee = 1%
     * Burn fee = 1%
     * Blackhole = 0%
     * Reserve Fee = 1%
     *
     * Requirements -
     * - Owners account should have 5% of amount tokens in his wallet to payout fees
     * - amount cannot be 0
     *
     * @param amount (uint256) - cannot be 0 amount
     */
    function _feesDistribution(uint256 amount) internal {
        uint256 tokensInCirculation = circulationSupply();
        
        if (tokensInCirculation > 0) {
            uint256 rewardFeeAmount = amount.mul(uint256(_rewardFee)).div(10000);
            uint256 burnFeeAmount = amount.mul(uint256(_burnFee)).div(10000);
            uint256 blackholeMonitorAmount = amount.mul(uint256(_blackholeMonitor)).div(10000);
            uint256 reserveFeeAmount = amount.mul(uint256(_reserveFee)).div(10000);
            
            // Distribute fees
            _transfer(owner(), _blackholeWallet, blackholeMonitorAmount);   
            _transfer(owner(), _reserveWallet, reserveFeeAmount);       // 1% of transaction amount sent to reserve wallet
    
            // burn 1% of token amount completely
            _burn(owner(), burnFeeAmount);
    
            // Distribute 2% of transaction amount as fees between all token holders
            _distributeFeesToTokenHolders(rewardFeeAmount, tokensInCirculation);
    
            emit TransactionFeeDistributed(rewardFeeAmount + burnFeeAmount + blackholeMonitorAmount + reserveFeeAmount);
        }
        
    }

    /**
     * @notice Distribute fees of current transaction to all stake holders as per
     * their holding share.
     *
     * rewardFeeAmount is total amount of fee of transaction to be distributed.
     * tokensInCirculation is the total amount of token open for sale/exchange in
     * market, but which are not hold by owner.
     *
     * Requirements -
     * - tokensInCirculation cannot be 0
     */
    function _distributeFeesToTokenHolders(uint256 rewardFeeAmount, uint256 tokensInCirculation) internal {
        require(tokensInCirculation > 0, "No tokens in circulation");
        uint256 len = _stakeholders.length;
        
        for (uint256 index = 0; index < len; index++) {
            uint256 rewardAmountForAccount =  _rewardAmountForAccount(_stakeholders[index], tokensInCirculation, rewardFeeAmount);
            if (rewardAmountForAccount > 0) {
                _transfer(owner(), _stakeholders[index], rewardAmountForAccount);
                
                emit RewardDistributedToAccount(_stakeholders[index], rewardFeeAmount, rewardAmountForAccount);
            }
        }
    }

    /**
     * @notice Calculates the reward amount for given account
     * This returns 0 if account is not a stakeholder
     *
     * @param account - user account address
     * @param tokensInCirculation - Total number of tokens in circulation
     * @param rewardFeeAmount - reward token amount for given transaction
     */
    function _rewardAmountForAccount(address account, uint256 tokensInCirculation, uint256 rewardFeeAmount)
        internal
        view
        returns(uint256)
    {
        (bool _isStakeholder, ) = isStakeholder(account);

        if (!_isStakeholder) return 0;

        uint256 holderBalance = balanceOf(account);
        uint256 holdersShare = (rewardFeeAmount.mul(holderBalance)).div(tokensInCirculation);

        return holdersShare;
    }

    /**
     * @notice This determines when should fees be paid for transactions.
     */
    function _checkWithoutFee() internal view returns (bool) {
        if (_msgSender() == owner()) return true;
        return false;
    }

    /**
    * @notice Adds _stakeholder to _stakeholders array.
    * This are individual accounts which holds the Daisy tokens
    *
    * - _stakeholder cannot be 0 address
    */
    function _addStakeholder(address _stakeholder)
        internal
    {
        require(_stakeholder != address(0), "Address cannot be 0 address");
        (bool _isStakeholder, ) = isStakeholder(_stakeholder);

        if (!_isStakeholder && (_stakeholder != owner()) && !_stakeholder.isContract()) {
            _stakeholders.push(_stakeholder);

            emit StakeHolderAdded(_stakeholder);
        }
    }

    /**
    * @notice A method to remove a _stakeholder from _stakeholders array.
    * _stakeholder cannot be 0 address
    */
    function removeStakeholder(address _stakeholder)
        public
        onlyOwner
    {
        require(_stakeholder != address(0), "Address cannot be 0 address");
        (bool _isStakeholder, uint256 s) = isStakeholder(_stakeholder);

        if (_isStakeholder) {
            _stakeholders[s] = _stakeholders[_stakeholders.length - 1];
            _stakeholders.pop();

            emit StakeHolderRemoved(_stakeholder);
        }
    }

    /**
     * @notice Updates Blackhole with given _account
     * _account cannot be a contract or 0 address
     */
    function updateBlackholeWallet(address _account)
        public
        onlyOwner
    {
        require(_account != address(0), "Account address is 0 address.");
        require(!_account.isContract(), "Account address cannot be contract address");

        _blackholeWallet = _account;
        emit BlackholeWalletChanged(_account, _blackholeWallet);
    }

    /**
     * @notice Updates _reserveWallet with given _account
     * _account cannot be a contract or 0 address
     */
    function updateReserveWallet(address _account)
        public
        onlyOwner
    {
        require(_account != address(0), "Account address is 0 address.");
        require(!_account.isContract(), "Account address cannot be contract address");

        _reserveWallet = _account;
        emit ReserveWalletChanged(_account, _reserveWallet);
    }

}
/**
 * @notice Implementation of the BEP20 token.
 *
 * This implements a BEP20 token named daisy. On deployment it mints initialSupply to the
 * owner's account address. Later for every transaction on the contract a 4% of transaction
 * amount is calculated and is distributed/burned from owner's address as per the decided
 * fees structure.
 */
function circulationSupply() public view returns (uint256) {
    return totalSupply().sub(balanceOf(owner()));
}

/**
 * @notice Returns the number of tokens in circulation
 */