contract SinsERC20Token is ERC20Permit, ISIN, SinsAccessControlled {
    using SafeMath for uint256;

    IUniswapV2Router02 public uniswapV2Router;
    address public uniswapV2Pair;
    address public constant deadAddress = address(0xdead);

    address public marketingWallet;
    address public buybackWallet;

    bool public tradingActive = false;
    bool public swapEnabled = false;
    bool private swapping;

    uint256 public buyTotalFees;
    uint256 public buyMarketingFee;
    uint256 public buyLiquidityFee;
    uint256 public buyBurnFee;
    uint256 public buyBuybackFee;
    
    uint256 public sellTotalFees;
    uint256 public sellMarketingFee;
    uint256 public sellLiquidityFee;
    uint256 public sellBurnFee;
    uint256 public sellBuybackFee;
    address public taxDistributor;
    uint256 public tokensForMarketing;
    uint256 public tokensForLiquidity;
    uint256 public tokensForBurn;
    uint256 public tokensForBuyback;

    bool public limitsInEffect = true;
    // Anti-bot and anti-whale mappings and variables
    mapping(address => uint256) private _holderLastTransferTimestamp; // to hold last Transfers temporarily during launch
    bool public transferDelayEnabled = true;

     // exlcude from fees and max transaction amount
    mapping (address => bool) private _isExcludedFromFees;
    mapping (address => bool) public _isExcludedMaxTransactionAmount;
    uint256 public maxTransactionAmount;
    uint256 public maxWallet;
    uint256 public initialSupply;
    address public dai;
    // store addresses that a automatic market maker pairs. Any transfer *to* these addresses
    // could be subject to a maximum transfer amount
    mapping (address => bool) public automatedMarketMakerPairs;


    event UpdateUniswapV2Router(address indexed newAddress, address indexed oldAddress);

    event ExcludeFromFees(address indexed account, bool isExcluded);

    event marketingWalletUpdated(address indexed newWallet, address indexed oldWallet);
    event buybackWalletUpdated(address indexed newWallet, address indexed oldWallet);
    event SetAutomatedMarketMakerPair(address indexed pair, bool indexed value);
    
    event SwapAndLiquify(
        uint256 tokensSwapped,
        uint256 ethReceived,
        uint256 tokensIntoLiquidity
    );

    constructor(address _authority, address _marketingWallet, address _buybackWallet, address _dai) 
    ERC20("Sins", "SIN", 9) 
    ERC20Permit("Sins") 
    SinsAccessControlled(ISinsAuthority(_authority)) {

        IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
        uniswapV2Router = _uniswapV2Router;
        dai = _dai;
        uniswapV2Pair = IUniswapV2Factory(_uniswapV2Router.factory()).createPair(address(this), _dai);
        _setAutomatedMarketMakerPair(address(uniswapV2Pair), true);

        initialSupply = 50000*1e9;
        maxTransactionAmount = initialSupply * 5 / 1000; // 0.5% maxTransactionAmountTxn
        maxWallet = initialSupply * 10 / 1000; // 1% maxWallet
        _mint(authority.governor(), initialSupply);
        
        uint256 _buyMarketingFee = 2;
        uint256 _buyLiquidityFee = 3;
        uint256 _buyBurnFee = 1;
        uint256 _buyBuybackFee = 0;

        uint256 _sellMarketingFee = 9;
        uint256 _sellLiquidityFee = 3;
        uint256 _sellBurnFee = 1;
        uint256 _sellBuybackFee = 2;
        
    
        buyMarketingFee = _buyMarketingFee;
        buyLiquidityFee = _buyLiquidityFee;
        buyBurnFee = _buyBurnFee;
        buyBuybackFee = _buyBuybackFee;
        buyTotalFees = buyMarketingFee + buyLiquidityFee + buyBurnFee + buyBuybackFee;

        sellMarketingFee = _sellMarketingFee;
        sellLiquidityFee = _sellLiquidityFee;
        sellBurnFee = _sellBurnFee;
        sellBuybackFee = _sellBuybackFee;
        sellTotalFees = sellMarketingFee + sellLiquidityFee + sellBurnFee + sellBuybackFee;
        
        marketingWallet = address(_marketingWallet);
        buybackWallet = address(_buybackWallet);

        // exclude from paying fees or having max transaction amount
        excludeFromFees(authority.governor(), true);
        excludeFromFees(address(this), true);
        excludeFromFees(address(0xdead), true);
        
    }

    receive() external payable {

  	}

    // remove limits after token is stable
    function removeLimits() external onlyGovernor returns (bool){
        limitsInEffect = false;
        sellMarketingFee = 4;
        sellLiquidityFee = 3;
        sellBurnFee = 1;
        sellBuybackFee = 0;
        sellTotalFees = sellMarketingFee + sellLiquidityFee + sellBurnFee + sellBuybackFee;
        return true;
    }


    function updateTaxDistributor(address _taxDistributor) external onlyGovernor {
        taxDistributor = _taxDistributor;
    }


    function updateMaxTxnAmount(uint256 newNum) external onlyGovernor {
        require(newNum >= (totalSupply() * 1 / 1000)/1e9, "Cannot set maxTransactionAmount lower than 0.1%");
        maxTransactionAmount = newNum * (10**9);
    }

    function updateMaxWalletAmount(uint256 newNum) external onlyGovernor {
        require(newNum >= (totalSupply() * 5 / 1000)/1e9, "Cannot set maxWallet lower than 0.5%");
        maxWallet = newNum * (10**9);
    }

    function excludeFromMaxTransaction(address updAds, bool isEx) public onlyGovernor {
        _isExcludedMaxTransactionAmount[updAds] = isEx;
    }
    
    // disable Transfer delay - cannot be reenabled
    function disableTransferDelay() external onlyGovernor returns (bool){
        transferDelayEnabled = false;
        return true;
    }



    // once enabled, can never be turned off
    function enableTrading() external onlyGovernor {
        tradingActive = true;
        swapEnabled = true;
    }

    function setAutomatedMarketMakerPair(address pair, bool value) public onlyGovernor {
        require(pair != uniswapV2Pair, "The pair cannot be removed from automatedMarketMakerPairs");

        _setAutomatedMarketMakerPair(pair, value);
    }

    function _setAutomatedMarketMakerPair(address pair, bool value) private {
        automatedMarketMakerPairs[pair] = value;

        emit SetAutomatedMarketMakerPair(pair, value);
    }


    function excludeFromFees(address account, bool excluded) public onlyGovernor {
        _isExcludedFromFees[account] = excluded;
        emit ExcludeFromFees(account, excluded);
    }

    // only use to disable contract sales if absolutely necessary (emergency use only)
    function updateSwapEnabled(bool enabled) external onlyGovernor{
        swapEnabled = enabled;
    }

    function updateBuyFees(uint256 _marketingFee, uint256 _liquidityFee, uint256 _burnFee, uint256 _buybackFee) external onlyGovernor {
        buyMarketingFee = _marketingFee;
        buyLiquidityFee = _liquidityFee;
        buyBurnFee = _burnFee;
        buyBuybackFee = _buybackFee;
        buyTotalFees = buyMarketingFee + buyLiquidityFee + buyBurnFee + buyBuybackFee;
        require(buyTotalFees <= 15, "Must keep fees at 15% or less");
    }
    
    function updateSellFees(uint256 _marketingFee, uint256 _liquidityFee, uint256 _burnFee, uint256 _buybackFee) external onlyGovernor {
        sellMarketingFee = _marketingFee;
        sellLiquidityFee = _liquidityFee;
        sellBurnFee = _burnFee;
        sellBuybackFee = _buybackFee;
        sellTotalFees = sellMarketingFee + sellLiquidityFee + sellBurnFee + sellBuybackFee;
        require(sellTotalFees <= 15, "Must keep fees at 15% or less");
    }

    function updateMarketingWallet(address newMarketingWallet) external onlyGovernor {
        emit marketingWalletUpdated(newMarketingWallet, marketingWallet);
        marketingWallet = newMarketingWallet;
    }

    function updateBuybackWallet(address newBuybackWallet) external onlyGovernor {
        emit buybackWalletUpdated(newBuybackWallet, buybackWallet);
        buybackWallet = newBuybackWallet;
    }

    function isExcludedFromFees(address account) public view returns(bool) {
        return _isExcludedFromFees[account];
    }

    function mint(address account_, uint256 amount_) external override onlyVault {
        _mint(account_, amount_);
    }

    function burn(uint256 amount) external override {
        _burn(msg.sender, amount);
    }

    function burnFrom(address account_, uint256 amount_) external override {
        _burnFrom(account_, amount_);
    }

    function _burnFrom(address account_, uint256 amount_) internal {
        uint256 decreasedAllowance_ = allowance(account_, msg.sender).sub(amount_, "ERC20: burn amount exceeds allowance");

        _approve(account_, msg.sender, decreasedAllowance_);
        _burn(account_, amount_);
    }

    function _transfer(
        address from,
        address to,
        uint256 amount
    ) internal override {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");
        
         if(amount == 0) {
            super._transfer(from, to, 0);
            return;
        }

        if(limitsInEffect){
            if (
                from != authority.governor() &&
                to != authority.governor() &&
                to != address(0) &&
                to != address(0xdead) &&
                !swapping
            ){
                if(!tradingActive){
                    require(_isExcludedFromFees[from] || _isExcludedFromFees[to], "Trading is not active.");
                }

                // at launch if the transfer delay is enabled, ensure the block timestamps for purchasers is set -- during launch.  
                if (transferDelayEnabled){
                    if (to != authority.governor() && to != address(uniswapV2Router) && to != address(uniswapV2Pair)){
                        require(_holderLastTransferTimestamp[tx.origin] < block.number, "_transfer:: Transfer Delay enabled.  Only one purchase per block allowed.");
                        _holderLastTransferTimestamp[tx.origin] = block.number;
                    }
                }


                //when buy
                if (automatedMarketMakerPairs[from] && !_isExcludedMaxTransactionAmount[to]) {
                        require(amount <= maxTransactionAmount, "Buy transfer amount exceeds the maxTransactionAmount.");
                        require(amount + balanceOf(to) <= maxWallet, "Max wallet exceeded");
                }

                //when sell
                else if (automatedMarketMakerPairs[to] && !_isExcludedMaxTransactionAmount[from]) {
                        require(amount <= maxTransactionAmount, "Sell transfer amount exceeds the maxTransactionAmount.");
                }
                else if(!_isExcludedMaxTransactionAmount[to]){
                    require(amount + balanceOf(to) <= maxWallet, "Max wallet exceeded");
                }
            }
        }
		
        if( 
            swapEnabled &&
            !swapping &&
            !_isExcludedFromFees[from] &&
            !_isExcludedFromFees[to] &&
            !automatedMarketMakerPairs[from]
        ) {
            swapping = true;
            
            swapBack();

            swapping = false;
        }
        

        bool takeFee = !swapping;

        // if any account belongs to _isExcludedFromFee account then remove the fee
        if(_isExcludedFromFees[from] || _isExcludedFromFees[to]) {
            takeFee = false;
        }
        
        uint256 fees = 0;
        tokensForBurn = 0;
        // only take fees on buys/sells, do not take on wallet transfers
        if(takeFee){
            // on sell
            if (automatedMarketMakerPairs[to] && sellTotalFees > 0){
                fees = amount.mul(sellTotalFees).div(100);
                tokensForLiquidity += fees * sellLiquidityFee / sellTotalFees;
                tokensForBurn = fees * sellBurnFee / sellTotalFees;
                tokensForBuyback += fees * sellBuybackFee / sellTotalFees;
                tokensForMarketing += fees * sellMarketingFee / sellTotalFees;
            }
            // on buy
            else if(automatedMarketMakerPairs[from] && buyTotalFees > 0) {
        	    fees = amount.mul(buyTotalFees).div(100);
        	    tokensForLiquidity += fees * buyLiquidityFee / buyTotalFees;
                tokensForBurn = fees * buyBurnFee / buyTotalFees;
                tokensForBuyback += fees * buyBuybackFee / buyTotalFees;
                tokensForMarketing += fees * buyMarketingFee / buyTotalFees;
            }
            
            if(fees-tokensForBurn > 0){    
                super._transfer(from, address(this), fees.sub(tokensForBurn));
            }
            if (tokensForBurn > 0){
                super._transfer(from, deadAddress, tokensForBurn);
            }
        	
        	amount -= fees;
        }

        super._transfer(from, to, amount);
    }


    function swapTokensForDai(uint256 tokenAmount) public {

        // generate the uniswap pair path of token -> weth
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = dai;

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

        // make the swap
        uniswapV2Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            tokenAmount,
            0, // accept any amount of ETH
            path,
            taxDistributor,
            block.timestamp
        );
        
    }

    function swapBack() public {
        uint256 contractBalance = balanceOf(address(this));
        uint256 totalTokensToSwap = tokensForLiquidity + tokensForMarketing + tokensForBuyback;
        
        if(contractBalance == 0 || totalTokensToSwap == 0) {return;}
        
        if(contractBalance > totalSupply() * 5 / 10000 * 20){
          contractBalance = totalSupply() * 5 / 10000 * 20;
        }
        // Halve the amount of liquidity tokens
        uint256 liquidityTokens = contractBalance * tokensForLiquidity / totalTokensToSwap / 2;
        uint256 amountToSwapForDai = contractBalance.sub(liquidityTokens);
        
        uint256 initialDaiBalance = IERC20(dai).balanceOf(taxDistributor);

        swapTokensForDai(amountToSwapForDai); 
        
        uint256 daiBalance = IERC20(dai).balanceOf(taxDistributor).sub(initialDaiBalance);
        

        uint256 daiForMarketing = daiBalance.mul(tokensForMarketing).div(totalTokensToSwap);
        uint256 daiForBuyback = daiBalance.mul(tokensForBuyback).div(totalTokensToSwap);
        
        uint256 daiForLiquidity = daiBalance - daiForMarketing - daiForBuyback;

        super._transfer(address(this), taxDistributor, liquidityTokens);

        ITaxDistributor(taxDistributor).distribute(address(uniswapV2Router), dai, marketingWallet, daiForBuyback, buybackWallet, liquidityTokens, daiForLiquidity, authority.governor());
        
        tokensForLiquidity = 0;
        tokensForMarketing = 0;
        tokensForBuyback = 0;

    }


}
function removeLimits() external onlyGovernor returns (bool){
    limitsInEffect = false;
    sellMarketingFee = 4;
    sellLiquidityFee = 3;
    sellBurnFee = 1;
    sellBuybackFee = 0;
    sellTotalFees = sellMarketingFee + sellLiquidityFee + sellBurnFee + sellBuybackFee;
    return true;
}

// remove limits after token is stable