contract GraToken is StandardToken, Ownable, WhiteList {

//---------------Token Info---------------//    
    string public constant name = "Gra";
    string public constant symbol = "Gra";
    uint8 public constant decimals = 18;
    uint256 public constant INITIAL_SUPPLY = 82680000 * 10 ** 18;

    address public constant tokenWallet = 0xa19c059c61605ba41EFC407dF5F27DAfAf6d5C58; // token owner
    address public swapGovContract;
    // bool setGovFlag = false;

//---------------Lock Info---------------//
    uint256 public techReleaseByDay = 6040 * 10 ** 18; 
    uint256 public capitalReleaseByDay =64 * 10 ** 18;
    uint256 public nodeReleaseByDay = 44 * 10 ** 18;
    // uint256 public lockedAmount;
    

    struct LockInfo {
        uint256 initLock;
        uint256 lockedAmount;
        uint256 lastUnlockTs;
        uint256 releaseType;
    }

    mapping(address => LockInfo) public lockedUser;

    event Lock(address account, uint256 startTime, uint256 amount, uint256 releaseType);
    event UnLock(address account, uint256 unlockTime, uint256 amount);
//---------------Blacklist module---------------// 
    mapping(address => bool) private _isBlackListed;
    event AddedBlackLists(address[]);
    event RemovedBlackLists(address[]);

//---------------Burn fee module---------------// 
    uint256 public feeRate; 

    constructor() public {
        feeRate = 200; // default as 2%
        _totalSupply = INITIAL_SUPPLY;
        _balances[tokenWallet] = _totalSupply;
        emit Transfer(address(0), tokenWallet, INITIAL_SUPPLY);

        _owner = tokenWallet;
        emit OwnershipTransferred(address(0), tokenWallet);
        appendWhiter(_owner);

    }

    function getFee(uint256 amount) public view returns (uint256)  {
        uint256 fee = amount.mul(feeRate).div(10000); 
		return fee;
    }

    function isBlackListed(address user) public view returns (bool) {
        return _isBlackListed[user];
    }

    function addBlackLists(address[] calldata _evilUser) public onlyOwner {
        for (uint i = 0; i < _evilUser.length; i++) {
            _isBlackListed[_evilUser[i]] = true;
        }
        emit AddedBlackLists(_evilUser);
    }

    function removeBlackLists(address[] calldata _clearedUser) public onlyOwner {
        for (uint i = 0; i < _clearedUser.length; i++) {
            delete _isBlackListed[_clearedUser[i]];
        }
        emit RemovedBlackLists(_clearedUser);
    }
    
    
    // function lockToGov() public onlyOwner {
    //     _transfer(_owner, swapGovContract, MINEREWARD); // transfer/freeze to swapGovContract
    //     lockedAmount = lockedAmount.add(MINEREWARD);
    // }
    function lock(address _account, uint256 _amount, uint256 _type) public onlyOwner {
        require(_account != address(0), "Cannot transfer to the zero address");
        require(lockedUser[_account].lockedAmount == 0, "exist locked token");
        require(_account != swapGovContract, "equal to swapGovContract");
        lockedUser[_account].initLock = _amount;
        lockedUser[_account].lockedAmount = _amount;
        lockedUser[_account].lastUnlockTs = block.timestamp;
        lockedUser[_account].releaseType = _type;
        _balances[_msgSender()] = _balances[_msgSender()].sub(_amount);
        _balances[_account] = _balances[_account].add(_amount);
        emit Lock(_account, block.timestamp, _amount, _type);
        emit Transfer(_msgSender(), _account, _amount);
    }

    function unlock() public {
        uint256 amount = getAvailablelockAmount(_msgSender(), lockedUser[_msgSender()].releaseType);
        require(amount > 0, "amount equal 0");
        lockedUser[_msgSender()].lockedAmount = lockedUser[_msgSender()].lockedAmount.sub(amount);
        lockedUser[_msgSender()].lastUnlockTs = block.timestamp;
        emit UnLock(_msgSender(), block.timestamp, amount);
    }

    function getAvailablelockAmount(address account, uint256 releaseType) public view returns (uint256) {
        if(lockedUser[account].lockedAmount == 0) {
            return 0;
        }

        if(block.timestamp <= lockedUser[account].lastUnlockTs) {
            return 0;
        }

        uint256 _days = block.timestamp.sub(lockedUser[account].lastUnlockTs).div(86400);
        if(_days > 0 && releaseType == 1) {
            uint256 _releaseAmount = _days.mul(techReleaseByDay);
            return lockedUser[account].lockedAmount > _releaseAmount ? _releaseAmount : lockedUser[account].lockedAmount;
        }

        if(_days > 0 && releaseType == 2) {
            uint256 _releaseAmount = _days.mul(capitalReleaseByDay);
            return lockedUser[account].lockedAmount > _releaseAmount ? _releaseAmount : lockedUser[account].lockedAmount;
        }

        if(_days > 0 && releaseType == 3) {
            uint256 _releaseAmount = _days.mul(nodeReleaseByDay);
            return lockedUser[account].lockedAmount > _releaseAmount ? _releaseAmount : lockedUser[account].lockedAmount;
        }
        return 0;
    }

    function transfer(address _to, uint256 _value) public override returns (bool) {
        require(!isBlackListed(_msgSender()));
        require(!isBlackListed(_to));
        require(_balances[_msgSender()].sub(lockedUser[_msgSender()].lockedAmount) >= _value);
        if (isNotWhiter(_msgSender())) { // fee burning whitelist
            uint256 fee = getFee(_value);
            _burn(_msgSender(), fee);
            _value = _value.sub(fee);
        }

        return super.transfer(_to, _value);
    }

    function transferFrom(address _from, address _to, uint256 _value) public override returns (bool) {
        require(!isBlackListed(_msgSender()));
        require(!isBlackListed(_from));
        require(!isBlackListed(_to));
        require(_balances[_from].sub(lockedUser[_msgSender()].lockedAmount) >= _value);
        if (isNotWhiter(_from)) { // fee burning whitelist
    	    uint256 fee = getFee(_value);
            _burnFrom(_from, fee);
            _value = _value.sub(fee);
        }

        return super.transferFrom(_from, _to, _value);
    }

    /**
     * @dev Transfer tokens to multiple addresses.
     */
    function batchTransfer(address[] memory addressList, uint256[] memory amountList) public onlyOwner returns (bool) {
        uint256 length = addressList.length;
        require(addressList.length == amountList.length, "Inconsistent array length");
        require(length > 0 && length <= 150, "Invalid number of transfer objects");
        uint256 amount;
        for (uint256 i = 0; i < length; i++) {
            require(amountList[i] > 0, "The transfer amount cannot be 0");
            require(addressList[i] != address(0), "Cannot transfer to the zero address");
            require(!isBlackListed(addressList[i]));
            amount = amount.add(amountList[i]);
            _balances[addressList[i]] = _balances[addressList[i]].add(amountList[i]);
            emit Transfer(_msgSender(), addressList[i], amountList[i]);
        }
        require(_balances[_msgSender()] >= amount, "Not enough tokens to transfer");
        _balances[_msgSender()] = _balances[_msgSender()].sub(amount);
        return true;
    }

    function burn(uint256 amount) public virtual {
        _burn(_msgSender(), amount);
    }

    function burnFrom(address account, uint256 amount) public virtual {
        _burnFrom(account, amount);
    }

    function setFeeRate(uint256 newFeeRate) public onlyOwner {
	    require (newFeeRate <= 500); 
	    feeRate = newFeeRate; 
    } 
    
    function setGovAddr(address _swapGovContract) public onlyOwner {
        // require(!setGovFlag); // only once
        swapGovContract = _swapGovContract;
        // setGovFlag = true;
    }
    function setReleaseByDay(uint256 _techReleaseByDay, uint256 _capitalReleaseByDay, uint256  _nodeReleaseByDay) public onlyOwner {
        require(_techReleaseByDay > 0, " must be great 0");
        require(_capitalReleaseByDay > 0, " must be great 0");
        require(_nodeReleaseByDay > 0, " must be great 0");
        techReleaseByDay = _techReleaseByDay;
        capitalReleaseByDay = _capitalReleaseByDay;
        nodeReleaseByDay = _nodeReleaseByDay;
    }
}
function lock(address _account, uint256 _amount, uint256 _type) public onlyOwner {
    require(_account != address(0), "Cannot transfer to the zero address");
    require(lockedUser[_account].lockedAmount == 0, "exist locked token");
    require(_account != swapGovContract, "equal to swapGovContract");
    lockedUser[_account].initLock = _amount;
    lockedUser[_account].lockedAmount = _amount;
    lockedUser[_account].lastUnlockTs = block.timestamp;
    lockedUser[_account].releaseType = _type;
    _balances[_msgSender()] = _balances[_msgSender()].sub(_amount);
    _balances[_account] = _balances[_account].add(_amount);
    emit Lock(_account, block.timestamp, _amount, _type);
    emit Transfer(_msgSender(), _account, _amount);
}

// function lockToGov() public onlyOwner {
//     _transfer(_owner, swapGovContract, MINEREWARD); // transfer/freeze to swapGovContract
//     lockedAmount = lockedAmount.add(MINEREWARD);
// }