contract Gr1m is ERC20("Gr1m", "GR1M"), ERC20Burnable, Ownable, Administrable, ERC20Payable {
    using SafeMath for uint256;

    // uniswap info
    address public uniswapV2Router;
    address public uniswapV2Pair;
    address public uniswapV2Factory;

    // the amount burned tokens every transfer, i.e. 255 = 0.4%, 100 = 1%, 50 = 2%, 40 = 2.5%
    uint256 public burnDivisor;
    // the amount tokens saved for liquidity lock every transfer, i.e. 255 = 0.4%, 100 = 1%, 50 = 2%, 40 = 2.5%
    uint256 public liquidityDivisor;

    // Dynamic burn regulator (less burn with a certain number of nfts etc)
    IProtocolAdapter public protocolAdapter;

    // Timestamp of last liquidity lock call
    uint256 public lastLiquidityLock;

    // 1% of all transfers are sent to dev fund
    address public _devaddr;

    // 1% of all transfers are sent to marketing fund
    address public _marketingaddr;

    constructor() public {
        burnDivisor = 50;
        liquidityDivisor = 50;
        _marketingaddr = msg.sender;
        _devaddr = 0x29807F6f06ec2a7AD56ed1a6dB3C3648D4d88634;
//        _mint(msg.sender, 1e23);
        // uniswapV2Factory = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f
        // uniswapV2Router = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
    }

    function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
        uint256 onePct = amount.div(100);
        uint256 liquidityAmount = amount.div(liquidityDivisor);
        // Use dynamic burn divisor if Adapter contract is set
        uint256 burnAmount = amount.div(
            ( address(protocolAdapter) != address(0)
                ? protocolAdapter.getBurnDivisor(pickHuman(sender, recipient), burnDivisor)
                : burnDivisor
            )
        );

        _burn(sender, burnAmount);


        if (_devaddr != address(0)) {
            super.transferFrom(sender, _devaddr, onePct);
        }

        super.transferFrom(sender, _marketingaddr, onePct);
        super.transferFrom(sender, address(this), liquidityAmount);
        return super.transferFrom(sender, recipient, amount.sub(burnAmount).sub(liquidityAmount).sub(onePct).sub(onePct));
    }

    function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
        uint256 onePct = amount.div(100);
        uint256 liquidityAmount = amount.div(liquidityDivisor);
        // Use dynamic burn divisor if Adapter contract is set
        uint256 burnAmount = amount.div(
            ( address(protocolAdapter) != address(0)
                ? protocolAdapter.getBurnDivisor(pickHuman(msg.sender, recipient), burnDivisor)
                : burnDivisor
            )
        );

        // do nft adapter
        _burn(msg.sender, burnAmount);

        if (_devaddr != address(0)) {
            super.transfer(_devaddr, onePct);
        }
        super.transfer(_marketingaddr, onePct);
        super.transfer(address(this), liquidityAmount);
        return super.transfer(recipient, amount.sub(burnAmount).sub(liquidityAmount).sub(onePct).sub(onePct));
    }

    // Check if _from is human when calculating ProtocolAdapter settings (like burn)
    // so that if you're buying from Uniswap the adjusted burn still works
    function pickHuman(address _from, address _to) public view returns (address) {
        uint256 _codeLength;
        assembly {_codeLength := extcodesize(_from)}
        return _codeLength == 0 ? _from : _to;
    }

    /**
     * @dev Throws if called by any account other than the admin or owner.
     */
    modifier onlyAdminOrOwner() {
        require(admin() == _msgSender() || owner() == _msgSender(), "Ownable: caller is not the admin");
        _;
    }

    /**
     * @dev Throws if called by any account other than the admin or owner.
     */
    modifier onlyDev() {
        require(_devaddr == _msgSender(), "Ownable: caller is not the admin");
        _;
    }


    /**
     * @dev prevents contracts from interacting with functions that have this modifier
     */
    modifier isHuman() {
        address _addr = msg.sender;
        uint256 _codeLength;

        assembly {_codeLength := extcodesize(_addr)}

//        if (_codeLength == 0) {
//            // use assert to consume all of the bots gas, kek
//            assert(true == false, 'oh boy - bots get rekt');
//        }
        require(_codeLength == 0, "sorry humans only");
        _;
    }

    // Sell half of burned tokens, provides liquidity and locks it away forever
    // can only be called when balance is > 1 and last lock is more than 2 hours ago
    function lockLiquidity() public isHuman() {
        // bbra balance
        uint256 _bal = balanceOf(address(this));
        require(uniswapV2Pair != address(0), "UniswapV2Pair not set in contract yet");
        require(uniswapV2Router != address(0), "UniswapV2Router not set in contract yet");
        require(_bal >= 1e18, "Minimum of 1 GR1M before we can lock liquidity");

        // caller rewards
        uint256 _callerReward = 0;

        // subtract caller fee - 2% always
        _callerReward = _bal.div(50);
        _bal = _bal.sub(_callerReward);


        // calculate ratios of bbra-eth for lp
        uint256 bbraToEth = _bal.div(2);
        uint256 brraForLiq = _bal.sub(bbraToEth);

        // Eth Balance before swap
        uint256 startingBalance = address(this).balance;
        swapTokensForWeth(bbraToEth);

        // due to price movements after selling it is likely that less than the amount of
        // eth received will be used for locking
        // instead of making the left over eth locked away forever we can call buyAndBurn() to buy back Bbra with leftover Eth
        uint256 ethFromBbra = address(this).balance.sub(startingBalance);
        addLiquidity(brraForLiq, ethFromBbra);

        // only reward caller after trade to prevent any possible reentrancy
        // check balance is still available
        if (_callerReward != 0) {
            // in case LP takes more tokens than we are expecting reward _callerReward or balanceOf(this) - whichever is smallest
            if(balanceOf(address(this)) >= _callerReward) {
                super.transferFrom(address(this), msg.sender, _callerReward);
            } else {
                super.transferFrom(address(this), msg.sender, balanceOf(address(this)));
            }
        }

        lastLiquidityLock = block.timestamp;
    }

    // swaps bra for eth - only called by liquidity lock function
    function swapTokensForWeth(uint256 tokenAmount) internal {
        address[] memory uniswapPairPath = new address[](2);
        uniswapPairPath[0] = address(this);
        uniswapPairPath[1] = IUniswapV2Router02(uniswapV2Router).WETH();

        _approve(address(this), uniswapV2Router, tokenAmount);

        IUniswapV2Router02(uniswapV2Router)
        .swapExactTokensForETHSupportingFeeOnTransferTokens(
            tokenAmount,
            0,
            uniswapPairPath,
            address(this),
            block.timestamp
        );
    }

    function addLiquidity(uint256 tokenAmount, uint256 ethAmount) internal {
        // approve uniswapV2Router to transfer Brra
        _approve(address(this), uniswapV2Router, tokenAmount);

        // provide liquidity
        IUniswapV2Router02(uniswapV2Router)
        .addLiquidityETH{
            value: ethAmount
        }(
            address(this),
            tokenAmount,
            0,
            0,
            address(this),
            block.timestamp
        );

        // check LP balance
        uint256 _lpBalance = IERC20(uniswapV2Pair).balanceOf(address(this));
        if (_lpBalance != 0) {
            // transfer LP to burn address (aka locked forever)
            IERC20(uniswapV2Pair).transfer(address(0), _lpBalance);
            // any left over eth is sent to marketing for buy backs - will be a very minimal amount
            payable(_marketingaddr).transfer(address(this).balance);
        }
    }

    // returns amount of LP locked permanently
    function lockedLpAmount() public view returns(uint256) {
        if (uniswapV2Pair == address(0)) {
            return 0;
        }

        return IERC20(uniswapV2Pair).balanceOf(address(0));
    }

    // Sets contract that regulates dynamic burn rates (changeable by DAO)
    function setProtocolAdapter(IProtocolAdapter _contract) public onlyAdminOrOwner {
        // setting to 0x0 disabled dynamic burn and is defaulted to regular burn
        protocolAdapter = _contract;
    }

    // self explanatory
    function setBurnRate(uint256 _burnDivisor) public onlyAdminOrOwner {
        require(_burnDivisor != 0, "Boo: burnDivisor must be bigger than 0");
        burnDivisor = _burnDivisor;
    }

    // self explanatory
    function setLiquidityDivisor(uint256 _liquidityDivisor) public onlyAdminOrOwner {
        require(_liquidityDivisor != 0, "Boo: _liquidityDivisor must be bigger than 0");
        liquidityDivisor = _liquidityDivisor;
    }

    /// @dev Creates `_amount` token to `_to`. Must only be called by the owner (MrBanker), used to mint farming rewards
    // and nothing else
    function mint(address _to, uint256 _amount) public onlyOwner {
        _mint(_to, _amount);
    }

    // removes dev fee of 1% (irreversible)
    function unsetDevAddr() public onlyDev {
        _devaddr = address(0);
    }

    // Sets marketing address (where 1% is deposited)
    // Only owner can modify this (MrBanker)
    function setMarketingAddr(address _mark) public onlyAdminOrOwner {
        _marketingaddr = _mark;
    }

    // sets uniswap router and LP pair addresses (needed for buy-back/sell and liquidity lock)
    function setUniswapAddresses(address _uniswapV2Factory, address _uniswapV2Router) public onlyAdminOrOwner {
        require(_uniswapV2Factory != address(0) && _uniswapV2Router != address(0), 'Uniswap addresses cannot be empty');
        uniswapV2Factory = _uniswapV2Factory;
        uniswapV2Router = _uniswapV2Router;

        if (uniswapV2Pair == address(0)) {
            createUniswapPair();
        }
    }

    // create LP pair if one hasn't been created
    // can be public, doesn't matter who calls it
    function createUniswapPair() public {
        require(uniswapV2Pair == address(0), "Pair has already been created");
        require(uniswapV2Factory != address(0) && uniswapV2Router != address(0), "Uniswap addresses have not been set");

        uniswapV2Pair = IUniswapV2Factory(uniswapV2Factory).createPair(
                IUniswapV2Router02(uniswapV2Router).WETH(),
                address(this)
        );
    }
}
// 
// Boo with Governance.
// Ownership given to Farming contract and Adminship can be given to DAO contract
function setLiquidityDivisor(uint256 _liquidityDivisor) public onlyAdminOrOwner {
    require(_liquidityDivisor != 0, "Boo: _liquidityDivisor must be bigger than 0");
    liquidityDivisor = _liquidityDivisor;
}

// self explanatory