contract QuickxProtocol is QuickxToken {
    using SafeMath for uint;
    // ------------------------------------------------------------------------
    //          STATE VARIABLES  00000000
    // ------------------------------------------------------------------------
    // 50% of totail coins will be sold in ico
    uint public tokenSaleAllocation = 250000000 * (10 ** 8);
    // 2% of total supply goes for bounty 
    uint public bountyAllocation = 10000000 * (10 ** 8); 
    //13% of total tokens is reserved for founders and team
    uint public founderAllocation =  65000000 * (10 ** 8); 
    //5% of total tokens is reserved for partners
    uint public partnersAllocation = 25000000 * (10 ** 8); 
    // 15% of total tokens is for Liquidity reserve
    uint public liquidityReserveAllocation = 75000000 * (10 ** 8); 
    //5% of total tokens is reserved for advisors
    uint public advisoryAllocation = 25000000 * (10 ** 8); 
    //10% of total tokens in reserved for pre-seed Inverstors
    uint public preSeedInvestersAllocation = 50000000 * (10 ** 8); 
    
    uint[] public founderFunds = [
        1300000000000000,
        2600000000000000, 
        3900000000000000, 
        5200000000000000, 
        6500000000000000
    ]; // 8 decimals included

    uint[] public advisoryFunds = [
        500000000000000, 
        1000000000000000,
        1500000000000000, 
        2000000000000000, 
        2500000000000000
    ];

    uint public founderFundsWithdrawn = 0;
    uint public advisoryFundsWithdrawn = 0;
    // check allcatios
    bool public bountyAllocated;
    //bool public founderAllocated;
    bool public partnersAllocated;
    bool public liquidityReserveAllocated;
    bool public preSeedInvestersAllocated;
    
    uint public icoSuccessfulTime;
    bool public isIcoSuccessful;

    address public beneficiary;   // address of hard wallet of admin. 

    // ico state variables
    uint private totalRaised = 0;     // total wei raised by ICO
    uint private totalCoinsSold = 0;   // total coins sold in ICO
    uint private softCap;             // soft cap target in ether
    uint private hardCap;             // hard cap target in ether
    // rate is number of tokens (including decimals) per wei
    uint private rateNum;              // rate numerator (to avoid fractions) (rate = rateNum/rateDeno)
    uint private rateDeno;              // rate denominator (to avoid fractions) (rate = rateNum/rateDeno)
    uint public tokenSaleStart;       // time when token sale starts
    uint public tokenSaleEnds;        // time when token sale ends
    bool public icoPaused;            // ICO can be paused anytime

    // ------------------------------------------------------------------------
    //                EVENTS
    // ------------------------------------------------------------------------
    event LogBontyAllocated(
        address recepient, 
        uint amount);

    event LogPartnersAllocated(
        address recepient, 
        uint amount);

    event LogLiquidityreserveAllocated(
        address recepient, 
        uint amount);

    event LogPreSeedInverstorsAllocated(
        address recepient,
        uint amount);

    event LogAdvisorsAllocated(
        address recepient, 
        uint amount);

    event LogFoundersAllocated(
        address indexed recepient, 
        uint indexed amount);
    
    // ico events
    event LogFundingReceived(
        address indexed addr, 
        uint indexed weiRecieved, 
        uint indexed tokenTransferred, 
        uint currentTotal);

    event LogRateUpdated(
        uint rateNum, 
        uint rateDeno); 

    event LogPaidToOwner(
        address indexed beneficiary,
        uint indexed amountPaid);

    event LogWithdrawnRemaining(
        address _owner, 
        uint amountWithdrawan);

    event LogIcoEndDateUpdated(
        uint _oldEndDate, 
        uint _newEndDate);

    event LogIcoSuccessful();
    
    /* mappings */
    mapping (address => uint) public contributedAmount; // amount contributed by a user

    // ------------------------------------------------------------------------
    //               CONSTRUCTOR
    // ------------------------------------------------------------------------
    constructor () public {
        owner = address(0x2cf93Eed42d4D0C0121F99a4AbBF0d838A004F64);
        rateNum = 75;
        rateDeno = 100000000;
        softCap = 4000  ether;
        hardCap = 30005019135500000000000  wei;
        tokenSaleStart = now;
        tokenSaleEnds = now;
        balances[this] = currentSupply;
        isIcoSuccessful = true;
        icoSuccessfulTime = now;
        beneficiary = address(0x2cf93Eed42d4D0C0121F99a4AbBF0d838A004F64);
        emit LogIcoSuccessful();
        emit Transfer(0x0, address(this), currentSupply);
    }

    /* Fallback function */
    function () public payable {
        require(msg.data.length == 0);
        contribute();
    }

    modifier isFundRaising() { 
        require(
            totalRaised <= hardCap &&
            now >= tokenSaleStart &&
            now < tokenSaleEnds &&
            !icoPaused
            );
        _;
    }

    // ------------------------------------------------------------------------
    //                ONLY OWNER METHODS
    // ------------------------------------------------------------------------
    function allocateBountyTokens() public onlyOwner {
        require(isIcoSuccessful && icoSuccessfulTime > 0);
        require(!bountyAllocated); 
        balances[owner] = balances[owner].add(bountyAllocation);
        currentSupply = currentSupply.add(bountyAllocation);
        bountyAllocated = true;
        assert(currentSupply <= totalSupply);
        emit LogBontyAllocated(owner, bountyAllocation);
        emit Transfer(0x0, owner, bountyAllocation);
    }

    function allocatePartnersTokens() public onlyOwner {
        require(isIcoSuccessful && icoSuccessfulTime > 0);
        require(!partnersAllocated);
        balances[owner] = balances[owner].add(partnersAllocation);
        currentSupply = currentSupply.add(partnersAllocation);
        partnersAllocated = true;
        assert(currentSupply <= totalSupply);
        emit LogPartnersAllocated(owner, partnersAllocation);
        emit Transfer(0x0, owner, partnersAllocation);
    }

    function allocateLiquidityReserveTokens() public onlyOwner {
        require(isIcoSuccessful && icoSuccessfulTime > 0);
        require(!liquidityReserveAllocated);
        balances[owner] = balances[owner].add(liquidityReserveAllocation);
        currentSupply = currentSupply.add(liquidityReserveAllocation);
        liquidityReserveAllocated = true;
        assert(currentSupply <= totalSupply);
        emit LogLiquidityreserveAllocated(owner, liquidityReserveAllocation);
        emit Transfer(0x0, owner, liquidityReserveAllocation);
    }

    function allocatePreSeedInvesterTokens() public onlyOwner {
        require(isIcoSuccessful && icoSuccessfulTime > 0);
        require(!preSeedInvestersAllocated);
        balances[owner] = balances[owner].add(preSeedInvestersAllocation);
        currentSupply = currentSupply.add(preSeedInvestersAllocation);
        preSeedInvestersAllocated = true;
        assert(currentSupply <= totalSupply);
        emit LogPreSeedInverstorsAllocated(owner, preSeedInvestersAllocation);
        emit Transfer(0x0, owner, preSeedInvestersAllocation);
    }

    function allocateFounderTokens() public onlyOwner {
        require(isIcoSuccessful && icoSuccessfulTime > 0);
        uint calculatedFunds = calculateFoundersTokens();
        uint eligibleFunds = calculatedFunds.sub(founderFundsWithdrawn);
        require(eligibleFunds > 0);
        balances[owner] = balances[owner].add(eligibleFunds);
        currentSupply = currentSupply.add(eligibleFunds);
        founderFundsWithdrawn = founderFundsWithdrawn.add(eligibleFunds);
        assert(currentSupply <= totalSupply);
        emit LogFoundersAllocated(owner, eligibleFunds);
        emit Transfer(0x0, owner, eligibleFunds);
    }

    function allocateAdvisoryTokens() public onlyOwner {
        require(isIcoSuccessful && icoSuccessfulTime > 0);
        uint calculatedFunds = calculateAdvisoryTokens();
        uint eligibleFunds = calculatedFunds.sub(advisoryFundsWithdrawn);
        require(eligibleFunds > 0);
        balances[owner] = balances[owner].add(eligibleFunds);
        currentSupply = currentSupply.add(eligibleFunds);
        advisoryFundsWithdrawn = advisoryFundsWithdrawn.add(eligibleFunds);
        assert(currentSupply <= totalSupply);
        emit LogAdvisorsAllocated(owner, eligibleFunds);
        emit Transfer(0x0, owner, eligibleFunds);
    }

    // there is no explicit need of this function as funds are directly transferred to owner's hardware wallet.
    // but this is kept just to avoid any case when ETH is locked in contract
    function withdrawEth () public onlyOwner {
        owner.transfer(address(this).balance);
        emit LogPaidToOwner(owner, address(this).balance);
    }

    function updateRate (uint _rateNum, uint _rateDeno) public onlyOwner {
        rateNum = _rateNum;
        rateDeno = _rateDeno;
        emit LogRateUpdated(rateNum, rateDeno);
    }

    function updateIcoEndDate(uint _newDate) public onlyOwner {
        uint oldEndDate = tokenSaleEnds;
        tokenSaleEnds = _newDate;
        emit LogIcoEndDateUpdated (oldEndDate, _newDate);
    }

    // admin can withdraw token not sold in ICO
    function withdrawUnsold() public onlyOwner returns (bool) {
        require(now > tokenSaleEnds);
        uint unsold = (tokenSaleAllocation.sub(totalCoinsSold));
        balances[owner] = balances[owner].add(unsold);
        balances[address(this)] = balances[address(this)].sub(unsold);
        emit LogWithdrawnRemaining(owner, unsold);
        emit Transfer(address(this), owner, unsold);
        return true;
    }

    // ------------------------------------------------------------------------
    // Owner can transfer out any accidentally sent ERC20 tokens
    // ------------------------------------------------------------------------
    function transferAnyERC20Token(address _tokenAddress, uint _value) public onlyOwner returns (bool success) {
        // this condition is to stop admin from withdrawing funds unless all funds of ICO are successfully settelled
        if (_tokenAddress == address(this)) {
            require(now > tokenSaleStart + 730 days); // expecting 2 years time, all vested funds will be released.
        }
        return ERC20(_tokenAddress).transfer(owner, _value);
    }

    function pauseICO(bool pauseStatus) public onlyOwner returns (bool status) {
        require(icoPaused != pauseStatus);
        icoPaused = pauseStatus;
        return true;
    }

    // ------------------------------------------------------------------------
    //               PUBLIC METHODS
    // ------------------------------------------------------------------------
    function contribute () public payable isFundRaising returns(bool) {
        uint calculatedTokens =  calculateTokens(msg.value);
        require(calculatedTokens > 0);
        require(totalCoinsSold.add(calculatedTokens) <= tokenSaleAllocation);
        contributedAmount[msg.sender] = contributedAmount[msg.sender].add(msg.value);
        totalRaised = totalRaised.add(msg.value);
        totalCoinsSold = totalCoinsSold.add(calculatedTokens);
        _transfer(address(this), msg.sender, calculatedTokens);
        beneficiary.transfer(msg.value);
        checkIfSoftCapReached();
        emit LogFundingReceived(msg.sender, msg.value, calculatedTokens, totalRaised);
        emit LogPaidToOwner(beneficiary, msg.value);
        return true;
    }

    // ------------------------------------------------------------------------
    //              CONSTANT METHODS
    // ------------------------------------------------------------------------
    function calculateTokens(uint weisToTransfer) public view returns(uint) {
        uint discount = calculateDiscount();
        uint coins = weisToTransfer.mul(rateNum).mul(discount).div(100 * rateDeno);
        return coins;
    }

    function getTotalWeiRaised () public view returns(uint totalEthRaised) {
        return totalRaised;
    }

    function getTotalCoinsSold() public view returns(uint _coinsSold) {
        return totalCoinsSold;
    }
      
    function getSoftCap () public view returns(uint _softCap) {
        return softCap;
    }

    function getHardCap () public view returns(uint _hardCap) {
        return hardCap;
    }

    function getContractOwner () public view returns(address contractOwner) {
        return owner;
    }

    function isContractAcceptingPayment() public view returns (bool) {
        if (totalRaised < hardCap && 
            now >= tokenSaleStart && 
            now < tokenSaleEnds && 
            totalCoinsSold < tokenSaleAllocation)
            return true;
        else
            return false;
    }

    // ------------------------------------------------------------------------
    //                INTERNAL METHODS
    // ------------------------------------------------------------------------
    function calculateFoundersTokens() internal view returns(uint) {
        uint timeAferIcoSuceess = now.sub(icoSuccessfulTime);
        uint timeSpendInMonths = timeAferIcoSuceess.div(30 days);
        if (timeSpendInMonths >= 3 && timeSpendInMonths < 6) {
            return founderFunds[0];
        } else  if (timeSpendInMonths >= 6 && timeSpendInMonths < 9) {
            return founderFunds[1];
        } else if (timeSpendInMonths >= 9 && timeSpendInMonths < 12) {
            return founderFunds[2];
        } else if (timeSpendInMonths >= 12 && timeSpendInMonths < 18) {
            return founderFunds[3];
        } else if (timeSpendInMonths >= 18) {
            return founderFunds[4];
        } else {
            revert();
        }
    } 

    function calculateAdvisoryTokens()internal view returns(uint) {
        uint timeSpentAfterIcoEnd = now.sub(icoSuccessfulTime);
        uint timeSpendInMonths = timeSpentAfterIcoEnd.div(30 days);
        if (timeSpendInMonths >= 0 && timeSpendInMonths < 3)
            return advisoryFunds[0];
        if (timeSpendInMonths < 6)
            return advisoryFunds[1];
        if (timeSpendInMonths < 9)
            return advisoryFunds[2];
        if (timeSpendInMonths < 12)
            return advisoryFunds[3];
        if (timeSpendInMonths >= 12)
            return advisoryFunds[4];
        revert();
    }

    function checkIfSoftCapReached() internal returns (bool) {
        if (totalRaised >= softCap && !isIcoSuccessful) {
            isIcoSuccessful = true;
            icoSuccessfulTime = now;
            emit LogIcoSuccessful();
        }
        return;
    }

    function calculateDiscount() internal view returns(uint) {
        if (totalCoinsSold < 12500000000000000) {
            return 115;   // 15 % discount
        } else if (totalCoinsSold < 18750000000000000) {
            return 110;   // 10 % discount
        } else if (totalCoinsSold < 25000000000000000) {
            return 105;   // 5 % discount
        } else {  // this case should never arise
            return 100;   // 0 % discount
        }
    }

}
function withdrawEth () public onlyOwner {
    owner.transfer(address(this).balance);
    emit LogPaidToOwner(owner, address(this).balance);
}

// there is no explicit need of this function as funds are directly transferred to owner's hardware wallet.
// but this is kept just to avoid any case when ETH is locked in contract