contract ERC777BaseToken is ERC777Token, ERC820Client {
  using SafeMath for uint256;
  using Address for address;

  string internal mName;
  string internal mSymbol;
  uint256 internal mGranularity;
  uint256 internal mTotalSupply;


  mapping(address => uint) internal mBalances;
  mapping(address => mapping(address => bool)) internal mAuthorized;

  address[] internal mDefaultOperators;
  mapping(address => bool) internal mIsDefaultOperator;
  mapping(address => mapping(address => bool)) internal mRevokedDefaultOperator;

  /* -- Constructor -- */
  //
  /// @notice Constructor to create a SelfToken
  /// @param _name Name of the new token
  /// @param _symbol Symbol of the new token.
  /// @param _granularity Minimum transferable chunk.
  constructor(
    string _name,
    string _symbol,
    uint256 _granularity,
    address[] _defaultOperators
  )
    internal
  {
    mName = _name;
    mSymbol = _symbol;
    mTotalSupply = 0;
    require(_granularity >= 1);
    mGranularity = _granularity;

    mDefaultOperators = _defaultOperators;
    for (uint i = 0; i < mDefaultOperators.length; i++) {
      mIsDefaultOperator[mDefaultOperators[i]] = true;
    }

    setInterfaceImplementation("ERC777Token", this);
  }

  /* -- ERC777 Interface Implementation -- */

  /// @notice Send `_amount` of tokens to address `_to` passing `_userData` 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 _userData) external {
    doSend(msg.sender, msg.sender, _to, _amount, _userData, "", true);
  }

  /// @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 _userData 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 _userData, bytes _operatorData) external {
    require(isOperatorFor(msg.sender, _from));
    doSend(msg.sender, _from, _to, _amount, _userData, _operatorData, true);
  }

  function burn(uint256 _amount, bytes _holderData) external {
    doBurn(msg.sender, msg.sender, _amount, _holderData, "");
  }

  function operatorBurn(address _tokenHolder, uint256 _amount, bytes _holderData, bytes _operatorData) external {
    require(isOperatorFor(msg.sender, _tokenHolder));
    doBurn(msg.sender, _tokenHolder, _amount, _holderData, _operatorData);
  }

  /// @return the name of the token
  function name() external view returns (string) { return mName; }

  /// @return the symbol of the token
  function symbol() external view returns (string) { return mSymbol; }

  /// @return the granularity of the token
  function granularity() external view returns (uint256) { return mGranularity; }

  /// @return the total supply of the token
  function totalSupply() public view returns (uint256) { return mTotalSupply; }

  /// @notice Return the account balance of some account
  /// @param _tokenHolder Address for which the balance is returned
  /// @return the balance of `_tokenAddress`.
  function balanceOf(address _tokenHolder) public view returns (uint256) { return mBalances[_tokenHolder]; }

  /// @notice Return the list of default operators
  /// @return the list of all the default operators
  function defaultOperators() external view returns (address[]) { return mDefaultOperators; }

  /// @notice Authorize a third party `_operator` to manage (send) `msg.sender`'s tokens. An operator cannot be reauthorized
  /// @param _operator The operator that wants to be Authorized
  function authorizeOperator(address _operator) external {
    require(_operator != msg.sender);
    require(!mAuthorized[_operator][msg.sender]);

    if (mIsDefaultOperator[_operator]) {
      mRevokedDefaultOperator[_operator][msg.sender] = false;
    } else {
      mAuthorized[_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) external {
    require(_operator != msg.sender);
    require(mAuthorized[_operator][msg.sender]);

    if (mIsDefaultOperator[_operator]) {
      mRevokedDefaultOperator[_operator][msg.sender] = true;
    } else {
      mAuthorized[_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
      || mAuthorized[_operator][_tokenHolder]
      || (mIsDefaultOperator[_operator] && !mRevokedDefaultOperator[_operator][_tokenHolder])
    );
  }

  /* -- Helper Functions -- */
  //
  /// @notice Internal function that ensures `_amount` is multiple of the granularity
  /// @param _amount The quantity that want's to be checked
  function requireMultiple(uint256 _amount) internal view {
    require(_amount.div(mGranularity).mul(mGranularity) == _amount);
  }

  /// @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 _userData 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 doSend(
    address _operator,
    address _from,
    address _to,
    uint256 _amount,
    bytes _userData,
    bytes _operatorData,
    bool _preventLocking
  )
    internal
  {
    requireMultiple(_amount);

    callSender(_operator, _from, _to, _amount, _userData, _operatorData);

    require(_to != address(0));          // forbid sending to 0x0 (=burning)
    require(mBalances[_from] >= _amount); // ensure enough funds

    mBalances[_from] = mBalances[_from].sub(_amount);
    mBalances[_to] = mBalances[_to].add(_amount);

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

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

  /// @notice Helper function actually performing the burning of tokens.
  /// @param _operator The address performing the burn
  /// @param _tokenHolder The address holding the tokens being burn
  /// @param _amount The number of tokens to be burnt
  /// @param _holderData Data generated by the token holder
  /// @param _operatorData Data generated by the operator
  function doBurn(address _operator, address _tokenHolder, uint256 _amount, bytes _holderData, bytes _operatorData)
    internal
  {
    requireMultiple(_amount);
    require(balanceOf(_tokenHolder) >= _amount);

    mBalances[_tokenHolder] = mBalances[_tokenHolder].sub(_amount);
    mTotalSupply = mTotalSupply.sub(_amount);

    callSender(_operator, _tokenHolder, 0x0, _amount, _holderData, _operatorData);
    emit Burned(_operator, _tokenHolder, _amount, _holderData, _operatorData);
  }

  /// @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 _userData 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 _userData,
    bytes _operatorData,
    bool _preventLocking
  )
    internal
  {
    address recipientImplementation = interfaceAddr(_to, "ERC777TokensRecipient");
    if (recipientImplementation != 0) {
      ERC777TokensRecipient(recipientImplementation).tokensReceived(
        _operator, _from, _to, _amount, _userData, _operatorData);
    } else if (_preventLocking) {
      require(!_to.isContract());
    }
  }

  /// @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 _userData 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 _userData,
    bytes _operatorData
  )
    internal
  {
    address senderImplementation = interfaceAddr(_from, "ERC777TokensSender");
    if (senderImplementation == 0) {
      return;
    }
    ERC777TokensSender(senderImplementation).tokensToSend(_operator, _from, _to, _amount, _userData, _operatorData);
  }
}
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
function balanceOf(address _tokenHolder) public view returns (uint256) { return mBalances[_tokenHolder]; }

/// @notice Return the account balance of some account
/// @param _tokenHolder Address for which the balance is returned
/// @return the balance of `_tokenAddress`.