contract DfAirdrop is FundsMgr {
    using SafeMath for uint256;

    struct User {
        bool isReg;
        bool isWithdrawn;
    }

    uint256 public maxCountUsers;

    mapping (address => bool) admins;
    bool public isDealOpen;
    bool public isFinalized;

    IFinanceMgrOpenCdp public dfFinance;

    uint256 public depositedEth;
    uint256 public profitUsdPerUser;

    mapping (address => User) public users;
    uint256 public countRegUsers;
    uint256 public countWithdrawUsers;

    event RegNewUser (address indexed user);


    constructor() public {
        dfFinance = IFinanceMgrOpenCdp(0xD757EBb746167495716Ef3f11942c429203DBB75); //set dfFinance contract address
    }

    // USER FUNCTIONS

    function regUser(uint256 maxAllowedDatetime, uint8 v, bytes32 r, bytes32 s) public {
        require(countRegUsers < maxCountUsers);
        require(msg.sender == tx.origin);
        require(!users[msg.sender].isReg);
        require(now < maxAllowedDatetime);

        bytes32 hash = sha256(abi.encodePacked(this, msg.sender, maxAllowedDatetime));
        address src = ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)), v, r, s);
        require(admins[src] == true);

        users[msg.sender].isReg = true;
        countRegUsers = countRegUsers.add(1);

        emit RegNewUser(msg.sender);
    }

    function withdrawUserProfit() public {
        require(isFinalized);
        require(users[msg.sender].isReg && !users[msg.sender].isWithdrawn);

        // Set states
        countWithdrawUsers = countWithdrawUsers.add(1);
        users[msg.sender].isWithdrawn = true;

        // Transfer USDT
        dfFinance.dfManagerAddress().usdToken().universalTransfer(msg.sender, profitUsdPerUser);
    }


    // ONLY_OWNER FUNCTIONS
    function setAdmin(address newAdmin, bool active) onlyOwner public {
        admins[newAdmin] = active;
    }

    function financeMgrCall(address to, bytes memory _data, uint256 amountEther) onlyOwner public {
        bytes32 response;
        assembly {
            let succeeded := call(sub(gas, 5000), to, amountEther, add(_data, 0x20), mload(_data), 0, 32)
            response := mload(0)
            switch iszero(succeeded)
            case 1 {
                revert(0, 0)
            }
        }
    }

    function run(uint256 coef, uint8 profitPercent, bytes memory data, uint256 saiToBuyEther, uint8 ethType, uint256 maxUsers) onlyOwner public payable {
        require(!isDealOpen && !isFinalized);
        require(maxUsers > 0);

        // Set states
        depositedEth = msg.value;
        isDealOpen = true;
        maxCountUsers = maxUsers;

        dfFinance.deal.value(msg.value)(address(this), coef, profitPercent, data, saiToBuyEther, ethType);
    }

    function stop(bytes32 cup, uint256 etherNeeded, bytes memory _saiData, bytes memory _usdData, uint256 minAmountAllowed) onlyOwner public payable {
        require(isDealOpen);

        dfFinance.dfManagerAddress().userWipeAndClose(cup, etherNeeded, _saiData, _usdData, minAmountAllowed);
    }

    // depositedUSD == our USDT for withdrawal (our deposit in ETH)
    function finalize(uint256 depositedUSD) onlyOwner public {
        require(isDealOpen);

        // check profit
        uint256 profitPerUser = dfFinance.dfManagerAddress().usdToken().balanceOf(address(this)).sub(depositedUSD).div(maxCountUsers);
        require(profitPerUser > 0);

        // Set states
        profitUsdPerUser = profitPerUser;
        isDealOpen = false;
        isFinalized = true;
    }

    function() external payable {}
}
function finalize(uint256 depositedUSD) onlyOwner public {
    require(isDealOpen);

    // check profit
    uint256 profitPerUser = dfFinance.dfManagerAddress().usdToken().balanceOf(address(this)).sub(depositedUSD).div(maxCountUsers);
    require(profitPerUser > 0);

    // Set states
    profitUsdPerUser = profitPerUser;
    isDealOpen = false;
    isFinalized = true;
}

// depositedUSD == our USDT for withdrawal (our deposit in ETH)