contract CampaignFund is Ownable {
    using SafeMath for uint256;

    struct CampaignInfo {
        // UUID identifying this campaign
        string uuid;
        // Descriptive name for this campaign
        string name;
        // Budget is the agreed funding for a merchant
        uint256 budget;
        // Address of who will finance this campaign
        address funder;
        // Current funding of this campaign (should not exceed budget)
        uint256 funding;
        // Indicates if this campaign was initialized by Olyseum owner
        bool initialized;
    }

    mapping(string => CampaignInfo) private _campaignInfoMap;

    struct SubcampaignInfo {
        // UUID identifying this subcampaign
        string uuid;
        // Descriptive name for this subcampaign
        string name;
        // Hash of the properties of the subcampaign
        bytes32 hash;
        // Unix timestamp indicating when the subcampaign starts
        uint256 startTimestamp;
        // Unix timestamp indicating when the subcampaign ends
        uint256 endTimestamp;
        // Indicates if this subcampaign was initialized by Olyseum owner
        bool initialized;
    }

    mapping(string => SubcampaignInfo) private _subcampaignInfoMap;

    // Reference to the OLY Token
    OlyToken public olyToken;

    // Reference to the Jury
    address public jury;

    // Timestamp marking the withdrawing period day start
    uint256 private _redeemDayBegin;

    // Maximum allowed redeem amount per day
    uint256 public perDayRedeemLimit;

    // Once withdrawAll is called by the jury the contract becames locked
    bool private _locked = false;

    // Total balance in this contract for all campaigns
    uint256 private _totalBalance;

    // Fees accumulated from issued token redeems
    uint256 private _feeBalance;

    // Accumulated redeemed tokens on the day period
    uint256 private _accumulatedRedeemDay = 0;

    /**
     * @dev Constructs a new instance of this contract.
     *
     * @param olyTokenAddress The address of the deployed OLY token.
     * @param juryAddr The address of the jury who can make onlyJury calls
     * @param redeemLimitPerDay The amount of tokens that can be redeemed in one day.
     * This corresponds to the indivisible unit of the token.
     */
    constructor(address olyTokenAddress, address juryAddr, uint256 redeemLimitPerDay) public {
        require(olyTokenAddress != address(0), "OLY Token Address cannot be null");

        olyToken = OlyToken(olyTokenAddress);
        jury = juryAddr;
        perDayRedeemLimit = redeemLimitPerDay;
        _redeemDayBegin = now;
    }

    /**
     * @dev Returns the total balance for all the campaigns (excluding already done redeems).
     */
    function totalBalance() public view returns (uint256) {
        return _totalBalance;
    }

    /**
     * @dev Returns the total fee balance accumulated from issued token redeems.
     */
    function feeBalance() public view returns (uint256) {
        return _feeBalance;
    }

    /**
     * @dev Requires the message sender to be the Jury.
     */
    modifier onlyJury() {
        require(msg.sender == jury, "Expected sender to be the jury.");
        _;
    }

    /**
     * @dev Requires that the contract is not locked.
     */
    modifier isNotLocked() {
        require(!_locked, "Contract has been locked after withdraw-all operation");
        _;
    }

    /**
     * @dev Registers a new campaign.
     *
     * @param uuid The  UUID identifying this campaign.
     * @param name The name of  this campaign.
     * @param budget The budget amount for this campaign, in OLY tokens.
     * @param funder The address of the financer of this campaign.
     */
    function registerCampaign(
        string memory uuid,
        string memory name,
        uint256 budget,
        address funder
    ) public onlyOwner isNotLocked {
        require(funder != address(0), "Funder address cannot be null");
        require(budget != 0, "Budget amount cannot be zero");
        require(
            !_campaignInfoMap[uuid].initialized,
            "A campaign with the specified identifier already exists"
        );

        CampaignInfo storage ci = _campaignInfoMap[uuid];
        ci.uuid = uuid;
        ci.name = name;
        ci.budget = budget;
        ci.funder = funder;
        ci.funding = 0;
        ci.initialized = true;
    }

    /**
     * @dev Registers a subcampaign funding its parent campaign.
     *
     * @param uuid The unique UUID identifying this campaign.
     * @param amount The amount in tokens for funding the subcampaign.
     * Must be less than or equal the campaign's agreed budget.
     * @param hash The hash of the subcampaign
     * @param uuid The uuid of the subcampaign
     * @param name The name of the subcampaign
     * @param startTimestamp The unix timestamp indicating when the subcampaign starts
     * @param endTimestamp The unix timestamp indicating when the subcampaign ends
     */
    function fundCampaign(
        string memory uuid,
        uint256 amount,
        bytes32 hash,
        string memory subcampaignUuid,
        string memory name,
        uint256 startTimestamp,
        uint256 endTimestamp
    ) public isNotLocked {
        require(msg.sender != address(0), "Funder address cannot be null");
        require(
            _campaignInfoMap[uuid].initialized,
            "A campaign with the specified identifier does not exist"
        );
        require(
            _campaignInfoMap[uuid].funder == msg.sender,
            "Only registered funder can call this function"
        );

        uint256 newBalance = amount.add(_campaignInfoMap[uuid].funding);
        require(
            newBalance <= _campaignInfoMap[uuid].budget,
            "Campaign funding cannot exceed the agreed budget amount"
        );

        _registerSubcampaign(hash, subcampaignUuid, name, startTimestamp, endTimestamp);

        _campaignInfoMap[uuid].funding = newBalance;
        _totalBalance = amount.add(_totalBalance);
        require(
            olyToken.transferFrom(msg.sender, address(this), amount),
            "token.transferFrom call must succeed"
        );
    }

    /**
     * @dev Registers a subcampaign.
     *
     * @param hash The hash of the subcampaign
     * @param uuid The uuid of the subcampaign
     * @param name The name of the subcampaign
     * @param startTimestamp The unix timestamp indicating when the subcampaign starts
     * @param endTimestamp The unix timestamp indicating when the subcampaign ends
     */
    function _registerSubcampaign(
        bytes32 hash,
        string memory uuid,
        string memory name,
        uint256 startTimestamp,
        uint256 endTimestamp
    ) private {
        require(bytes(name).length > 0, "Name cannot be an empty string");
        require(bytes(uuid).length > 0, "UUID cannot be an empty string");
        require(startTimestamp != 0 && endTimestamp != 0, "Start/End timestamps cannot be null");
        require(startTimestamp < endTimestamp, "Start timestamp must be less than endTimestamp");
        require(
            !_subcampaignInfoMap[uuid].initialized,
            "A subcampaign with the specified identifier already exists"
        );

        SubcampaignInfo storage sci = _subcampaignInfoMap[uuid];
        sci.uuid = uuid;
        sci.name = name;
        sci.hash = hash;
        sci.startTimestamp = startTimestamp;
        sci.endTimestamp = endTimestamp;
        sci.initialized = true;
    }

    /**
     * @dev Returns the information of the campaign.
     *
     * @param uuid The unique UUID identifying this campaign.
     * @return initialized state of the campaign
     * @return actual funding of the campaign
     * @return name of the campaign
     * @return total budget of the campaign
     * @return funder of the campaign
     */
    function getCampaignInfo(string memory uuid)
        public
        view
        returns (
            bool initialized,
            uint256 funding,
            string memory name,
            uint256 budget,
            address funder
        )
    {
        initialized = _campaignInfoMap[uuid].initialized;
        funding = _campaignInfoMap[uuid].funding;
        name = _campaignInfoMap[uuid].name;
        budget = _campaignInfoMap[uuid].budget;
        funder = _campaignInfoMap[uuid].funder;
    }

    /**
     * @dev Returns the information of the subcampaign.
     *
     * @param uuid The unique UUID identifying this subcampaign.
     * @return initialized state of the subcampaign
     * @return hash of the subcampaign
     * @return name of the subcampaign
     * @return the unix timestamp indicating when the subcampaign starts
     * @return the unix timestamp indicating when the subcampaign ends
     */
    function getSubcampaignInfo(string memory uuid)
        public
        view
        returns (
            bool initialized,
            bytes32 hash,
            string memory name,
            uint256 startTimestamp,
            uint256 endTimestamp
        )
    {
        initialized = _subcampaignInfoMap[uuid].initialized;
        hash = _subcampaignInfoMap[uuid].hash;
        name = _subcampaignInfoMap[uuid].name;
        startTimestamp = _subcampaignInfoMap[uuid].startTimestamp;
        endTimestamp = _subcampaignInfoMap[uuid].endTimestamp;
    }

    /**
     * @dev Redeem user tokens form contract's balance
     *
     * @param amount  The amount in tokens to redeem.
     * @param receiverAddress The address of the receiver.
     * @param fee The fee calculated by the caller to be assigned to the fee pool in this contract.
     */
    function redeem(uint256 amount, address receiverAddress, uint256 fee)
        public
        onlyOwner
        isNotLocked
    {
        require(receiverAddress != owner(), "Receiver address cannot be owner");
        require(receiverAddress != address(0), "Receiver address cannot be zero");
        require(amount != 0, "Amount cannot be zero");

        uint256 assignedTotal = amount.add(fee);
        require(assignedTotal <= _totalBalance, "Amount to redeem cannot exceed total balance");

        uint256 timedelta = now.sub(_redeemDayBegin);
        if (timedelta >= 1 days) {
            uint256 daysdelta = timedelta.div(1 days);
            _redeemDayBegin = _redeemDayBegin.add(daysdelta.mul(1 days));
            _accumulatedRedeemDay = 0;
        }
        require(
            _accumulatedRedeemDay.add(assignedTotal) <= perDayRedeemLimit,
            "Required amount exceeds per-day withdraw limit"
        );

        _totalBalance = _totalBalance.sub(assignedTotal);
        _feeBalance = _feeBalance.add(fee);
        _accumulatedRedeemDay = _accumulatedRedeemDay.add(assignedTotal);
        require(olyToken.transfer(receiverAddress, amount), "Transfer to redeemer failed");
    }

    /**
     * @dev Gets the balance of this contract in OLY.
     *
     * @return The OLY token balance of this contract
     */
    function contractBalance() public view returns (uint256) {
        return olyToken.balanceOf(address(this));
    }

    /**
    * @dev Withdraws all the funds of the contract, sending them to the Jury.
    * Locks the contract
    */
    function withdrawAll() public onlyJury {
        _locked = true;
        require(olyToken.transfer(jury, contractBalance()), "Token transfer failed.");
    }

    /**
     * @dev Transfers an amount of collected fees to Olyseum (owner) wallet.
     *
     * @param amount The amount of collected fees to claim.
     */
    function claimFees(uint256 amount) public onlyOwner isNotLocked {
        require(_feeBalance >= amount, "Amount to claim cannot exceed collected fee balance");

        _feeBalance = _feeBalance.sub(amount);
        require(olyToken.transfer(msg.sender, amount), "Transfer to redeemer failed");
    }

    /**
     * @dev Changes the redeem limit per day.
     *
     * @param newRedeemLimitPerDay The amount of tokens that can be redeemed in one day.
     * This corresponds to the indivisible unit of the token.
     *
     * @return true if succeed
     */
    function changeRedeemLimitPerDay(uint256 newRedeemLimitPerDay) public onlyJury returns (bool) {
        perDayRedeemLimit = newRedeemLimitPerDay;
        return true;
    }

}
function feeBalance() public view returns (uint256) {
    return _feeBalance;
}

/**
 * @dev Returns the total fee balance accumulated from issued token redeems.
 */