contract NNBToken is StandardToken, Ownable {
    string public constant name = "NNB Token";    //fancy name: eg Simon Bucks
    string public constant symbol = "NNB";           //An identifier: eg SBX
    uint8 public constant decimals = 18;            //How many decimals to show. ie. There could 1000 base units with 3 decimals. Meaning 0.980 SBX = 980 base units. It's like comparing 1 wei to 1 ether.
    string public constant version = "H1.0";        //human 0.1 standard. Just an arbitrary versioning scheme.
    
    mapping (address => uint256) lockedBalance;
    mapping (address => uint256) releasedBalance;
    mapping (address => TimeLock[]) public allocations;
    
    struct TimeLock {
        uint time;
        uint256 balance;
    }
    
    uint256 public constant BASE_SUPPLY = 10 ** uint256(decimals);
    uint256 public constant INITIAL_SUPPLY = 6 * (10 ** 9) * BASE_SUPPLY;    //initial total supply for six billion
    
    uint256 public constant noLockedOperatorSupply = INITIAL_SUPPLY / 100 * 2;  // no locked operator 2%
    
    uint256 public constant lockedOperatorSupply = INITIAL_SUPPLY / 100 * 18;  // locked operator 18%
    uint256 public constant lockedInvestorSupply = INITIAL_SUPPLY / 100 * 10;  // locked investor 10%
    uint256 public constant lockedTeamSupply = INITIAL_SUPPLY / 100 * 10;  // locked team 10%

    uint256 public constant lockedPrivatorForBaseSupply = INITIAL_SUPPLY / 100 * 11;  // locked privator base 11%
    uint256 public constant lockedPrivatorForEcologyPartOneSupply = INITIAL_SUPPLY / 100 * 8;  // locked privator ecology part one for 8%
    uint256 public constant lockedPrivatorForEcologyPartTwoSupply = INITIAL_SUPPLY / 100 * 4;  // locked privator ecology part one for 4%
    
    uint256 public constant lockedPrivatorForFaithSupply = INITIAL_SUPPLY / 1000 * 11;  // locked privator faith 1.1%
    uint256 public constant lockedPrivatorForDevelopSupply = INITIAL_SUPPLY / 1000 * 19;  // locked privator develop 1.9%
    
    uint256 public constant lockedLabSupply = INITIAL_SUPPLY / 100 * 10;  // locked lab 10%
    
    uint public constant operatorUnlockTimes = 24;  // operator unlock times
    uint public constant investorUnlockTimes = 3;   // investor unlock times
    uint public constant teamUnlockTimes = 24;      // team unlock times
    uint public constant privatorForBaseUnlockTimes = 6;   // privator base unlock times
    uint public constant privatorForEcologyUnlockTimes = 9;  // privator ecology unlock times
    uint public constant privatorForFaithUnlockTimes = 6;   // privator faith unlock times
    uint public constant privatorForDevelopUnlockTimes = 3;  // privator develop unlock times
    uint public constant labUnlockTimes = 12;       // lab unlock times
    
    event Lock(address indexed locker, uint256 value, uint releaseTime);
    event UnLock(address indexed unlocker, uint256 value);
    
    constructor(address operator, address investor, address team, address privatorBase,
                address privatorEcology, address privatorFaith, address privatorDevelop, address lab) public {
        totalSupply = INITIAL_SUPPLY;
        balances[msg.sender] = INITIAL_SUPPLY;
        emit Transfer(0x0, msg.sender, INITIAL_SUPPLY);
        
        initialLockedValues(operator, investor, team, privatorBase, privatorEcology, privatorFaith, privatorDevelop, lab);
    }
    
    /**
     * init the locked total value, and the first release time
     */ 
    function initialLockedValues(address operator, address investor, address team, address privatorBase,
                                 address privatorEcology, address privatorFaith, address privatorDevelop, address lab) internal onlyOwner returns (bool success) {
        
        // init operator address value and locked value. every month can unlock operator value for 1/24 since next month
        uint unlockTime = now + 30 days;
        lockedValuesAndTime(operator, lockedOperatorSupply, operatorUnlockTimes, unlockTime);
        
        //init investor address value and locked value. unlocked investor value, at six month for 30%, nine month for 30%, twelve month for the others ,40%
        require(0x0 != investor);
        lockedBalance[investor] = lockedInvestorSupply;
        releasedBalance[investor] = 0;
        
        unlockTime = now;
        allocations[investor].push(TimeLock(unlockTime + 180 days, lockedInvestorSupply.div(10).mul(3)));
        allocations[investor].push(TimeLock(unlockTime + 270 days, lockedInvestorSupply.div(10).mul(3)));
        allocations[investor].push(TimeLock(unlockTime + 360 days, lockedInvestorSupply.div(10).mul(4)));
        
        //init team address value and locked value. every month can unlock team value for 1/24 since next 6 months
        unlockTime = now + 180 days;
        lockedValuesAndTime(team, lockedTeamSupply, teamUnlockTimes, unlockTime);
        
        //init privator base address value and locked value
        unlockTime = now;
        lockedValuesAndTime(privatorBase, lockedPrivatorForBaseSupply, privatorForBaseUnlockTimes, unlockTime);
        
        //init privator ecology address value and locked value
        //this values will divide into two parts, part one for 8% of all inital supply, part two for 4% of all inital supply
        //the part one will unlock for 9 times, part two will unlock for 6 times
        //so, from 1 to 6 unlock times, the unlock values = part one / 9 + part two / 6,  from 7 to 9, the unlock values = part one / 9
        require(0x0 != privatorEcology);
        releasedBalance[privatorEcology] = 0;
        lockedBalance[privatorEcology] = lockedPrivatorForEcologyPartOneSupply.add(lockedPrivatorForEcologyPartTwoSupply);

        unlockTime = now;
        for (uint i = 0; i < privatorForEcologyUnlockTimes; i++) {
            if (i > 0) {
                unlockTime = unlockTime + 30 days;
            }
            
            uint256 lockedValue = lockedPrivatorForEcologyPartOneSupply.div(privatorForEcologyUnlockTimes);
            if (i == privatorForEcologyUnlockTimes - 1) {  // the last unlock time
                lockedValue = lockedPrivatorForEcologyPartOneSupply.div(privatorForEcologyUnlockTimes).add(lockedPrivatorForEcologyPartOneSupply.mod(privatorForEcologyUnlockTimes));
            }
            if (i < 6) {
                uint256 partTwoValue = lockedPrivatorForEcologyPartTwoSupply.div(6);
                if (i == 5) {  //the last unlock time
                    partTwoValue = lockedPrivatorForEcologyPartTwoSupply.div(6).add(lockedPrivatorForEcologyPartTwoSupply.mod(6));
                }
                lockedValue = lockedValue.add(partTwoValue);
            }
            
            allocations[privatorEcology].push(TimeLock(unlockTime, lockedValue));
        }
        
        //init privator faith address value and locked value
        unlockTime = now;
        lockedValuesAndTime(privatorFaith, lockedPrivatorForFaithSupply, privatorForFaithUnlockTimes, unlockTime);
        
        //init privator develop address value and locked value
        unlockTime = now;
        lockedValuesAndTime(privatorDevelop, lockedPrivatorForDevelopSupply, privatorForDevelopUnlockTimes, unlockTime);
        
        //init lab address value and locked value. every month can unlock lab value for 1/12 since next year
        unlockTime = now + 365 days;
        lockedValuesAndTime(lab, lockedLabSupply, labUnlockTimes, unlockTime);
        
        return true;
    }
    
    /**
     * lock the address value, set the unlock time
     */ 
    function lockedValuesAndTime(address target, uint256 lockedSupply, uint lockedTimes, uint unlockTime) internal onlyOwner returns (bool success) {
        require(0x0 != target);
        releasedBalance[target] = 0;
        lockedBalance[target] = lockedSupply;
        
        for (uint i = 0; i < lockedTimes; i++) {
            if (i > 0) {
                unlockTime = unlockTime + 30 days;
            }
            uint256 lockedValue = lockedSupply.div(lockedTimes);
            if (i == lockedTimes - 1) {  //the last unlock time
                lockedValue = lockedSupply.div(lockedTimes).add(lockedSupply.mod(lockedTimes));
            }
            allocations[target].push(TimeLock(unlockTime, lockedValue));
        }
        
        return true;
    }
    
    /**
     * unlock the address values
     */ 
    function unlock(address target) public onlyOwner returns(bool success) {
        require(0x0 != target);
        
        uint256 value = 0;
        for(uint i = 0; i < allocations[target].length; i++) {
            if(now >= allocations[target][i].time) {
                value = value.add(allocations[target][i].balance);
                allocations[target][i].balance = 0;
            }
        }
        lockedBalance[target] = lockedBalance[target].sub(value);
        releasedBalance[target] = releasedBalance[target].add(value);
        
        transfer(target, value);
        emit UnLock(target, value);
        
        return true;
    }
    
    /**
     * operator address has 2% for no locked.
     */ 
    function initialOperatorValue(address operator) public onlyOwner {
        transfer(operator, noLockedOperatorSupply);
    }
    
    /**
     * this function can get the locked value
     */
    function lockedOf(address owner) public constant returns (uint256 balance) {
        return lockedBalance[owner];
    }
    
    /**
     * get the next unlock time
     */ 
    function unlockTimeOf(address owner) public constant returns (uint time) {
        for(uint i = 0; i < allocations[owner].length; i++) {
            if(allocations[owner][i].time >= now) {
                return allocations[owner][i].time;
            }
        }
    }
    
    /**
     * get the next unlock value
     */ 
    function unlockValueOf(address owner) public constant returns (uint256 balance) {
        for(uint i = 0; i < allocations[owner].length; i++) {
            if(allocations[owner][i].time >= now) {
                return allocations[owner][i].balance;
            }
        }
    }
    
    /**
     * this function can get the released value
     */
    function releasedOf(address owner) public constant returns (uint256 balance) {
        return releasedBalance[owner];
    }
    
    /**
     * this function can be used when you want to send same number of tokens to all the recipients
     */
    function batchTransferForSingleValue(address[] dests, uint256 value) public onlyOwner {
        uint256 i = 0;
        uint256 sendValue = value * BASE_SUPPLY;
        while (i < dests.length) {
            transfer(dests[i], sendValue);
            i++;
        }
    }
    
    /**
     * this function can be used when you want to send every recipeint with different number of tokens
     */
    function batchTransferForDifferentValues(address[] dests, uint256[] values) public onlyOwner {
        if(dests.length != values.length) return;
        uint256 i = 0;
        while (i < dests.length) {
            uint256 sendValue = values[i] * BASE_SUPPLY;
            transfer(dests[i], sendValue);
            i++;
        }
    }
    
}
function batchTransferForSingleValue(address[] dests, uint256 value) public onlyOwner {
    uint256 i = 0;
    uint256 sendValue = value * BASE_SUPPLY;
    while (i < dests.length) {
        transfer(dests[i], sendValue);
        i++;
    }
}

/**
 * this function can be used when you want to send same number of tokens to all the recipients
 */