pragma solidity 0.5.2;

contract Ownable {
    address public owner;

    constructor() public {
        owner = msg.sender;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "");
        _;
    }

    function transferOwnership(address newOwner) public onlyOwner {
        require(newOwner != address(0), "");
        owner = newOwner;
    }

}

contract iRNG {
    function __callback(bytes32 _queryId, uint _result) public;
}


contract Randao is Ownable {
    struct Participant {
        uint256   secret;
        bytes32   commitment;
        uint256   reward;
        bool      revealed;
        bool      rewarded;
    }

    struct Consumer {
        address caddr;
        uint256 bountypot;
    }

    struct Campaign {
        uint32    bnum;
        uint96    deposit;
        uint16    commitBalkline;
        uint16    commitDeadline;

        uint256   random;
        bool      settled;
        uint256   bountypot;
        uint32    commitNum;
        uint32    revealsNum;

        mapping (address => Consumer) consumers;
        mapping (address => Participant) participants;
    }

    uint256 public numCampaigns;
    Campaign[] public campaigns;
    address public founder;

    address public rng;

    modifier blankAddress(address _n) { if (_n != address(0)) revert(); _; }

    modifier moreThanZero(uint256 _deposit) { if (_deposit <= 0) revert(); _; }

    modifier notBeBlank(bytes32 _s) { if (_s == "") revert(); _; }

    modifier beBlank(bytes32 _s) { if (_s != "") revert(); _; }

    modifier beFalse(bool _t) { if (_t) revert(); _; }

    constructor() public {
        founder = msg.sender;
    }

    event LogCampaignAdded(uint256 indexed campaignID,
        address from,
        uint32 bnum,
        uint96 deposit,
        uint16 commitBalkline,
        uint16 commitDeadline,
        uint256 bountypot);

    modifier timeLineCheck(uint32 _bnum, uint16 _commitBalkline, uint16 _commitDeadline) {
        if (block.number >= _bnum) revert();
        if (_commitBalkline <= 0) revert();
        if (_commitDeadline <= 0) revert();
        if (_commitDeadline >= _commitBalkline) revert();
        if (block.number >= _bnum - _commitBalkline) revert();
        _;
    }

    function newCampaign(
        uint32 _bnum,
        uint96 _deposit,
        uint16 _commitBalkline,
        uint16 _commitDeadline
    )
        payable
        timeLineCheck(_bnum, _commitBalkline, _commitDeadline)
        moreThanZero(_deposit)
        external
        returns (uint256 _campaignID)
    {
        _campaignID = campaigns.length++;
        Campaign storage c = campaigns[_campaignID];
        numCampaigns++;
        c.bnum = _bnum;
        c.deposit = _deposit;
        c.commitBalkline = _commitBalkline;
        c.commitDeadline = _commitDeadline;
        c.bountypot = msg.value;
        c.consumers[msg.sender] = Consumer(msg.sender, msg.value);
        emit LogCampaignAdded(_campaignID, msg.sender, _bnum, _deposit, _commitBalkline, _commitDeadline, msg.value);
    }

    event LogFollow(uint256 indexed CampaignId, address indexed from, uint256 bountypot);

    function follow(uint256 _campaignID)
        external
        payable
        returns (bool)
    {
        Campaign storage c = campaigns[_campaignID];
        Consumer storage consumer = c.consumers[msg.sender];
        return followCampaign(_campaignID, c, consumer);
    }

    modifier checkFollowPhase(uint256 _bnum, uint16 _commitDeadline) {
        if (block.number > _bnum - _commitDeadline) revert();
        _;
    }

    function followCampaign(
        uint256 _campaignID,
        Campaign storage c,
        Consumer storage consumer
    )
        checkFollowPhase(c.bnum, c.commitDeadline)
        blankAddress(consumer.caddr)
        internal
        returns (bool)
    {
        c.bountypot += msg.value;
        c.consumers[msg.sender] = Consumer(msg.sender, msg.value);
        emit LogFollow(_campaignID, msg.sender, msg.value);
        return true;
    }

    event LogCommit(uint256 indexed CampaignId, address indexed from, bytes32 commitment);

    function commit(uint256 _campaignID, bytes32 _hs) notBeBlank(_hs) external payable {
        Campaign storage c = campaigns[_campaignID];
        commitmentCampaign(_campaignID, _hs, c);
    }

    modifier checkDeposit(uint256 _deposit) { if (msg.value != _deposit) revert(); _; }

    modifier checkCommitPhase(uint256 _bnum, uint16 _commitBalkline, uint16 _commitDeadline) {
        if (block.number < _bnum - _commitBalkline) revert();
        if (block.number > _bnum - _commitDeadline) revert();
        _;
    }

    function commitmentCampaign(
        uint256 _campaignID,
        bytes32 _hs,
        Campaign storage c
    )
        checkDeposit(c.deposit)
        checkCommitPhase(c.bnum, c.commitBalkline, c.commitDeadline)
        beBlank(c.participants[msg.sender].commitment)
        internal
    {
        c.participants[msg.sender] = Participant(0, _hs, 0, false, false);
        c.commitNum++;
        emit LogCommit(_campaignID, msg.sender, _hs);
    }

    event LogReveal(uint256 indexed CampaignId, address indexed from, uint256 secret);

    function reveal(uint256 _campaignID, uint256 _s) external {
        Campaign storage c = campaigns[_campaignID];
        Participant storage p = c.participants[msg.sender];
        revealCampaign(_campaignID, _s, c, p);
    }

    modifier checkRevealPhase(uint256 _bnum, uint16 _commitDeadline) {
        if (block.number <= _bnum - _commitDeadline) revert();
        if (block.number >= _bnum) revert();
        _;
    }

    modifier checkSecret(uint256 _s, bytes32 _commitment) {
        if (keccak256(abi.encodePacked(keccak256(abi.encodePacked(_s)))) != _commitment) revert();
        _;
    }

    function revealCampaign(
        uint256 _campaignID,
        uint256 _s,
        Campaign storage c,
        Participant storage p
    )
        checkRevealPhase(c.bnum, c.commitDeadline)
        checkSecret(_s, p.commitment)
        beFalse(p.revealed)
        internal
    {
        p.secret = _s;
        p.revealed = true;
        c.revealsNum++;
        c.random ^= uint256(keccak256(abi.encodePacked(p.secret)));
        emit LogReveal(_campaignID, msg.sender, _s);
    }

    modifier bountyPhase(uint256 _bnum){ if (block.number < _bnum) revert(); _; }

    function getRandom(uint256 _campaignID) external returns (uint256) {
        Campaign storage c = campaigns[_campaignID];
        return returnRandom(c);
    }

    function returnRandom(Campaign storage c) bountyPhase(c.bnum) internal returns (uint256) {
        if (c.revealsNum > 0) {
            c.settled = true;
            return c.random;
        }
    }

    // The commiter get his bounty and deposit, there are three situations
    // 1. Campaign succeeds.Every revealer gets his deposit and the bounty.
    // 2. Someone revels, but some does not,Campaign fails.
    // The revealer can get the deposit and the fines are distributed.
    // 3. Nobody reveals, Campaign fails.Every commiter can get his deposit.
    function getMyBounty(uint256 _campaignID) external {
        Campaign storage c = campaigns[_campaignID];
        Participant storage p = c.participants[msg.sender];
        transferBounty(c, p);
    }

    function transferBounty(
        Campaign storage c,
        Participant storage p
    )
        bountyPhase(c.bnum)
        beFalse(p.rewarded)
        internal
    {
        if (c.revealsNum > 0) {
            if (p.revealed) {
                uint256 share = calculateShare(c);
                returnReward(share, c, p);
            }
            // Nobody reveals
        } else {
            returnReward(0, c, p);
        }
    }

    function calculateShare(Campaign memory c) internal pure returns (uint256 _share) {
        // Someone does not reveal. Campaign fails.
        if (c.commitNum > c.revealsNum) {
            _share = (c.bountypot + fines(c)) / c.revealsNum;
            // Campaign succeeds.
        } else {
            _share = c.bountypot / c.revealsNum;
        }
    }

    function returnReward(
        uint256 _share,
        Campaign storage c,
        Participant storage p
    ) internal {
        p.reward = _share;
        p.rewarded = true;
        if (!msg.sender.send(_share + c.deposit)) {
            p.reward = 0;
            p.rewarded = false;
        }
    }

    function fines(Campaign memory c) internal pure returns (uint256) {
        return (c.commitNum - c.revealsNum) * c.deposit;
    }

    // If the campaign fails, the consumers can get back the bounty.
    function refundBounty(uint256 _campaignID) external {
        Campaign storage c = campaigns[_campaignID];
        returnBounty(c);
    }

    modifier campaignFailed(uint32 _commitNum, uint32 _revealsNum) {
        if (_commitNum != 0 && _revealsNum != 0) revert();
        _;
    }

    modifier beConsumer(address _caddr) {
        if (_caddr != msg.sender) revert();
        _;
    }

    function returnBounty(Campaign storage c)
        bountyPhase(c.bnum)
        campaignFailed(c.commitNum, c.revealsNum)
        beConsumer(c.consumers[msg.sender].caddr)
        internal
    {
        uint256 bountypot = c.consumers[msg.sender].bountypot;
        c.consumers[msg.sender].bountypot = 0;
        if (!msg.sender.send(bountypot)) {
            c.consumers[msg.sender].bountypot = bountypot;
        }
    }

    function getDoubleKeccak256(uint256 _s) public pure returns (bytes32) {
        return bytes32(keccak256(abi.encodePacked(keccak256(abi.encodePacked(_s)))));
    }

    function getKeccak256(uint256 _s) public pure returns (bytes32) {
        return bytes32(keccak256(abi.encodePacked(_s)));
    }

    function getBytes32(uint256 _s) public pure returns (bytes32) {
        return bytes32(_s);
    }

    function setRNG(address _rng) public onlyOwner {
        require(_rng != address(0));

        rng = _rng;
    }

    function sendRandomToRNg(uint256 _campaignID) public onlyOwner bountyPhase(campaigns[_campaignID].bnum) {
        iRNG(rng).__callback(bytes32(_campaignID), campaigns[_campaignID].random);
    }

}