pragma solidity 0.4.25;


/**
 * @title SafeMath
 * @dev Math operations with safety checks that revert on error
 */
library SafeMath {

  /**
  * @dev Multiplies two numbers, reverts on overflow.
  */
  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
    // benefit is lost if 'b' is also tested.
    // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
    if (a == 0) {
      return 0;
    }

    uint256 c = a * b;
    require(c / a == b);

    return c;
  }

  /**
  * @dev Integer division of two numbers truncating the quotient, reverts on division by zero.
  */
  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    require(b > 0); // Solidity only automatically asserts when dividing by 0
    uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold

    return c;
  }

  /**
  * @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend).
  */
  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    require(b <= a);
    uint256 c = a - b;

    return c;
  }

  /**
  * @dev Adds two numbers, reverts on overflow.
  */
  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    require(c >= a);

    return c;
  }

  /**
  * @dev Divides two numbers and returns the remainder (unsigned integer modulo),
  * reverts when dividing by zero.
  */
  function mod(uint256 a, uint256 b) internal pure returns (uint256) {
    require(b != 0);
    return a % b;
  }
}


/**
 * Utility library of inline functions on addresses
 */
library Address {

  /**
   * Returns whether the target address is a contract
   * @dev This function will return false if invoked during the constructor of a contract,
   * as the code is not actually created until after the constructor finishes.
   * @param account address of the account to check
   * @return whether the target address is a contract
   */
  function isContract(address account) internal view returns (bool) {
    uint256 size;
    // XXX Currently there is no better way to check if there is a contract in an address
    // than to check the size of the code at that address.
    // See https://ethereum.stackexchange.com/a/14016/36603
    // for more details about how this works.
    // TODO Check this again before the Serenity release, because all addresses will be
    // contracts then.
    // solium-disable-next-line security/no-inline-assembly
    assembly { size := extcodesize(account) }
    return size > 0;
  }

}


interface IOrbsGuardians {

    event GuardianRegistered(address indexed guardian);
    event GuardianLeft(address indexed guardian);
    event GuardianUpdated(address indexed guardian);

    /// @dev register a new guardian. You will need to transfer registrationDepositWei amount of ether.
    /// @param name string The name of the guardian
    /// @param website string The website of the guardian
    function register(string name, string website) external payable;

    /// @dev update guardian details. only msg.sender can update it's own guardian details.
    /// @param name string The name of the guardian
    /// @param website string The website of the guardianfunction update(string name, string website) external;
    function update(string name, string website) external;

    /// @dev Delete the guardian and take back the locked ether. only msg.sender can leave.
    function leave() external;

    /// @dev Returns if the address belongs to a guardian
    /// @param guardian address the guardian address
    function isGuardian(address guardian) external view returns (bool);

    /// @dev Returns name and website for  a specific guardian.
    /// @param guardian address the guardian address
    function getGuardianData(address guardian)
        external
        view
        returns (string name, string website);

    /// @dev Returns in which block the guardian registered, and in which block it was last updated.
    /// @param guardian address the guardian address
    function getRegistrationBlockNumber(address guardian)
        external
        view
        returns (uint registeredOn, uint lastUpdatedOn);

    /// @dev Returns an array of guardians.
    /// @param offset uint offset from which to start getting guardians from the array
    /// @param limit uint limit of guardians to be returned.
    function getGuardians(uint offset, uint limit)
        external
        view
        returns (address[]);

    /// @dev Similar to getGuardians, but returns addresses represented as byte20.
    /// @param offset uint offset from which to start getting guardians from the array
    /// @param limit uint limit of guardians to be returned.
    function getGuardiansBytes20(uint offset, uint limit)
        external
        view
        returns (bytes20[]);
}


