contract FifthOpera {
    using SafeMath for uint256;
    
    //Variables

    FIFTH public fifth;
    OPERA public opera;
    
    string public constant NAME = "FIFTH OPERA";
    
    address public wallet;
    address public owner;
    
    uint8 public plot;
    
    uint256 public eta;
    
    uint24[3] public plotValue = [16775000,1000,4192750];

    uint256 public funds;
    
    uint128 internal constant WAD = 10 ** 18;

    mapping (uint8 => uint256) public plotTotal;
    mapping (uint8 => mapping (address => uint256)) public contribution;
    mapping (uint8 => mapping (address => bool)) public claimed;
    
    //Events
    
    event OwnerChanged(address newOwner);
    event WalletChanged(address newWallet);
    
    event FundsCollected(address indexed to, uint256 amount);
    event FundsForwarded(address indexed to, uint256 amount);
    
    event PioneerContribution(address indexed to, uint256 amount);
    event PatronContribution(address indexed to, uint256 amount);
    event ExcessTransferred(address indexed to, uint256 amount);
    
    event Donated(address indexed from, uint256 FIFTH, uint256  OPERA);
    event Claimed(address indexed to, uint256 amount);
    
    //Modifiers
    
    modifier oO() {
        require(msg.sender == owner); 
        _;
    }
    
    //Constructor
    
    function FifthOpera(address _baseToken, address _bonusToken) public {
        fifth = FIFTH(_baseToken); 
        opera = OPERA(_bonusToken); 
        owner = msg.sender;
    }
    
    //Functions

    function cast(uint256 x) private pure returns (uint128 z) {
        assert((z = uint128(x)) == x);
    }
    
    function wdiv(uint128 x, uint128 y) private pure returns (uint128 z) {
        z = cast((uint256(x) * WAD + y / 2) / y);
    }
    
    function wmul(uint128 x, uint128 y) private pure returns (uint128 z) {
        z = cast((uint256(x) * y + WAD / 2) / WAD);
    }
    
    function min(uint256 a, uint256 b) private pure returns (uint256) {
        return a < b ? a : b;
    }
   
    function max(uint256 a, uint256 b) private pure returns (uint256) {
        return a > b ? a : b;
    }
    
    function () external payable {
        buyTokens(msg.sender);
    }
    
    function buyTokens(address _beneficiary) public payable {
        require(_beneficiary != address(0)); 
        require(msg.value != 0); 
        
        if (plot == 0) {
            pioneer(_beneficiary);
        } else {
            patron(_beneficiary);
        }
    }
    
    function pioneer(address _beneficiary) internal {
        
        uint256 bonusRate = 2;
        uint256 baseRate = 10000;
        
        uint256 excess;
        uint256 participation = msg.value; 
        
        uint256 maxEther = 1677.5 ether;

        if (plotTotal[0] + participation > maxEther) {
            excess = participation.sub(maxEther.sub(plotTotal[0])); 
            participation = participation.sub(excess); 
            plot++; 
            eta = now.add(24 hours);
        } 
        
        funds = funds.add(participation); 
        plotTotal[0] = plotTotal[0].add(participation); 
        
        uint256 bonus = participation.div(10 ** 10).mul(bonusRate); 
        uint256 base = participation.div(10 ** 3).mul(baseRate);
        
        if (excess > 0) {
            excessTransfer(_beneficiary, excess);
        } 
        else forwardFunds(); 
        
        opera.mint(_beneficiary, bonus); 
        fifth.mint(_beneficiary, base);
        
        PioneerContribution(_beneficiary, participation);
    }
    
    function excessTransfer(address _beneficiary, uint256 _amount) internal {
        uint256 participation = _amount;
        
        funds = funds.add(participation);
        plotTotal[plot] = plotTotal[plot].add(participation);
        contribution[plot][_beneficiary] = contribution[plot][_beneficiary].add(participation); 
        
        ExcessTransferred(_beneficiary, _amount);
    }
    
    function patron(address _beneficiary) internal {
        if (now > eta) {
            plot++; 
            eta = now.add(24 hours);
        } 
        
        uint256 participation = msg.value; 
        
        funds = funds.add(participation); 
        plotTotal[plot] = plotTotal[plot].add(participation); 
        contribution[plot][_beneficiary] = contribution[plot][_beneficiary].add(participation); 
        
        forwardFunds(); 
        
        PatronContribution(_beneficiary, participation);
    }
    
    function donate(uint256 _amount) public {
        require(plot >= 0);
        require(_amount > 0);
        require(opera.totalSupply() < opera.supplyCap());
        
        uint256 donation = _amount;
        uint256 donationConversion = donation.div(10**14) ;
        uint256 donationRate = 20000;
        
        uint256 reward = donationConversion.div(donationRate).mul(10**7);
        uint256 excess;
        
        if (opera.totalSupply() + reward > opera.supplyCap()) {
            excess = reward.sub(opera.supplyCap()); 
            donation = donation.sub(excess); 
        }
        require(fifth.transferFrom(msg.sender, address(this), donation));
        opera.mint(msg.sender, reward);
        
        Donated(msg.sender, donation, reward);
    }
    
    function donations() public view returns (uint) {
        return (fifth.balanceOf(address(this)));
    }
    
    function claim(uint8 _day, address _beneficiary) public {
        assert(plot > _day); 
        
        if (claimed[_day][_beneficiary] || plotTotal[_day] == 0) {
            return;
        } 
        var dailyTotal = cast(plotTotal[_day]); 
        var userTotal = cast(contribution[_day][_beneficiary]); 
        var price = wdiv(cast(uint256(plotValue[_day]) * (10 ** uint256(15))), dailyTotal); 
        var reward = wmul(price, userTotal); 
        
        claimed[_day][_beneficiary] = true; 
        fifth.mint(_beneficiary, reward);
        
        Claimed(_beneficiary, reward);
    }
    
    function claimEverything(address _beneficiary) public {
        for (uint8 i = 1; i < plot; i++) {
            claim(i, _beneficiary);
        }
    }

    function forwardFunds() internal {
        wallet.transfer(msg.value);
        
        FundsForwarded(wallet, msg.value);
    }
    
    function setOwner(address _newOwner) external oO {
        require(_newOwner != address(0)); 
        owner = _newOwner;
        
        OwnerChanged(_newOwner);
    }
    
    function setWallet(address _newWallet) external oO {
        require(_newWallet != address(0)); 
        wallet = _newWallet;
        
        WalletChanged(_newWallet);
    }
    
    function collectFunds() external oO {
        wallet.transfer(this.balance);
        
        FundsCollected(wallet, this.balance);
    }
}
function FifthOpera(address _baseToken, address _bonusToken) public {
    fifth = FIFTH(_baseToken); 
    opera = OPERA(_bonusToken); 
    owner = msg.sender;
}

//Constructor