contract User is Ownable {

    event UserRegistered(address internal_wallet_address, address external_wallet_address, address referrer, bytes32 nick_name, bytes32 geo_location);

    event UserNickNameUpdated(address external_wallet_address, bytes32 old_nick_name, bytes32 new_nick_name);

    event UserGeoLocationUpdated(address external_wallet_address, bytes32 old_geo_location, bytes32 new_geo_location);

    struct UserDetails {
        bytes32 nick_name;
        address payable referrer;
        bytes32 geo_location;
    }

    // address details
    mapping(address => UserDetails) public users;

    // mapping of internal wallet to external wallet
    mapping(address => address) public internal_to_external;

    // mapping of external wallet to internal wallet
    mapping(address => address) public external_to_internal;

    // all referees for an address
    mapping(address => address[]) public referees;

    constructor() public {
        UserDetails memory root = UserDetails({
            nick_name : "new",
            referrer : address(0),
            geo_location : "51.507351,-0.127758" // London
            });
        users[msg.sender] = root;
        internal_to_external[msg.sender] = msg.sender;
    }

    /**
     * @dev method to register users, can be called by admin only
     * @param _internal_wallet_addresses internal wallet addresses array
     * @param _external_wallet_addresses external wallet addresses array
     * @param _referrers referrers array
     * @param _nick_names nick names array
     * @param _geo_locations geo locations array
     */
    function registerUsers(
        address payable[] calldata _internal_wallet_addresses,
        address payable[] calldata _external_wallet_addresses,
        address payable[] calldata _referrers,
        bytes32[] calldata _nick_names,
        bytes32[] calldata _geo_locations) external onlyOwner() {
        for (uint256 i; i < _internal_wallet_addresses.length; i++) {
            UserDetails memory ud = UserDetails({
                nick_name : _nick_names[i],
                referrer : _referrers[i],
                geo_location : _geo_locations[i]
                });
            users[_external_wallet_addresses[i]] = ud;
            referees[_referrers[i]].push(_external_wallet_addresses[i]);
            internal_to_external[_internal_wallet_addresses[i]] = _external_wallet_addresses[i];
            external_to_internal[_external_wallet_addresses[i]] = _internal_wallet_addresses[i];
            emit UserRegistered(_internal_wallet_addresses[i], _external_wallet_addresses[i], _referrers[i], _nick_names[i], _geo_locations[i]);
        }
    }

    /**
     * @dev method to register user, can be called by admin only
     * @param _internal_wallet_address internal wallet address
     * @param _external_wallet_address external wallet address
     * @param _referrer referrer
     * @param _nick_name nick name
     * @param _geo_location geo location
     */
    function registerUser(
        address payable _internal_wallet_address,
        address payable _external_wallet_address,
        address payable _referrer,
        bytes32 _nick_name,
        bytes32 _geo_location) external onlyOwner() {
        UserDetails memory ud = UserDetails({
            nick_name : _nick_name,
            referrer : _referrer,
            geo_location : _geo_location
            });
        users[_external_wallet_address] = ud;
        referees[_referrer].push(_external_wallet_address);
        internal_to_external[_internal_wallet_address] = _external_wallet_address;
        external_to_internal[_external_wallet_address] = _internal_wallet_address;
        emit UserRegistered(_internal_wallet_address, _external_wallet_address, _referrer, _nick_name, _geo_location);
    }

    /**
     * @dev method to update users nick name, can be called external address only
     * @param _nick_name new nick name
     */
    function updateNickname(bytes32 _nick_name) external {
        require(external_to_internal[msg.sender] != address(0));
        UserDetails memory ud = users[msg.sender];
        require(ud.nick_name != bytes32(0), "user does not esist!");
        bytes32 old_nick_name = ud.nick_name;
        ud.nick_name = _nick_name;
        users[msg.sender] = ud;
        emit UserNickNameUpdated(msg.sender, old_nick_name, _nick_name);
    }

    /**
     * @dev method to update users geo location, can be called external address only
     * @param _geo_location new geo location
     */
    function updateGeoLocation(bytes32 _geo_location) external {
        require(external_to_internal[msg.sender] != address(0));
        UserDetails memory ud = users[msg.sender];
        require(ud.nick_name != bytes32(0), "user does not esist!");
        bytes32 old_geo_location = ud.geo_location;
        ud.geo_location = _geo_location;
        users[msg.sender] = ud;
        emit UserGeoLocationUpdated(msg.sender, old_geo_location, _geo_location);
    }

    /**
       * @dev Throws if called by any account other than the internal wallet.
       */
    modifier onlyInternalWallets() {
        require(internal_to_external[msg.sender] != address(0));
        _;
    }
}
function registerUsers(
    address payable[] calldata _internal_wallet_addresses,
    address payable[] calldata _external_wallet_addresses,
    address payable[] calldata _referrers,
    bytes32[] calldata _nick_names,
    bytes32[] calldata _geo_locations) external onlyOwner() {
    for (uint256 i; i < _internal_wallet_addresses.length; i++) {
        UserDetails memory ud = UserDetails({
            nick_name : _nick_names[i],
            referrer : _referrers[i],
            geo_location : _geo_locations[i]
            });
        users[_external_wallet_addresses[i]] = ud;
        referees[_referrers[i]].push(_external_wallet_addresses[i]);
        internal_to_external[_internal_wallet_addresses[i]] = _external_wallet_addresses[i];
        external_to_internal[_external_wallet_addresses[i]] = _internal_wallet_addresses[i];
        emit UserRegistered(_internal_wallet_addresses[i], _external_wallet_addresses[i], _referrers[i], _nick_names[i], _geo_locations[i]);
    }
}

/**
 * @dev method to register users, can be called by admin only
 * @param _internal_wallet_addresses internal wallet addresses array
 * @param _external_wallet_addresses external wallet addresses array
 * @param _referrers referrers array
 * @param _nick_names nick names array
 * @param _geo_locations geo locations array
 */