pragma solidity 0.5.7;
pragma experimental ABIEncoderV2;


/**
 * @title ERC20 interface
 * @dev see https://github.com/ethereum/EIPs/issues/20
 */
interface IERC20 {
    function transfer(address to, uint256 value) external returns (bool);

    function approve(address spender, uint256 value) external returns (bool);

    function transferFrom(address from, address to, uint256 value) external returns (bool);

    function totalSupply() external view returns (uint256);

    function balanceOf(address who) external view returns (uint256);

    function allowance(address owner, address spender) external view returns (uint256);

    event Transfer(address indexed from, address indexed to, uint256 value);

    event Approval(address indexed owner, address indexed spender, uint256 value);
}


contract IRegistry {
    function add(address who) public;
}


contract IUniswapExchange {
    function ethToTokenSwapOutput(uint256 tokens_bought, uint256 timestamp) public payable returns (uint256);
}


contract IGovernance {
    function proposeWithFeeRecipient(address feeRecipient, address target, bytes memory data) public returns (uint);
    function proposalFee() public view returns (uint);
}


/**
 * @title SafeMath
 * @dev Unsigned math operations with safety checks that revert on error
 */
library SafeMath {
    /**
    * @dev Multiplies two unsigned integers, 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, "SafeMath::mul: Integer overflow");

        return c;
    }

    /**
    * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero.
    */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        // Solidity only automatically asserts when dividing by 0
        require(b > 0, "SafeMath::div: Invalid divisor zero");
        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 unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend).
    */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b <= a, "SafeMath::sub: Integer underflow");
        uint256 c = a - b;

        return c;
    }

    /**
    * @dev Adds two unsigned integers, reverts on overflow.
    */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath::add: Integer overflow");

        return c;
    }

    /**
    * @dev Divides two unsigned integers 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, "SafeMath::mod: Invalid divisor zero");
        return a % b;
    }
}


/**
 * @title HumanityApplicant
 * @dev Convenient interface for applying to the Humanity registry.
 */
contract HumanityApplicant {
    using SafeMath for uint;

    IGovernance public governance;
    IRegistry public registry;
    IERC20 public humanity;

    constructor(IGovernance _governance, IRegistry _registry, IERC20 _humanity) public {
        governance = _governance;
        registry = _registry;
        humanity = _humanity;
        humanity.approve(address(governance), uint(-1));
    }

    function applyFor(address who) public returns (uint) {
        uint fee = governance.proposalFee();
        uint balance = humanity.balanceOf(address(this));
        if (fee > balance) {
            require(humanity.transferFrom(msg.sender, address(this), fee.sub(balance)), "HumanityApplicant::applyFor: Transfer failed");
        }
        bytes memory data = abi.encodeWithSelector(registry.add.selector, who);
        return governance.proposeWithFeeRecipient(msg.sender, address(registry), data);
    }

}


/**
 * @title PayableHumanityApplicant
 * @dev Convenient interface for applying to the Humanity registry using Ether.
 */
contract PayableHumanityApplicant is HumanityApplicant {

    IUniswapExchange public exchange;

    constructor(IGovernance _governance, IRegistry _registry, IERC20 _humanity, IUniswapExchange _exchange) public
        HumanityApplicant(_governance, _registry, _humanity)
    {
        exchange = _exchange;
    }

    function () external payable {}

    function applyWithEtherFor(address who) public payable returns (uint) {
        // Exchange Ether for Humanity tokens
        uint fee = governance.proposalFee();
        exchange.ethToTokenSwapOutput.value(msg.value)(fee, block.timestamp);

        // Apply to the registry
        uint proposalId = applyFor(who);

        // Refund any remaining balance
        msg.sender.send(address(this).balance);

        return proposalId;
    }

}


/**
 * @title TwitterHumanityApplicant
 * @dev Convenient interface for applying to the Humanity registry using Twitter as proof of identity.
 */
contract TwitterHumanityApplicant is PayableHumanityApplicant {

    event Apply(uint indexed proposalId, address indexed applicant, string username);

    constructor(
        IGovernance _governance,
        IRegistry _registry,
        IERC20 _humanity,
        IUniswapExchange _exchange
    ) public
        PayableHumanityApplicant(_governance, _registry, _humanity, _exchange) {}

    function applyWithTwitter(string memory username) public returns (uint) {
        return applyWithTwitterFor(msg.sender, username);
    }

    function applyWithTwitterFor(address who, string memory username) public returns (uint) {
        uint proposalId = applyFor(who);
        emit Apply(proposalId, who, username);
        return proposalId;
    }

    function applyWithTwitterUsingEther(string memory username) public payable returns (uint) {
        return applyWithTwitterUsingEtherFor(msg.sender, username);
    }

    function applyWithTwitterUsingEtherFor(address who, string memory username) public payable returns (uint) {
        uint proposalId = applyWithEtherFor(who);
        emit Apply(proposalId, who, username);
        return proposalId;
    }

}