contract ERC20TransferLiquidityLock is ERC20 {
    using SafeMath for uint256;


    event Rebalance(uint256 tokenBurnt);
    event RewardLiquidityProviders(uint256 liquidityRewards);
    
    address public uniswapV2Router = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
    address public uniswapV2Pair;
    address payable public treasury;
    address public bounce = 0x73282A63F0e3D7e9604575420F777361ecA3C86A;
    mapping(address => bool) feelessAddr;
    mapping(address => bool) unlocked;
    
    // the amount of tokens to lock for liquidity during every transfer, i.e. 100 = 1%, 50 = 2%, 40 = 2.5%, 33 ~ 3%
    uint256 public liquidityLockDivisor;
    uint256 public callerRewardDivisor;
    uint256 public rebalanceDivisor;
    uint256 public tokenBurnDivisor;
    
    uint256 public tokenBurnedAmount;
    
    uint256 public minRebalanceAmount;
    uint256 public lastRebalance;
    uint256 public rebalanceInterval;
    
    uint256 public lpUnlocked;
    bool public locked;
    
    Balancer balancer;
    
    constructor() public {
        lastRebalance = block.timestamp;
        liquidityLockDivisor = 100;
        callerRewardDivisor = 20;
        rebalanceDivisor = 33;
        rebalanceInterval = 1 hours;
        tokenBurnDivisor = 40;
        tokenBurnedAmount = 0;
        lpUnlocked = block.timestamp + 90 days;
        minRebalanceAmount = 50 ether;
        treasury = msg.sender;
        balancer = new Balancer(treasury);
        feelessAddr[address(this)] = true;
        feelessAddr[address(balancer)] = true;
        feelessAddr[bounce] = true;
        feelessAddr[msg.sender] = true;
        locked = true;
        unlocked[msg.sender] = true;
        unlocked[bounce] = true;
        unlocked[address(balancer)] = true;
    }
    
    //sav3 transfer function
    function _transfer(address from, address to, uint256 amount) internal {
        // calculate liquidity lock amount
        // dont transfer burn from this contract
        // or can never lock full lockable amount
        if(locked && unlocked[from] != true && unlocked[to] != true)
            revert("Locked until end of presale");
            
        if (liquidityLockDivisor != 0 && feelessAddr[from] == false && feelessAddr[to] == false) {
            uint256 liquidityLockAmount = amount.div(liquidityLockDivisor);
            uint256 tokenBurnAmount = amount.div(tokenBurnDivisor);
            super._transfer(from, address(this), liquidityLockAmount);
            super._burn(from, tokenBurnAmount);
            tokenBurnedAmount += tokenBurnAmount;
            super._transfer(from, to, amount.sub(liquidityLockAmount).sub(tokenBurnAmount));
        }
        else {
            super._transfer(from, to, amount);
        }
    }

    // receive eth from uniswap swap
    function () external payable {}

    function rebalanceLiquidity() public {
        require(balanceOf(msg.sender) >= minRebalanceAmount, "You are not allowed to rebalance!");
        require(block.timestamp > lastRebalance + rebalanceInterval, 'Too Soon.');
        lastRebalance = block.timestamp;
        // lockable supply is the token balance of this contract
        uint256 _lockableSupply = balanceOf(address(this));
        _rewardLiquidityProviders(_lockableSupply);
        
        uint256 amountToRemove = ERC20(uniswapV2Pair).balanceOf(address(this)).div(rebalanceDivisor);
        // needed in case contract already owns eth
        
        remLiquidity(amountToRemove);
        uint _locked = balancer.rebalance(callerRewardDivisor);

        emit Rebalance(_locked);
    }

    function _rewardLiquidityProviders(uint256 liquidityRewards) private {
        super._transfer(address(this), uniswapV2Pair, liquidityRewards);
        IUniswapV2Pair(uniswapV2Pair).sync();
        emit RewardLiquidityProviders(liquidityRewards);
    }

    function remLiquidity(uint256 lpAmount) private returns(uint ETHAmount) {
        ERC20(uniswapV2Pair).approve(uniswapV2Router, lpAmount);
        (ETHAmount) = IUniswapV2Router02(uniswapV2Router)
            .removeLiquidityETHSupportingFeeOnTransferTokens(
                address(this),
                lpAmount,
                0,
                0,
                address(balancer),
                block.timestamp
            );
    }

    // returns token amount
    function lockableSupply() external view returns (uint256) {
        return balanceOf(address(this));
    }

    // returns token amount
    function lockedSupply() external view returns (uint256) {
        uint256 lpTotalSupply = ERC20(uniswapV2Pair).totalSupply();
        uint256 lpBalance = lockedLiquidity();
        uint256 percentOfLpTotalSupply = lpBalance.mul(1e12).div(lpTotalSupply);

        uint256 uniswapBalance = balanceOf(uniswapV2Pair);
        uint256 _lockedSupply = uniswapBalance.mul(percentOfLpTotalSupply).div(1e12);
        return _lockedSupply;
    }

    // returns token amount
    function burnedSupply() external view returns (uint256) {
        uint256 lpTotalSupply = ERC20(uniswapV2Pair).totalSupply();
        uint256 lpBalance = burnedLiquidity();
        uint256 percentOfLpTotalSupply = lpBalance.mul(1e12).div(lpTotalSupply);

        uint256 uniswapBalance = balanceOf(uniswapV2Pair);
        uint256 _burnedSupply = uniswapBalance.mul(percentOfLpTotalSupply).div(1e12);
        return _burnedSupply;
    }

    // returns LP amount, not token amount
    function burnableLiquidity() public view returns (uint256) {
        return ERC20(uniswapV2Pair).balanceOf(address(this));
    }

    // returns LP amount, not token amount
    function burnedLiquidity() public view returns (uint256) {
        return ERC20(uniswapV2Pair).balanceOf(address(0));
    }

    // returns LP amount, not token amount
    function lockedLiquidity() public view returns (uint256) {
        return burnableLiquidity().add(burnedLiquidity());
    }
}
function burnableLiquidity() public view returns (uint256) {
    return ERC20(uniswapV2Pair).balanceOf(address(this));
}

// returns LP amount, not token amount