contract PapyrusToken is SafeOwnable, TokenERC20, ERC677, ERC677Bridgeable {

    // EVENTS

    event ControlByOwnerRevoked();
    event MintableChanged(bool mintable);
    event TransferableChanged(bool transferable);
    event ContractFallbackCallFailed(address from, address to, uint256 value);
    event BridgeContractChanged(address indexed previousBridgeContract, address indexed newBridgeContract);

    // PUBLIC FUNCTIONS

    constructor() public {
        _totalSupply = PPR_INITIAL_SUPPLY;
        _balances[msg.sender] = _totalSupply;
        emit Transfer(address(0), msg.sender, _totalSupply);
    }

    /// @dev Transfers tokens to a specified address with additional data if the recipient is a contact.
    /// @param to The address to transfer tokens to.
    /// @param value The amount of tokens to be transferred.
    /// @param data The extra data to be passed to the receiving contract.
    /// @return A boolean that indicates if the operation was successful.
    function transferAndCall(address to, uint256 value, bytes data) external canTransfer returns (bool) {
        require(to != address(this));
        require(super.transfer(to, value));
        emit Transfer(msg.sender, to, value, data);
        if (isContract(to)) {
            require(contractFallback(msg.sender, to, value, data));
        }
        return true;
    }

    /// @dev Transfers tokens to a specified address.
    /// @param to The address to transfer tokens to.
    /// @param value The amount of tokens to be transferred.
    /// @return A boolean that indicates if the operation was successful.
    function transfer(address to, uint256 value) public canTransfer returns (bool) {
        require(super.transfer(to, value));
        if (isContract(to) && !contractFallback(msg.sender, to, value, new bytes(0))) {
            if (to == _bridgeContract) {
                revert();
            }
            emit ContractFallbackCallFailed(msg.sender, to, value);
        }
        return true;
    }

    /// @dev Transfers tokens from one address to another.
    /// @param from The address to transfer tokens from.
    /// @param to The address to transfer tokens to.
    /// @param value The amount of tokens to be transferred.
    /// @return A boolean that indicates if the operation was successful.
    function transferFrom(address from, address to, uint256 value) public canTransfer returns (bool) {
        require(super.transferFrom(from, to, value));
        if (isContract(to) && !contractFallback(from, to, value, new bytes(0))) {
            if (to == _bridgeContract) {
                revert();
            }
            emit ContractFallbackCallFailed(from, to, value);
        }
        return true;
    }

    /// @dev Transfers tokens to a specified addresses (optimized version for initial sending tokens).
    /// @param recipients Addresses to transfer tokens to.
    /// @param values Amounts of tokens to be transferred.
    /// @return A boolean that indicates if the operation was successful.
    function airdrop(address[] recipients, uint256[] values) public canTransfer returns (bool) {
        require(recipients.length == values.length);
        uint256 senderBalance = _balances[msg.sender];
        for (uint256 i = 0; i < values.length; i++) {
            uint256 value = values[i];
            address to = recipients[i];
            require(senderBalance >= value);
            if (msg.sender != recipients[i]) {
                senderBalance = senderBalance - value;
                _balances[to] += value;
            }
            emit Transfer(msg.sender, to, value);
        }
        _balances[msg.sender] = senderBalance;
        return true;
    }

    /// @dev Mints specified amount of tokens and assigns it to a specified address.
    /// @param receiver The address that will receive the minted tokens.
    /// @param value The amount of tokens that will be minted.
    function mint(address receiver, uint256 value) public canMint returns (bool) {
        _mint(receiver, value);
        _totalMinted = _totalMinted.add(value);
        emit Mint(receiver, value);
        return true;
    }

    /// @dev Burns specified amount of tokens of the sender.
    /// @param value The amount of token to be burnt.
    function burn(uint256 value) public canBurn {
        _burn(msg.sender, value);
        _totalBurnt = _totalBurnt.add(value);
        emit Burn(msg.sender, value);
    }

    /// @dev Burns specified amount of tokens of the specified account.
    /// @param burner The address from which tokens will be burnt.
    /// @param value The amount of token to be burnt.
    function burnByOwner(address burner, uint256 value) public canBurnByOwner {
        _burn(burner, value);
        _totalBurnt = _totalBurnt.add(value);
        emit Burn(burner, value);
    }

    /// @dev Transfers funds stored on the token contract to specified recipient (required by token bridge).
    function claimTokens(address token, address to) public onlyOwnerOrBridgeContract {
        require(to != address(0));
        if (token == address(0)) {
            to.transfer(address(this).balance);
        } else {
            ERC20 erc20 = ERC20(token);
            uint256 balance = erc20.balanceOf(address(this));
            require(erc20.transfer(to, balance));
        }
    }

    /// @dev Revokes control by owner so it is not possible to burn tokens from any account by contract owner.
    function revokeControlByOwner() public onlyOwner {
        require(_controllable);
        _controllable = false;
        emit ControlByOwnerRevoked();
    }

    /// @dev Changes ability to mint tokens by permissioned accounts.
    function setMintable(bool mintable) public onlyOwner {
        require(_mintable != mintable);
        _mintable = mintable;
        emit MintableChanged(_mintable);
    }

    /// @dev Changes ability to transfer tokens by token holders.
    function setTransferable(bool transferable) public onlyOwner {
        require(_transferable != transferable);
        _transferable = transferable;
        emit TransferableChanged(_transferable);
    }

    /// @dev Changes address of token bridge contract.
    function setBridgeContract(address bridgeContract) public onlyOwner {
        require(_controllable);
        require(bridgeContract != address(0) && bridgeContract != _bridgeContract && isContract(bridgeContract));
        emit BridgeContractChanged(_bridgeContract, bridgeContract);
        _bridgeContract = bridgeContract;
    }

    /// @dev Turn off renounceOwnership() method from Ownable contract.
    function renounceOwnership() public pure {
        revert();
    }

    /// @dev Returns a boolean flag representing ability to burn tokens from any account by contract owner.
    /// @return A boolean that indicates if tokens can be burnt from any account by contract owner.
    function controllableByOwner() public view returns (bool) {
        return _controllable;
    }

    /// @dev Returns a boolean flag representing token mint ability.
    /// @return A boolean that indicates if tokens can be mintable by permissioned accounts.
    function mintable() public view returns (bool) {
        return _mintable;
    }

    /// @dev Returns a boolean flag representing token transfer ability.
    /// @return A boolean that indicates if tokens can be transferred by token holders.
    function transferable() public view returns (bool) {
        return _transferable;
    }

    /// @dev Returns an address of token bridge contract.
    /// @return An address of token bridge contract.
    function bridgeContract() public view returns (address) {
        return _bridgeContract;
    }

    /// @dev Returns total amount of minted tokens.
    /// @return A uint256 representing the amount of tokens were mint during token lifetime.
    function totalMinted() public view returns (uint256) {
        return _totalMinted;
    }

    /// @dev Returns total amount of burnt tokens.
    /// @return A uint256 representing the amount of tokens were burnt during token lifetime.
    function totalBurnt() public view returns (uint256) {
        return _totalBurnt;
    }

    /// @dev Returns version of token interfaces (required by token bridge).
    function getTokenInterfacesVersion() public pure returns (uint64, uint64, uint64) {
        uint64 major = 2;
        uint64 minor = 0;
        uint64 patch = 0;
        return (major, minor, patch);
    }

    // PRIVATE FUNCTIONS

    /// @dev Calls method onTokenTransfer() on specified contract address `receiver`.
    /// @return A boolean that indicates if the operation was successful.
    function contractFallback(address from, address receiver, uint256 value, bytes data) private returns (bool) {
        return receiver.call(abi.encodeWithSignature("onTokenTransfer(address,uint256,bytes)", from, value, data));
    }

    /// @dev Determines if specified address is contract address.
    /// @return A boolean that indicates if specified address is contract address.
    function isContract(address account) private view returns (bool) {
        uint256 codeSize;
        assembly { codeSize := extcodesize(account) }
        return codeSize > 0;
    }

    // MODIFIERS

    modifier onlyOwnerOrBridgeContract() {
        require(msg.sender == _owner || msg.sender == _bridgeContract);
        _;
    }

    modifier canMint() {
        require(_mintable);
        require(msg.sender == _owner || msg.sender == _bridgeContract);
        _;
    }

    modifier canBurn() {
        require(msg.sender == _owner || msg.sender == _bridgeContract);
        _;
    }

    modifier canBurnByOwner() {
        require(msg.sender == _owner && _controllable);
        _;
    }

    modifier canTransfer() {
        require(_transferable || msg.sender == _owner);
        _;
    }

    // FIELDS

    // Standard fields used to describe the token
    string public constant name = "Papyrus Token";
    string public constant symbol = "PPR";
    uint8 public constant decimals = 18;

    // At the start of the token existence it is fully controllable by owner
    bool private _controllable = true;

    // At the start of the token existence it is mintable
    bool private _mintable = true;

    // At the start of the token existence it is not transferable
    bool private _transferable = false;

    // Address of token bridge contract
    address private _bridgeContract;

    // Total amount of tokens minted during token lifetime
    uint256 private _totalMinted;
    // Total amount of tokens burnt during token lifetime
    uint256 private _totalBurnt;

    // Amount of initially supplied tokens is constant and equals to 1,000,000,000 PPR
    uint256 private constant PPR_INITIAL_SUPPLY = 10**27;
}
/// @title Papyrus Token contract (PPR).
function transferAndCall(address to, uint256 value, bytes data) external canTransfer returns (bool) {
    require(to != address(this));
    require(super.transfer(to, value));
    emit Transfer(msg.sender, to, value, data);
    if (isContract(to)) {
        require(contractFallback(msg.sender, to, value, data));
    }
    return true;
}

/// @dev Transfers tokens to a specified address with additional data if the recipient is a contact.
/// @param to The address to transfer tokens to.
/// @param value The amount of tokens to be transferred.
/// @param data The extra data to be passed to the receiving contract.
/// @return A boolean that indicates if the operation was successful.