contract CtgToken is StandardToken, BlackList, IterableMapping {
    string public name;
    string public symbol;
    uint public decimals;

    address public pool = 0x9492D2F14d6d4D562a9DA4793b347f2AaB3B607A; //矿池地址
    address public teamAddress = 0x45D1c050C458de9b18104bdFb7ddEbA510f6D9f2; //研发团队地址
    address public peer = 0x87dfEFFa31950584d6211D6A7871c3AdA2157aE1; //节点分红
    address public foundation0 = 0x6eDFEaB0D0B6BD3d6848A3556B2753f53b182cCd;
    address public foundation1 = 0x5CD65995e25EC1D73EcDBc61D4cF32238304D1eA;
    address public foundation2 = 0x7D1E3dD3c5459BAdA93C442442D4072116e21034;
    address public foundation3 = 0x5001c2917B18B18853032C3e944Fe512532E0FD1;
    address public foundation4 = 0x9c131257919aE78B746222661076CF781a8FF7c6;
    address public candy = 0x279C18756568B8717e915FfB8eFe2784abCb89cf;
    address public contractAddress = 0x81E98EfF052837f7c1Dceb8947d08a2b908E8793;
    uint public recommendNum;

    itmap accounts;

    mapping(uint => uint) public shareRate;
    mapping(address => uint8) public levels;
    mapping(uint => uint) public levelProfit;

    struct StaticProfit {
        uint num;
        uint8 day;
        uint8 rate;
    }

    mapping(uint => StaticProfit) public profits;
    mapping(address => AddressInfo) public addressInfos;
    
    struct AddressInfo {
        address[] children;
        address _address;
        uint[] profitsIndex;
        bool activated;
    }

    struct ProfitLog {
        address _address;
        uint levelNum;
        uint num;
        uint8 day;
        uint8 rate;
        uint8 getDay;
        uint updatedAt;
    }

    mapping(uint => ProfitLog) public profitLogs;
    uint logIndex = 0;

    constructor(string _name, string _symbol, uint _decimals) public {
        totalSupplyNum = formatDecimals(720000000);
        name = _name;
        symbol = _symbol;
        decimals = _decimals;

        balances[pool] = formatDecimals(5400000000);
        balances[teamAddress] = formatDecimals(64800000);
        balances[peer] = formatDecimals(43200000);
        balances[foundation0] = formatDecimals(10080000);
        balances[foundation1] = formatDecimals(10080000);
        balances[foundation2] = formatDecimals(10080000);
        balances[foundation3] = formatDecimals(10080000);
        balances[foundation4] = formatDecimals(10080000);
        balances[candy] = formatDecimals(21600000);

        //推广收益比例
        shareRate[1] = 7;
        shareRate[2] = 5;
        shareRate[3] = 3;
        shareRate[4] = 2;
        shareRate[5] = 1;
        shareRate[6] = 1;
        shareRate[7] = 1;
        shareRate[8] = 1;
        shareRate[9] = 1;
        shareRate[10] = 1;
        
        //等级奖励
        levelProfit[1] = formatDecimals(1000);
        levelProfit[2] = formatDecimals(3000);
        levelProfit[3] = formatDecimals(10000);
        levelProfit[4] = formatDecimals(50000);
        levelProfit[5] = formatDecimals(100000);

        //合约收益配置
        profits[formatDecimals(100)] = StaticProfit(formatDecimals(100), 30, 10);
        profits[formatDecimals(1000)] = StaticProfit(formatDecimals(1000), 30, 15);
        profits[formatDecimals(5000)] = StaticProfit(formatDecimals(5000), 30, 20);
        profits[formatDecimals(10000)] = StaticProfit(formatDecimals(10000), 30, 25);
        profits[formatDecimals(30000)] = StaticProfit(formatDecimals(30000), 30, 30);
        
        recommendNum = formatDecimals(23).div(10);
    }
    
    function setLevelProfit(uint level, uint num) public onlyOwner {
        require(levelProfit[level] > 0, "The level config doesn't exist!");
        levelProfit[level] = formatDecimals(num);
    }
    
    function setRecommendNum(uint num) public onlyOwner {
        require(recommendNum != num, "The value is equal old value!");
        recommendNum = num;
    }
    
    
    function setShareRateConfig(uint level, uint rate) public onlyOwner {
        require(shareRate[level] > 0, "This level does not exist");
        
        uint oldRate = shareRate[level];
        shareRate[level] = rate;
        
        emit SetShareRateConfig(level, oldRate, rate);
    }
    
    function getProfitLevelNum(uint num) internal constant returns(uint) {
        if (num < formatDecimals(100)) {
            return 0;
        }
        if (num >=formatDecimals(100) && num < formatDecimals(1000)) {
            return formatDecimals(100);
        } 
        if (num >=formatDecimals(1000) && num < formatDecimals(5000)) {
            return formatDecimals(1000);
        }
        if (num >=formatDecimals(5000) && num < formatDecimals(10000)) {
            return formatDecimals(5000);
        }
        if (num >=formatDecimals(10000) && num < formatDecimals(30000)) {
            return formatDecimals(10000);
        }
        if (num >=formatDecimals(30000)) {
            return formatDecimals(30000);
        }
    }
    
    function getAddressProfitLevel(address _address) public constant returns (uint) {
        uint maxLevel = 0;
        uint[] memory indexes = addressInfos[_address].profitsIndex;
        for (uint i=0; i<indexes.length; i++) {
            uint k = indexes[i];
           if (profitLogs[k].day > 0 && (profitLogs[k].day > profitLogs[k].getDay) && (profitLogs[k].levelNum > maxLevel)) {
               maxLevel = profitLogs[k].levelNum;
           }
        }
        return maxLevel;
    }
    
    function getAddressProfitNum(address _address) public constant returns (uint) {
        uint num = 0;
        uint[] memory indexes = addressInfos[_address].profitsIndex;
        for (uint i=0; i<indexes.length; i++) {
            uint k = indexes[i];
            if (profitLogs[k].day > 0 && (profitLogs[k].day > profitLogs[k].getDay)) {
                num += profitLogs[k].num;
            }
        }
        
        return num;
    }
    
    function getAddressActiveChildrenCount(address _address) public constant returns (uint) {
        uint num  = 0;
        for(uint i=0; i<addressInfos[_address].children.length; i++) {
            address child = addressInfos[_address].children[i];
            AddressInfo memory childInfo = addressInfos[child];
            if (childInfo.activated) {
                num++;
            }
        }
        
        return num;
    }
    
    
    function setProfitConfig(uint256 num, uint8 day, uint8 rate) public onlyOwner {
        require(profits[formatDecimals(num)].num>0, "This profit config not exist");
        profits[formatDecimals(num)] = StaticProfit(formatDecimals(num), day, rate);
        
        emit SetProfitConfig(num, day, rate);
    }

    function formatDecimals(uint256 _value) internal view returns (uint256) {
        return _value * 10 ** decimals;
    }

    function parent(address _address) public view returns (address) {
        return accounts.data[_address].parentAddress;
    }
    
    function checkIsCycle(address _child, address _parent) internal view returns (bool) {
        address t = _parent;
        while (t != address(0)) {
            if (t == _child) {
                return true;
            }
            t = parent(t);
        }
        return false;
    }

    function iterate_start() public view returns (uint keyIndex) {
        return super.iterate_start(accounts);
    }

    function iterate_next(uint keyIndex) public view returns (uint r_keyIndex)
    {
        return super.iterate_next(accounts, keyIndex);
    }

    function iterate_valid(uint keyIndex) public view returns (bool) {
        return super.iterate_valid(accounts, keyIndex);
    }

    function iterate_get(uint keyIndex) public view returns (address accountAddress, address parentAddress, bool active) {
        (accountAddress, parentAddress, active) = super.iterate_get(accounts, keyIndex);
    }

    function sendBuyShare(address _address, uint _value) internal  {
        address p = parent(_address);
        uint level = 1;
        
        while (p != address(0) && level <= 10) {
            uint activeChildrenNum = getAddressActiveChildrenCount(p);
            if (activeChildrenNum < level) {
                p = parent(p);
                level = level + 1;
                continue;
            }
            
            AddressInfo storage info = addressInfos[p];
            if (!info.activated) {
                p = parent(p);
                level = level + 1;
                continue;
            }
            
            uint profitLevel = getAddressProfitLevel(p);
            
           
            uint addValue = _value.mul(shareRate[level]).div(100);
            if (_value > profitLevel) {
                addValue = profitLevel.mul(shareRate[level]).div(100);
            }

            
            transferFromPool(p, addValue);
            emit BuyShare(msg.sender, p, addValue);
            p = parent(p);
            level = level + 1;
        }
    }

    function releaseProfit(uint index) public onlyOwner {
        ProfitLog memory log = profitLogs[index];
        if (log.day == 0 || log.day == log.getDay) {
            return;
        }
        uint addValue = log.num.mul(uint(log.rate).add(100)).div(100).div(uint(log.day));
        
        uint diffDay = 1;
        if (log.updatedAt > 0) {
            diffDay = now.sub(log.updatedAt).div(24*3600);
        }
        if (diffDay > 0) {
            addValue = addValue.mul(diffDay);
            transferFrom(pool, log._address, addValue);
            profitLogs[index].getDay = log.getDay + uint8(diffDay);
            profitLogs[index].updatedAt = now;
        
            emit ReleaseProfit(log._address, addValue, log.getDay+1);
        }
        
    }
    
    function releaseAllProfit() public onlyOwner {
        for (uint i = 0; i<logIndex; i++) {
            releaseProfit(i);
        }
    }


    function setLevel(address _address, uint8 level, bool sendProfit) public onlyOwner {
        levels[_address] = level;
        
        emit SetLevel(_address, level);
        if (sendProfit) {
            uint num = levelProfit[uint(level)];
            if (num > 0) {
                transferFromPool(_address, num);
                emit SendLevelProfit(_address, level, num);
            }
        }
    }

    function transfer(address _to, uint _value) public {
        address parentAddress = parent(msg.sender);
        if (_value == formatDecimals(23).div(10) && parentAddress == address(0) && !checkIsCycle(msg.sender, _to)) {
            IterableMapping.insert(accounts, msg.sender, _to, addressInfos[msg.sender].activated);
            AddressInfo storage info = addressInfos[_to];
            info.children.push(msg.sender);
            super.transfer(_to, _value);
            emit SetParent(msg.sender, _to);
        } else if (_to == contractAddress) {
            super.transfer(_to, _value);
            // Static income 
            uint profitKey = getProfitLevelNum(_value);
            StaticProfit storage profit = profits[profitKey];
            if (profit.num > 0) {
                profitLogs[logIndex] = ProfitLog({_address:msg.sender, levelNum:profit.num, num : _value, day : profit.day, rate : profit.rate, getDay : 0, updatedAt: 0});
            }
            //activate user
            addressInfos[msg.sender].profitsIndex.push(logIndex);
            logIndex++;
            if (profitKey >= 1000) {
                addressInfos[msg.sender].activated = true;
                IterableMapping.insert(accounts, msg.sender, parentAddress, true);
            }
            //Dynamic  income
            if (profitKey > 0 && addressInfos[msg.sender].activated) {
                sendBuyShare(msg.sender, profitKey);
            }
            
        } else {
            super.transfer(_to, _value);
        }
    }
    
    function transferFromPool(address _to, uint _value) internal {
        balances[pool] = balances[pool].sub(_value);
        balances[_to] = balances[_to].add(_value);
        emit Transfer(pool, _to, _value);
    }
    


    function transferFrom(address _from, address _to, uint _value) public onlyOwner  {
        require(!isBlackListed[_from]);

        //        var _allowance = allowed[_from][msg.sender];

        // Check is not needed because sub(_allowance, _value) will already throw if this condition is not met
        // if (_value > _allowance) throw;

        uint fee = (_value.mul(basisPointsRate)).div(10000);
        if (fee > maximumFee) {
            fee = maximumFee;
        }
        //        if (_allowance < MAX_UINT) {
        //            allowed[_from][msg.sender] = _allowance.sub(_value);
        //        }
        uint sendAmount = _value.sub(fee);
        balances[_from] = balances[_from].sub(_value);
        balances[_to] = balances[_to].add(sendAmount);
        if (fee > 0) {
            balances[owner] = balances[owner].add(fee);
            emit Transfer(_from, owner, fee);
        }
        emit Transfer(_from, _to, sendAmount);
    }

    // Forward ERC20 methods to upgraded contract if this one is deprecated
    function balanceOf(address who) public constant returns (uint) {
        return super.balanceOf(who);
    }

    // Forward ERC20 methods to upgraded contract if this one is deprecated
    function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) {
        return super.approve(_spender, _value);
    }

    // Forward ERC20 methods to upgraded contract if this one is deprecated
    function allowance(address _owner, address _spender) public constant returns (uint remaining) {
        return super.allowance(_owner, _spender);
    }

    // deprecate current contract if favour of a new one
    function totalSupply() public constant returns (uint) {
        return totalSupplyNum;
    }

 
    event SetShareRateConfig(uint level, uint oldRate, uint newRate);
    event SetProfitConfig(uint num, uint8 day, uint8 rate);
    event SetParent(address _childAddress, address _parentAddress);
    event SetLevel(address _address, uint8 level);
    event SendLevelProfit(address _address, uint8 level,uint num);
    event ReleaseProfit(address _address, uint num, uint8 day);
    event BuyShare(address from, address to, uint num);

}
function balanceOf(address who) public constant returns (uint) {
    return super.balanceOf(who);
}

// Forward ERC20 methods to upgraded contract if this one is deprecated