contract ERC777BaseToken is ProxyImplementation, ERC20BaseToken, /*ERC777Token,*/ ERC777TokenEvents, ERC820Implementer {

    mapping(address => mapping(address => bool)) internal mAuthorizedOperators;

    constructor() public {
        init777();
    }
    function init777() public phase('777') {
        setInterfaceImplementation("ERC777Token", address(this));
        setInterfaceImplementation("ERC20Token", address(this));
    }

    /// @return the granularity of the token
    function granularity() public pure returns (uint256) { return 1; }

    /// @notice Return the list of default operators
    /// @return the list of all the default operators
    function defaultOperators() public pure returns (address[] memory) {}

    /// @notice Send `_amount` of tokens to address `_to` passing `_data` to the recipient
    /// @param _to The address of the recipient
    /// @param _amount The number of tokens to be sent
    function send(address _to, uint256 _amount, bytes memory _data) public {
        _send(msg.sender, msg.sender, _to, _amount, _data, "", true);
    }

    /// @notice Authorize a third party `_operator` to manage (send) `msg.sender`'s tokens.
    /// @param _operator The operator that wants to be Authorized
    function authorizeOperator(address _operator) public {
        require(_operator != msg.sender, "Cannot authorize yourself as an operator");
        mAuthorizedOperators[_operator][msg.sender] = true;
        emit AuthorizedOperator(_operator, msg.sender);
    }

    /// @notice Revoke a third party `_operator`'s rights to manage (send) `msg.sender`'s tokens.
    /// @param _operator The operator that wants to be Revoked
    function revokeOperator(address _operator) public {
        require(_operator != msg.sender, "Cannot revoke yourself as an operator");
        mAuthorizedOperators[_operator][msg.sender] = false;
        emit RevokedOperator(_operator, msg.sender);
    }

    /// @notice Check whether the `_operator` address is allowed to manage the tokens held by `_tokenHolder` address.
    /// @param _operator address to check if it has the right to manage the tokens
    /// @param _tokenHolder address which holds the tokens to be managed
    /// @return `true` if `_operator` is authorized for `_tokenHolder`
    function isOperatorFor(address _operator, address _tokenHolder) public view returns (bool) {
        return (_operator == _tokenHolder // solium-disable-line operator-whitespace
            || mAuthorizedOperators[_operator][_tokenHolder]);
    }

    /// @notice Send `_amount` of tokens on behalf of the address `from` to the address `to`.
    /// @param _from The address holding the tokens being sent
    /// @param _to The address of the recipient
    /// @param _amount The number of tokens to be sent
    /// @param _data Data generated by the user to be sent to the recipient
    /// @param _operatorData Data generated by the operator to be sent to the recipient
    function operatorSend(address _from, address _to, uint256 _amount, bytes memory _data, bytes memory _operatorData) public {
        require(isOperatorFor(msg.sender, _from), "Not an operator");
        _send(msg.sender, _from, _to, _amount, _data, _operatorData, true);
    }

    

    /* -- Helper Functions -- */
    
    /// @notice Check whether an address is a regular address or not.
    /// @param _addr Address of the contract that has to be checked
    /// @return `true` if `_addr` is a regular address (not a contract)
    function isRegularAddress(address _addr) internal view returns(bool) {
        if (_addr == address(0)) { return false; }
        uint size;
        assembly { size := extcodesize(_addr) } // solium-disable-line security/no-inline-assembly
        return size == 0;
    }

    /// @notice Helper function actually performing the sending of tokens.
    /// @param _operator The address performing the send
    /// @param _from The address holding the tokens being sent
    /// @param _to The address of the recipient
    /// @param _amount The number of tokens to be sent
    /// @param _data Data generated by the user to be passed to the recipient
    /// @param _operatorData Data generated by the operator to be passed to the recipient
    /// @param _preventLocking `true` if you want this function to throw when tokens are sent to a contract not
    ///  implementing `ERC777tokensRecipient`.
    ///  ERC777 native Send functions MUST set this parameter to `true`, and backwards compatible ERC20 transfer
    ///  functions SHOULD set this parameter to `false`.
    function _send(
        address _operator,
        address _from,
        address _to,
        uint256 _amount,
        bytes memory _data,
        bytes memory _operatorData,
        bool _preventLocking
    )
        internal
    {
        callSender(_operator, _from, _to, _amount, _data, _operatorData);

        super._transfer(_from, _to, _amount); // TODO test it does not call this._transfer (which would emit 2 Sent event)

        callRecipient(_operator, _from, _to, _amount, _data, _operatorData, _preventLocking);

        emit Sent(_operator, _from, _to, _amount, _data, _operatorData);
    }

    function _transfer(address _from, address _to, uint256 _amount) internal {
        _send(msg.sender, _from, _to, _amount, "", "", false);
    }
    
    /// @notice Helper function that checks for ERC777TokensRecipient on the recipient and calls it.
    ///  May throw according to `_preventLocking`
    /// @param _operator The address performing the send or mint
    /// @param _from The address holding the tokens being sent
    /// @param _to The address of the recipient
    /// @param _amount The number of tokens to be sent
    /// @param _data Data generated by the user to be passed to the recipient
    /// @param _operatorData Data generated by the operator to be passed to the recipient
    /// @param _preventLocking `true` if you want this function to throw when tokens are sent to a contract not
    ///  implementing `ERC777TokensRecipient`.
    ///  ERC777 native Send functions MUST set this parameter to `true`, and backwards compatible ERC20 transfer
    ///  functions SHOULD set this parameter to `false`.
    function callRecipient(
        address _operator,
        address _from,
        address _to,
        uint256 _amount,
        bytes memory _data,
        bytes memory _operatorData,
        bool _preventLocking
    )
        internal
    {
        address recipientImplementation = interfaceAddr(_to, "ERC777TokensRecipient");
        if (recipientImplementation != address(0)) {
            ERC777TokensRecipient(recipientImplementation).tokensReceived(
                _operator, _from, _to, _amount, _data, _operatorData);
        } else if (_preventLocking) {
            require(isRegularAddress(_to), "Cannot send to contract without ERC777TokensRecipient");
        }
    }

    /// @notice Helper function that checks for ERC777TokensSender on the sender and calls it.
    ///  May throw according to `_preventLocking`
    /// @param _from The address holding the tokens being sent
    /// @param _to The address of the recipient
    /// @param _amount The amount of tokens to be sent
    /// @param _data Data generated by the user to be passed to the recipient
    /// @param _operatorData Data generated by the operator to be passed to the recipient
    ///  implementing `ERC777TokensSender`.
    ///  ERC777 native Send functions MUST set this parameter to `true`, and backwards compatible ERC20 transfer
    ///  functions SHOULD set this parameter to `false`.
    function callSender(
        address _operator,
        address _from,
        address _to,
        uint256 _amount,
        bytes memory _data,
        bytes memory _operatorData
    )
        internal
    {
        address senderImplementation = interfaceAddr(_from, "ERC777TokensSender");
        if (senderImplementation == address(0)) { return; }
        ERC777TokensSender(senderImplementation).tokensToSend(
            _operator, _from, _to, _amount, _data, _operatorData);
    }


    // extra functionalities //////////////////////////////////////////////////////////////////////////////

    function _mint(address _to, uint256 _amount) internal {
        super._mint(_to, _amount);
        emit Minted(msg.sender, _to, _amount, '');
    }

    function _burn(address _from, uint256 _amount) internal {
        super._burn(_from, _amount);
        emit Burned(msg.sender, _from, _amount, '', '');
    }

}
function _send(
    address _operator,
    address _from,
    address _to,
    uint256 _amount,
    bytes memory _data,
    bytes memory _operatorData,
    bool _preventLocking
)
    internal
{
    callSender(_operator, _from, _to, _amount, _data, _operatorData);

    super._transfer(_from, _to, _amount); // TODO test it does not call this._transfer (which would emit 2 Sent event)

    callRecipient(_operator, _from, _to, _amount, _data, _operatorData, _preventLocking);

    emit Sent(_operator, _from, _to, _amount, _data, _operatorData);
}

/// @notice Helper function actually performing the sending of tokens.
/// @param _operator The address performing the send
/// @param _from The address holding the tokens being sent
/// @param _to The address of the recipient
/// @param _amount The number of tokens to be sent
/// @param _data Data generated by the user to be passed to the recipient
/// @param _operatorData Data generated by the operator to be passed to the recipient
/// @param _preventLocking `true` if you want this function to throw when tokens are sent to a contract not
///  implementing `ERC777tokensRecipient`.
///  ERC777 native Send functions MUST set this parameter to `true`, and backwards compatible ERC20 transfer
///  functions SHOULD set this parameter to `false`.