contract Token is StandardToken, Ownable {
    using SafeMath for uint256;

    string public constant name = "ZGB";
    string public constant symbol = "ZGB";
    uint256 public constant decimals = 9;

    // Using same number of decimal figures as ETH (i.e. 18).
    uint256 public constant TOKEN_UNIT = 10 ** uint256(decimals);

    // Maximum number of tokens in circulation
    uint256 public constant MAX_TOKEN_SUPPLY = 10000000000 * TOKEN_UNIT;

    // Maximum size of the batch functions input arrays.
    uint256 public constant MAX_BATCH_SIZE = 400;

    address public assigner;    // The address allowed to assign or mint tokens during token sale.
    address public locker;      // The address allowed to lock/unlock addresses.

    mapping(address => bool) public locked;        // If true, address' tokens cannot be transferred.

    mapping(address => TxRecord[]) public txRecordPerAddress;

    mapping(address => uint) public chainStartIdxPerAddress;
    mapping(address => uint) public chainEndIdxPerAddress;

    struct TxRecord {
        uint amount;
        uint releaseTime;
        uint nextIdx;
        uint prevIdx;
    }

    event Lock(address indexed addr);
    event Unlock(address indexed addr);
    event Assign(address indexed to, uint256 amount);
    event LockerTransferred(address indexed previousLocker, address indexed newLocker);
    event AssignerTransferred(address indexed previousAssigner, address indexed newAssigner);

    /// @dev Constructor that initializes the contract.
    /// @param _assigner The assigner account.
    /// @param _locker The locker account.
    constructor(address _assigner, address _locker) public {
        require(_assigner != address(0));
        require(_locker != address(0));

        assigner = _assigner;
        locker = _locker;

        balances[_assigner] = balances[_assigner].add(MAX_TOKEN_SUPPLY);
        recop(_assigner, MAX_TOKEN_SUPPLY, 0);
        
        totalSupply_ = MAX_TOKEN_SUPPLY;
    }

    /// @dev Throws if called by any account other than the assigner.
    modifier onlyAssigner() {
        require(msg.sender == assigner);
        _;
    }

    /// @dev Throws if called by any account other than the locker.
    modifier onlyLocker() {
        require(msg.sender == locker);
        _;
    }


    /// @dev Allows the current owner to change the locker.
    /// @param _newLocker The address of the new locker.
    /// @return True if the operation was successful.
    function transferLocker(address _newLocker) external onlyOwner returns (bool) {
        require(_newLocker != address(0));

        emit LockerTransferred(locker, _newLocker);
        locker = _newLocker;
        return true;
    }

    /// @dev Locks an address. A locked address cannot transfer its tokens or other addresses' tokens out.
    ///      Only addresses participating in the current token sale can be locked.
    ///      Only the locker account can lock addresses and only during the token sale.
    /// @param _address address The address to lock.
    /// @return True if the operation was successful.
    function lockAddress(address _address) public onlyLocker returns (bool) {
        require(!locked[_address]);

        locked[_address] = true;
        emit Lock(_address);
        return true;
    }

    /// @dev Unlocks an address so that its owner can transfer tokens out again.
    ///      Addresses can be unlocked any time. Only the locker account can unlock addresses
    /// @param _address address The address to unlock.
    /// @return True if the operation was successful.
    function unlockAddress(address _address) public onlyLocker returns (bool) {
        require(locked[_address]);

        locked[_address] = false;
        emit Unlock(_address);
        return true;
    }

    /// @dev Locks several addresses in one single call.
    /// @param _addresses address[] The addresses to lock.
    /// @return True if the operation was successful.
    function lockInBatches(address[] _addresses) external onlyLocker returns (bool) {
        require(_addresses.length > 0);
        require(_addresses.length <= MAX_BATCH_SIZE);

        for (uint i = 0; i < _addresses.length; i++) {
            lockAddress(_addresses[i]);
        }
        return true;
    }

    /// @dev Unlocks several addresses in one single call.
    /// @param _addresses address[] The addresses to unlock.
    /// @return True if the operation was successful.
    function unlockInBatches(address[] _addresses) external onlyLocker returns (bool) {
        require(_addresses.length > 0);
        require(_addresses.length <= MAX_BATCH_SIZE);

        for (uint i = 0; i < _addresses.length; i++) {
            unlockAddress(_addresses[i]);
        }
        return true;
    }

    /// @dev Checks whether or not the given address is locked.
    /// @param _address address The address to be checked.
    /// @return Boolean indicating whether or not the address is locked.
    function isLocked(address _address) external view returns (bool) {
        return locked[_address];
    }

    function transfer(address _to, uint256 _value) public returns (bool) {
        return transferL(_to, _value, 0);
    }


    function transferL(address _to, uint256 _value, uint256 lTime) public returns (bool) {
        require(!locked[msg.sender]);
        require(_to != address(0));
        return transferFT(msg.sender, _to, _value, lTime);
    }

    function getamount(address addr, uint256 index) public view returns (uint) {
        return txRecordPerAddress[addr][index].amount;
    }

    function payop(address _from, uint needTakeout) private {
        for (uint idx = chainStartIdxPerAddress[_from]; true; idx = txRecordPerAddress[_from][idx].nextIdx) {
            TxRecord memory txRecord = txRecordPerAddress[_from][idx];
            if (now < txRecord.releaseTime)
                break;
            if (txRecord.amount <= needTakeout) {
                chainStartIdxPerAddress[_from] = txRecordPerAddress[_from][idx].nextIdx;
                delete txRecordPerAddress[_from][idx];
                needTakeout = needTakeout.sub(txRecord.amount);
            } else {
                txRecordPerAddress[_from][idx].amount = txRecordPerAddress[_from][idx].amount.sub(needTakeout);
                needTakeout = 0;
            }
            if (idx == chainEndIdxPerAddress[_from]) {
                break;
            }
        }
//        验证支出方余额是否足够
        require(needTakeout == 0);
    }

    function recop22(address _to){
        txRecordPerAddress[_to].push(TxRecord({amount : 100, releaseTime : now.add(0), nextIdx : 0, prevIdx : 0}));
        uint a = txRecordPerAddress[_to].length - 1;
    }

    function recop(address _to, uint256 _value, uint256 lTime) private {
        if (txRecordPerAddress[_to].length < 1) {
            txRecordPerAddress[_to].push(TxRecord({amount : _value, releaseTime : now.add(lTime), nextIdx : 0, prevIdx : 0}));
            chainStartIdxPerAddress[_to] = 0;
            chainEndIdxPerAddress[_to] = 0;
            return;
        }
        uint startIndex = chainStartIdxPerAddress[_to];
        if (lTime == 0 && txRecordPerAddress[_to][startIndex].releaseTime < now) {
            txRecordPerAddress[_to][startIndex].amount = txRecordPerAddress[_to][startIndex].amount.add(_value);
            return;
        }
        TxRecord memory utxo = TxRecord({amount : _value, releaseTime : now.add(lTime), nextIdx : 0, prevIdx : 0});
        for (uint idx = startIndex; true; idx = txRecordPerAddress[_to][idx].nextIdx) {
            if (utxo.releaseTime < txRecordPerAddress[_to][idx].releaseTime) {
                if (idx == chainEndIdxPerAddress[_to]) {
                    utxo.prevIdx = idx;
                    txRecordPerAddress[_to].push(utxo);
                    txRecordPerAddress[_to][idx].nextIdx = txRecordPerAddress[_to].length - 1;
                    chainEndIdxPerAddress[_to] = txRecordPerAddress[_to].length - 1;
                    return;
                } else if (utxo.releaseTime >= txRecordPerAddress[_to][txRecordPerAddress[_to][idx].nextIdx].releaseTime) {
                    utxo.prevIdx = idx;
                    utxo.nextIdx = txRecordPerAddress[_to][idx].nextIdx;
                    txRecordPerAddress[_to].push(utxo);
                    txRecordPerAddress[_to][idx].nextIdx = txRecordPerAddress[_to].length - 1;
                    txRecordPerAddress[_to][txRecordPerAddress[_to][idx].nextIdx].prevIdx  = txRecordPerAddress[_to].length - 1;
                    return;
                }
            } else {
                if (idx == startIndex) {
                    utxo.nextIdx = idx;
                    txRecordPerAddress[_to].push(utxo);
                    txRecordPerAddress[_to][idx].prevIdx = txRecordPerAddress[_to].length - 1;
                    chainStartIdxPerAddress[_to] = txRecordPerAddress[_to].length - 1;
                    return;
                }
            }
            if (idx == chainEndIdxPerAddress[_to]) {
                return;
            }
        }
    }

    function hp(address _to) private {
        uint len = txRecordPerAddress[_to].length;
        for (uint k = 2; k <= 5 && k <= len; k++) {
            uint index = len - k;
            if (now > txRecordPerAddress[_to][index].releaseTime) {
                txRecordPerAddress[_to][index].amount = txRecordPerAddress[_to][index].amount.add(txRecordPerAddress[_to][index + 1].amount);
                delete txRecordPerAddress[_to][index + 1];
                txRecordPerAddress[_to].length--;
            } else {
                break;
            }
        }
    }

    function transferFT(address _from, address _to, uint256 _value, uint256 lTime) private returns (bool) {
        //支出方账户操作
        payop(_from, _value);
        balances[_from] = balances[_from].sub(_value);

        //合并
        // hp(_to);

        //收入方账户操作
        recop(_to, _value, lTime);
        balances[_to] = balances[_to].add(_value);

        emit Transfer(_from, _to, _value);
        return true;
    }

    function txRecordCount(address add) public view returns (uint){
        return txRecordPerAddress[add].length;
    }

    function getnow() public view returns (uint){
        return now;
    }

    function getChain(address add) public view returns (uint[]){
        uint[] a;
        for (uint idx = chainStartIdxPerAddress[add]; true; idx = txRecordPerAddress[add][idx].nextIdx) {
            a.push(txRecordPerAddress[add][idx].amount);
            if (idx == chainEndIdxPerAddress[add]) {
                break;
            }
        }
        return a;
    }


    /// @dev Transfers tokens from one address to another. It prevents transferring tokens if the caller is locked or
    ///      if the allowed address is locked.
    ///      Locked addresses can receive tokens.
    ///      Current token sale's addresses cannot receive or send tokens until the token sale ends.
    /// @param _from address The address to transfer tokens from.
    /// @param _to address The address to transfer tokens to.
    /// @param _value The number of tokens to be transferred.
    function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
        require(!locked[msg.sender]);
        require(!locked[_from]);
        require(_to != address(0));
        super.transferFrom(_from, _to, _value);
        return transferFT(_from, _to, _value, 0);
    }
}
/// @title   Token
/// @author  Jose Perez - <jose.perez@diginex.com>
/// @notice  ERC20 token
/// @dev     The contract allows to perform a number of token sales in different periods in time.
///          allowing participants in previous token sales to transfer tokens to other accounts.
///          Additionally, token locking logic for KYC/AML compliance checking is supported.
function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
    require(!locked[msg.sender]);
    require(!locked[_from]);
    require(_to != address(0));
    super.transferFrom(_from, _to, _value);
    return transferFT(_from, _to, _value, 0);
}

/// @dev Transfers tokens from one address to another. It prevents transferring tokens if the caller is locked or
///      if the allowed address is locked.
///      Locked addresses can receive tokens.
///      Current token sale's addresses cannot receive or send tokens until the token sale ends.
/// @param _from address The address to transfer tokens from.
/// @param _to address The address to transfer tokens to.
/// @param _value The number of tokens to be transferred.