pragma solidity 0.4.25;


/**
 * @title Ownable
 * @dev The Ownable contract has an owner address, and provides basic authorization control
 * functions, this simplifies the implementation of "user permissions".
 */
contract Ownable {
  address private _owner;

  event OwnershipTransferred(
    address indexed previousOwner,
    address indexed newOwner
  );

  /**
   * @dev The Ownable constructor sets the original `owner` of the contract to the sender
   * account.
   */
  constructor() internal {
    _owner = msg.sender;
    emit OwnershipTransferred(address(0), _owner);
  }

  /**
   * @return the address of the owner.
   */
  function owner() public view returns(address) {
    return _owner;
  }

  /**
   * @dev Throws if called by any account other than the owner.
   */
  modifier onlyOwner() {
    require(isOwner());
    _;
  }

  /**
   * @return true if `msg.sender` is the owner of the contract.
   */
  function isOwner() public view returns(bool) {
    return msg.sender == _owner;
  }

  /**
   * @dev Allows the current owner to relinquish control of the contract.
   * @notice Renouncing to ownership will leave the contract without an owner.
   * It will not be possible to call the functions with the `onlyOwner`
   * modifier anymore.
   */
  function renounceOwnership() public onlyOwner {
    emit OwnershipTransferred(_owner, address(0));
    _owner = address(0);
  }

  /**
   * @dev Allows the current owner to transfer control of the contract to a newOwner.
   * @param newOwner The address to transfer ownership to.
   */
  function transferOwnership(address newOwner) public onlyOwner {
    _transferOwnership(newOwner);
  }

  /**
   * @dev Transfers control of the contract to a newOwner.
   * @param newOwner The address to transfer ownership to.
   */
  function _transferOwnership(address newOwner) internal {
    require(newOwner != address(0));
    emit OwnershipTransferred(_owner, newOwner);
    _owner = newOwner;
  }
}


interface IOrbsNetworkTopology {

    /// @dev returns an array of pairs with node addresses and ip addresses.
    function getNetworkTopology()
        external
        view
        returns (bytes20[] nodeAddresses, bytes4[] ipAddresses);
}


interface IOrbsValidators {

    event ValidatorApproved(address indexed validator);
    event ValidatorRemoved(address indexed validator);

    /// @dev Adds a validator to participate in network
    /// @param validator address The address of the validators.
    function approve(address validator) external;

    /// @dev Remove a validator from the List based on Guardians votes.
    /// @param validator address The address of the validators.
    function remove(address validator) external;

    /// @dev returns if an address belongs to the approved list & exists in the validators metadata registration database.
    /// @param validator address The address of the validators.
    function isValidator(address validator) external view returns (bool);

    /// @dev returns if an address belongs to the approved list
    /// @param validator address The address of the validators.
    function isApproved(address validator) external view returns (bool);

    /// @dev returns a list of all validators that have been approved and exist in the validator registration database.
    function getValidators() external view returns (address[]);

    /// @dev returns a list of all validators that have been approved and exist in the validator registration
    ///      database. same as getValidators but returns addresses represented as byte20.
    function getValidatorsBytes20() external view returns (bytes20[]);

    /// @dev returns the block number in which the validator was approved.
    /// @param validator address The address of the validators.
    function getApprovalBlockNumber(address validator)
        external
        view
        returns (uint);
}


interface IOrbsValidatorsRegistry {

