contract StartersProxy is Whitelist{
    using SafeMath for uint256;

    uint256 public TX_PER_SIGNER_LIMIT = 5;          //limit of metatx per signer
    uint256 public META_BET = 1 finney;              //wei, equal to 0.001 ETH
    uint256 public DEBT_INCREASING_FACTOR = 3;       //increasing factor (times) applied on the bet

    struct Record {
        uint256 nonce;
        uint256 debt;
    }
    mapping(address => Record) signersBacklog;
    event Received (address indexed sender, uint value);
    event Forwarded (address signer, address destination, uint value, bytes data);

    function() public payable {
        emit Received(msg.sender, msg.value);
    }

    constructor(address[] _senders) public {
        addAddressToWhitelist(msg.sender);
        addAddressesToWhitelist(_senders);
    }

    function forwardPlay(address signer, address destination, bytes data, bytes32 hash, bytes signature) onlyWhitelisted public {
        require(signersBacklog[signer].nonce < TX_PER_SIGNER_LIMIT, "Signer has reached the tx limit");

        signersBacklog[signer].nonce++;
        //we increase the personal debt here
        //it grows much (much) faster than the actual bet to compensate sender's and proxy's expenses
        uint256 debtIncrease = META_BET.mul(DEBT_INCREASING_FACTOR);
        signersBacklog[signer].debt = signersBacklog[signer].debt.add(debtIncrease);

        forward(signer, destination, META_BET, data, hash, signature);
    }

    function forwardWin(address signer, address destination, bytes data, bytes32 hash, bytes signature) onlyWhitelisted public {
        require(signersBacklog[signer].nonce > 0, 'Hm, no meta plays for this signer');

        forward(signer, destination, 0, data, hash, signature);
    }

    function forward(address signer, address destination,  uint256 value, bytes data, bytes32 hash, bytes signature) internal {
        require(recoverSigner(hash, signature) == signer);

        //execute the transaction with all the given parameters
        require(executeCall(destination, value, data));
        emit Forwarded(signer, destination, value, data);
    }

    //borrowed from OpenZeppelin's ESDA stuff:
    //https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/cryptography/ECDSA.sol
    function recoverSigner(bytes32 _hash, bytes _signature) onlyWhitelisted public view returns (address){
        bytes32 r;
        bytes32 s;
        uint8 v;
        // Check the signature length
        require (_signature.length == 65);
        // Divide the signature in r, s and v variables
        // ecrecover takes the signature parameters, and the only way to get them
        // currently is to use assembly.
        assembly {
            r := mload(add(_signature, 32))
            s := mload(add(_signature, 64))
            v := byte(0, mload(add(_signature, 96)))
        }
        // Version of signature should be 27 or 28, but 0 and 1 are also possible versions
        if (v < 27) {
            v += 27;
        }
        // If the version is correct return the signer address
        require(v == 27 || v == 28);
        return ecrecover(keccak256(
                abi.encodePacked("\x19Ethereum Signed Message:\n32", _hash)
            ), v, r, s);
    }

    // this originally was copied from GnosisSafe
    // https://github.com/gnosis/gnosis-safe-contracts/blob/master/contracts/GnosisSafe.sol
    function executeCall(address to, uint256 value, bytes data) internal returns (bool success) {
        assembly {
            success := call(gas, to, value, add(data, 0x20), mload(data), 0, 0)
        }
    }

    function payDebt(address signer) public payable{
        require(signersBacklog[signer].nonce > 0, "Provided address has no debt");
        require(signersBacklog[signer].debt >= msg.value, "Address's debt is less than payed amount");

        signersBacklog[signer].debt = signersBacklog[signer].debt.sub(msg.value);
    }

    function debt(address signer) public view returns (uint256) {
        return signersBacklog[signer].debt;
    }

    function gamesLeft(address signer) public view returns (uint256) {
        return TX_PER_SIGNER_LIMIT.sub(signersBacklog[signer].nonce);
    }

    function withdraw(uint256 amountWei) onlyWhitelisted public {
        msg.sender.transfer(amountWei);
    }

    function setMetaBet(uint256 _newMetaBet) onlyWhitelisted public {
        META_BET = _newMetaBet;
    }

    function setTxLimit(uint256 _newTxLimit) onlyWhitelisted public {
        TX_PER_SIGNER_LIMIT = _newTxLimit;
    }

    function setDebtIncreasingFactor(uint256 _newFactor) onlyWhitelisted public {
        DEBT_INCREASING_FACTOR = _newFactor;
    }


}
function executeCall(address to, uint256 value, bytes data) internal returns (bool success) {
    assembly {
        success := call(gas, to, value, add(data, 0x20), mload(data), 0, 0)
    }
}

// this originally was copied from GnosisSafe
// https://github.com/gnosis/gnosis-safe-contracts/blob/master/contracts/GnosisSafe.sol