contract OrbsValidatorsRegistry is IOrbsValidatorsRegistry {

    // The validator registration data object.
    struct ValidatorData {
        string name;
        bytes4 ipAddress;
        string website;
        bytes20 orbsAddress;
        uint registeredOnBlock;
        uint lastUpdatedOnBlock;
    }

    // The version of the current validators data registration smart contract.
    uint public constant VERSION = 1;

    // A mapping from validator's Ethereum address to registration data.
    mapping(address => ValidatorData) internal validatorsData;

    // Lookups for IP Address & Orbs Address for uniqueness checks. Useful also be used for as a public lookup index.
    mapping(bytes4 => address) public lookupByIp;
    mapping(bytes20 => address) public lookupByOrbsAddr;

    /// @dev check that the caller is a validator.
    modifier onlyValidator() {
        require(isValidator(msg.sender), "You must be a registered validator");
        _;
    }

    /// @dev register a validator and provide registration data.
    /// the new validator entry will be owned and identified by msg.sender.
    /// if msg.sender is already registered as a validator in this registry the
    /// transaction will fail.
    /// @param name string The name of the validator
    /// @param ipAddress bytes4 The validator node ip address. If another validator previously registered this ipAddress the transaction will fail
    /// @param website string The website of the validator
    /// @param orbsAddress bytes20 The validator node orbs public address. If another validator previously registered this orbsAddress the transaction will fail
    function register(
        string name,
        bytes4 ipAddress,
        string website,
        bytes20 orbsAddress
    )
        external
    {
        address sender = msg.sender;
        require(bytes(name).length > 0, "Please provide a valid name");
        require(bytes(website).length > 0, "Please provide a valid website");
        require(!isValidator(sender), "Validator already exists");
        require(ipAddress != bytes4(0), "Please pass a valid ip address represented as an array of exactly 4 bytes");
        require(orbsAddress != bytes20(0), "Please provide a valid Orbs Address");
        require(lookupByIp[ipAddress] == address(0), "IP address already in use");
        require(lookupByOrbsAddr[orbsAddress] == address(0), "Orbs Address is already in use by another validator");

        lookupByIp[ipAddress] = sender;
        lookupByOrbsAddr[orbsAddress] = sender;

        validatorsData[sender] = ValidatorData({
            name: name,
            ipAddress: ipAddress,
            website: website,
            orbsAddress: orbsAddress,
            registeredOnBlock: block.number,
            lastUpdatedOnBlock: block.number
        });

        emit ValidatorRegistered(sender);
    }

    /// @dev update the validator registration data entry associated with msg.sender.
    /// msg.sender must be registered in this registry contract.
    /// @param name string The name of the validator
    /// @param ipAddress bytes4 The validator node ip address. If another validator previously registered this ipAddress the transaction will fail
    /// @param website string The website of the validator
    /// @param orbsAddress bytes20 The validator node orbs public address. If another validator previously registered this orbsAddress the transaction will fail
    function update(
        string name,
        bytes4 ipAddress,
        string website,
        bytes20 orbsAddress
    )
        external
        onlyValidator
    {
        address sender = msg.sender;
        require(bytes(name).length > 0, "Please provide a valid name");
        require(bytes(website).length > 0, "Please provide a valid website");
        require(ipAddress != bytes4(0), "Please pass a valid ip address represented as an array of exactly 4 bytes");
        require(orbsAddress != bytes20(0), "Please provide a valid Orbs Address");
        require(isIpFreeToUse(ipAddress), "IP Address is already in use by another validator");
        require(isOrbsAddressFreeToUse(orbsAddress), "Orbs Address is already in use by another validator");

        ValidatorData storage data = validatorsData[sender];

        // Remove previous key from lookup.
        delete lookupByIp[data.ipAddress];
        delete lookupByOrbsAddr[data.orbsAddress];

        // Set new keys in lookup.
        lookupByIp[ipAddress] = sender;
        lookupByOrbsAddr[orbsAddress] = sender;

        data.name = name;
        data.ipAddress = ipAddress;
        data.website = website;
        data.orbsAddress = orbsAddress;
        data.lastUpdatedOnBlock = block.number;

        emit ValidatorUpdated(sender);
    }

    /// @dev deletes a validator registration entry associated with msg.sender.
    function leave() external onlyValidator {
        address sender = msg.sender;

        ValidatorData storage data = validatorsData[sender];

        delete lookupByIp[data.ipAddress];
        delete lookupByOrbsAddr[data.orbsAddress];

        delete validatorsData[sender];

        emit ValidatorLeft(sender);
    }

    /// @dev returns the blocks in which a validator was registered and last updated.
    /// if validator does not designate a registered validator this method returns zero values.
    /// @param validator address of a validator
    function getRegistrationBlockNumber(address validator)
        external
        view
        returns (uint registeredOn, uint lastUpdatedOn)
    {
        require(isValidator(validator), "Unlisted Validator");

        ValidatorData storage entry = validatorsData[validator];
        registeredOn = entry.registeredOnBlock;
        lastUpdatedOn = entry.lastUpdatedOnBlock;
    }

    /// @dev returns the orbs node public address of a specific validator.
    /// @param validator address address of the validator
    /// @return an Orbs node address
    function getOrbsAddress(address validator)
        external
        view
        returns (bytes20)
    {
        return validatorsData[validator].orbsAddress;
    }

    /// @dev returns validator registration data.
    /// @param validator address address of the validator.
    function getValidatorData(address validator)
        public
        view
        returns (
            string memory name,
            bytes4 ipAddress,
            string memory website,
            bytes20 orbsAddress
        )
    {
        ValidatorData storage entry = validatorsData[validator];
        name = entry.name;
        ipAddress = entry.ipAddress;
        website = entry.website;
        orbsAddress = entry.orbsAddress;
    }

    /// @dev Checks if validator is currently registered as a validator.
    /// @param validator address address of the validator
    /// @return true iff validator belongs to a registered validator
    function isValidator(address validator) public view returns (bool) {
        return validatorsData[validator].registeredOnBlock > 0;
    }

    /// @dev INTERNAL. Checks if ipAddress is currently available to msg.sender.
    /// @param ipAddress bytes4 ip address to check for uniqueness
    /// @return true iff ipAddress is currently not registered for any validator other than msg.sender.
    function isIpFreeToUse(bytes4 ipAddress) internal view returns (bool) {
        return
            lookupByIp[ipAddress] == address(0) ||
            lookupByIp[ipAddress] == msg.sender;
    }

    /// @dev INTERNAL. Checks if orbsAddress is currently available to msg.sender.
    /// @param orbsAddress bytes20 ip address to check for uniqueness
    /// @return true iff orbsAddress is currently not registered for a validator other than msg.sender.
    function isOrbsAddressFreeToUse(bytes20 orbsAddress)
        internal
        view
        returns (bool)
    {
        return
            lookupByOrbsAddr[orbsAddress] == address(0) ||
            lookupByOrbsAddr[orbsAddress] == msg.sender;
    }
}
/// @title Orbs Validators Registry smart contract.
function getRegistrationBlockNumber(address validator)
    external
    view
    returns (uint registeredOn, uint lastUpdatedOn)
{
    require(isValidator(validator), "Unlisted Validator");

    ValidatorData storage entry = validatorsData[validator];
    registeredOn = entry.registeredOnBlock;
    lastUpdatedOn = entry.lastUpdatedOnBlock;
}

/// @dev returns the blocks in which a validator was registered and last updated.
/// if validator does not designate a registered validator this method returns zero values.
/// @param validator address of a validator