    event ValidatorLeft(address indexed validator);
    event ValidatorRegistered(address indexed validator);
    event ValidatorUpdated(address indexed 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;

    /// @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;

    /// @dev deletes a validator registration entry associated with msg.sender.
    function leave() external;

    /// @dev returns validator registration data.
    /// @param validator address address of the validator.
    function getValidatorData(address validator)
        external
        view
        returns (
            string name,
            bytes4 ipAddress,
            string website,
            bytes20 orbsAddress
        );

    /// @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);

    /// @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) external view returns (bool);

    /// @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 orbsAddress);
}


contract OrbsValidators is Ownable, IOrbsValidators, IOrbsNetworkTopology {

    // The version of the current Validators smart contract.
    uint public constant VERSION = 1;

    // Maximum number of validators.
    uint internal constant MAX_VALIDATOR_LIMIT = 100;
    uint public validatorsLimit;

    // The validators metadata registration database smart contract
    IOrbsValidatorsRegistry public orbsValidatorsRegistry;

    // Array of approved validators addresses
    address[] internal approvedValidators;

    // Mapping of address and in which block it was approved.
    mapping(address => uint) internal approvalBlockNumber;

    /// @dev Constructor that initializes the validators smart contract with the validators metadata registration
    ///     database smart contract.
    /// @param registry_ IOrbsValidatorsRegistry The address of the validators metadata registration database.
    /// @param validatorsLimit_ uint Maximum number of validators list maximum size.
    constructor(IOrbsValidatorsRegistry registry_, uint validatorsLimit_) public {
        require(registry_ != IOrbsValidatorsRegistry(0), "Registry contract address 0");
        require(validatorsLimit_ > 0, "Limit must be positive");
        require(validatorsLimit_ <= MAX_VALIDATOR_LIMIT, "Limit is too high");

        validatorsLimit = validatorsLimit_;
        orbsValidatorsRegistry = registry_;
    }

    /// @dev Adds a validator to participate in network
    /// @param validator address The address of the validators.
    function approve(address validator) external onlyOwner {
        require(validator != address(0), "Address must not be 0!");
        require(approvedValidators.length < validatorsLimit, "Can't add more members!");
        require(!isApproved(validator), "Address must not be already approved");

        approvedValidators.push(validator);
        approvalBlockNumber[validator] = block.number;
        emit ValidatorApproved(validator);
    }

    /// @dev Remove a validator from the List based on Guardians votes.
    /// @param validator address The address of the validators.
    function remove(address validator) external onlyOwner {
        require(isApproved(validator), "Not an approved validator");

        uint approvedLength = approvedValidators.length;
        for (uint i = 0; i < approvedLength; ++i) {
            if (approvedValidators[i] == validator) {

                // Replace with last element and remove from end
                approvedValidators[i] = approvedValidators[approvedLength - 1];
                approvedValidators.length--;

                // Clear approval block height
                delete approvalBlockNumber[validator];

                emit ValidatorRemoved(validator);
                return;
            }
        }
    }

    /// @dev returns if an address belongs to the approved list & exists in the validators metadata registration database.
    /// @param validator address The address of the validators.
    function isValidator(address validator) public view returns (bool) {
        return isApproved(validator) && orbsValidatorsRegistry.isValidator(validator);
    }

    /// @dev returns if an address belongs to the approved list
    /// @param validator address The address of the validators.
    function isApproved(address validator) public view returns (bool) {
        return approvalBlockNumber[validator] > 0;
    }

    /// @dev returns a list of all validators that have been approved and exist in the validator registration database.
    function getValidators() public view returns (address[] memory) {
        uint approvedLength = approvedValidators.length;
        address[] memory validators = new address[](approvedLength);

        uint pushAt = 0;
        for (uint i = 0; i < approvedLength; i++) {
            if (orbsValidatorsRegistry.isValidator(approvedValidators[i])) {
                validators[pushAt] = approvedValidators[i];
                pushAt++;
            }
        }

        return sliceArray(validators, pushAt);
    }

    /// @dev returns a list of all validators that have been approved and exist in the validator registration
    ///      database. same as getValidators but returns addresses represented as byte20.
    function getValidatorsBytes20() external view returns (bytes20[]) {
        address[] memory validatorAddresses = getValidators();
        uint validatorAddressesLength = validatorAddresses.length;

        bytes20[] memory result = new bytes20[](validatorAddressesLength);

        for (uint i = 0; i < validatorAddressesLength; i++) {
            result[i] = bytes20(validatorAddresses[i]);
        }

        return result;
    }

    /// @dev returns the block number in which the validator was approved.
    /// @param validator address The address of the validators.
    function getApprovalBlockNumber(address validator)
        public
        view
        returns (uint)
    {
        return approvalBlockNumber[validator];
    }

    /// @dev returns an array of pairs with node addresses and ip addresses.
    function getNetworkTopology()
        external
        view
        returns (bytes20[] memory nodeAddresses, bytes4[] memory ipAddresses)
    {
        address[] memory validators = getValidators(); // filter unregistered
        uint validatorsLength = validators.length;
        nodeAddresses = new bytes20[](validatorsLength);
        ipAddresses = new bytes4[](validatorsLength);

        for (uint i = 0; i < validatorsLength; i++) {
            bytes4 ip;
            bytes20 orbsAddr;
            ( , ip , , orbsAddr) = orbsValidatorsRegistry.getValidatorData(validators[i]);
            nodeAddresses[i] = orbsAddr;
            ipAddresses[i] = ip;
        }
    }

    /// @dev internal method that returns a slice of an array.
    function sliceArray(address[] memory arr, uint len)
        internal
        pure
        returns (address[] memory)
    {
        require(len <= arr.length, "sub array must be longer then array");

        address[] memory result = new address[](len);
        for(uint i = 0; i < len; i++) {
            result[i] = arr[i];
        }
        return result;
    }
}