contract OrbsGuardians is IOrbsGuardians {
    using SafeMath for uint256;

    struct GuardianData {
        string name;
        string website;
        uint index;
        uint registeredOnBlock;
        uint lastUpdatedOnBlock;
        uint registeredOn;
    }

    // The version of the current Guardian smart contract.
    uint public constant VERSION = 1;

    // Amount of Ether in Wei need to be locked when registering - this will be set to 1.
    uint public registrationDepositWei;
    // The amount of time needed to wait until a guardian can leave and get registrationDepositWei_
    uint public registrationMinTime;

    // Iterable array to get a list of all guardians
    address[] internal guardians;

    // Mapping between address and the guardian data.
    mapping(address => GuardianData) internal guardiansData;

    /// @dev Check that the caller is a guardian.
    modifier onlyGuardian() {
        require(isGuardian(msg.sender), "You must be a registered guardian");
        _;
    }

    /// @dev Check that the caller is not a contract.
    modifier onlyEOA() {
        require(!Address.isContract(msg.sender),"Only EOA may register as Guardian");
        _;
    }

    /// @dev Constructor that initializes the amount of ether needed to lock when registering. This will be set to 1.
    /// @param registrationDepositWei_ uint the amount of ether needed to lock when registering.
    /// @param registrationMinTime_ uint the amount of time needed to wait until a guardian can leave and get registrationDepositWei_
    constructor(uint registrationDepositWei_, uint registrationMinTime_) public {
        require(registrationDepositWei_ > 0, "registrationDepositWei_ must be positive");

        registrationMinTime = registrationMinTime_;
        registrationDepositWei = registrationDepositWei_;
    }

    /// @dev register a new guardian. You will need to transfer registrationDepositWei amount of ether.
    /// @param name string The name of the guardian
    /// @param website string The website of the guardian
    function register(string name, string website)
        external
        payable
        onlyEOA
    {
        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(!isGuardian(sender), "Cannot be a guardian");
        require(msg.value == registrationDepositWei, "Please provide the exact registration deposit");

        uint index = guardians.length;
        guardians.push(sender);
        guardiansData[sender] = GuardianData({
            name: name,
            website: website,
            index: index ,
            registeredOnBlock: block.number,
            lastUpdatedOnBlock: block.number,
            registeredOn: now
        });

        emit GuardianRegistered(sender);
    }

    /// @dev update guardian details. only msg.sender can update it's own guardian details.
    /// @param name string The name of the guardian
    /// @param website string The website of the guardian
    function update(string name, string website)
        external
        onlyGuardian
        onlyEOA
    {
        address sender = msg.sender;
        require(bytes(name).length > 0, "Please provide a valid name");
        require(bytes(website).length > 0, "Please provide a valid website");


        guardiansData[sender].name = name;
        guardiansData[sender].website = website;
        guardiansData[sender].lastUpdatedOnBlock = block.number;

        emit GuardianUpdated(sender);
    }

    /// @dev Delete the guardian and take back the locked ether. only msg.sender can leave.
    function leave() external onlyGuardian onlyEOA {
        address sender = msg.sender;
        require(now >= guardiansData[sender].registeredOn.add(registrationMinTime), "Minimal guardian time didnt pass");

        uint i = guardiansData[sender].index;

        assert(guardians[i] == sender); // Will consume all available gas.

        // Replace with last element and remove from end
        guardians[i] = guardians[guardians.length - 1]; // Switch with last
        guardiansData[guardians[i]].index = i; // Update it's lookup index
        guardians.length--; // Remove the last one

        // Clear data
        delete guardiansData[sender];

        // Refund deposit
        sender.transfer(registrationDepositWei);

        emit GuardianLeft(sender);
    }

    /// @dev Similar to getGuardians, but returns addresses represented as byte20.
    /// @param offset uint offset from which to start getting guardians from the array
    /// @param limit uint limit of guardians to be returned.
    function getGuardiansBytes20(uint offset, uint limit)
        external
        view
        returns (bytes20[])
    {
        address[] memory guardianAddresses = getGuardians(offset, limit);
        uint guardianAddressesLength = guardianAddresses.length;

        bytes20[] memory result = new bytes20[](guardianAddressesLength);

        for (uint i = 0; i < guardianAddressesLength; i++) {
            result[i] = bytes20(guardianAddresses[i]);
        }

        return result;
    }

    /// @dev Returns in which block the guardian registered, and in which block it was last updated.
    /// @param guardian address the guardian address
    function getRegistrationBlockNumber(address guardian)
        external
        view
        returns (uint registeredOn, uint lastUpdatedOn)
    {
        require(isGuardian(guardian), "Please provide a listed Guardian");

        GuardianData storage entry = guardiansData[guardian];
        registeredOn = entry.registeredOnBlock;
        lastUpdatedOn = entry.lastUpdatedOnBlock;
    }

    /// @dev Returns an array of guardians.
    /// @param offset uint offset from which to start getting guardians from the array
    /// @param limit uint limit of guardians to be returned.
    function getGuardians(uint offset, uint limit)
        public
        view
        returns (address[] memory)
    {
        if (offset >= guardians.length) { // offset out of bounds
            return new address[](0);
        }

        if (offset.add(limit) > guardians.length) { // clip limit to array size
            limit = guardians.length.sub(offset);
        }

        address[] memory result = new address[](limit);

        uint resultLength = result.length;
        for (uint i = 0; i < resultLength; i++) {
            result[i] = guardians[offset.add(i)];
        }

        return result;
    }

    /// @dev Returns name and website for  a specific guardian.
    /// @param guardian address the guardian address
    function getGuardianData(address guardian)
        public
        view
        returns (string memory name, string memory website)
    {
        require(isGuardian(guardian), "Please provide a listed Guardian");
        name = guardiansData[guardian].name;
        website = guardiansData[guardian].website;
    }

    /// @dev Returns if the address belongs to a guardian
    /// @param guardian address the guardian address
    function isGuardian(address guardian) public view returns (bool) {
        return guardiansData[guardian].registeredOnBlock > 0;
    }
}